diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 0a55843cfd..8b5b165536 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -54,6 +54,7 @@ jobs: cp -r ../build/docs/html/ docs/ touch docs/.nojekyll + echo -n docs.comma.ai > docs/CNAME git add -f . git commit -m "build docs" diff --git a/.gitignore b/.gitignore index afb3d5b1c3..e414e4301a 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,7 @@ comma*.sh selfdrive/modeld/thneed/compile selfdrive/modeld/models/*.thneed +selfdrive/modeld/models/*.pkl *.bz2 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..a93556b1ea --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,162 @@ +variables: + BUILD_DIR: "/data/openpilot" + OUTPUT_DIR: "${CI_PROJECT_DIR}/output" + CI_DIR: "${CI_PROJECT_DIR}/release/ci" + VERSION: "unknown" + GIT_SUBMODULE_STRATEGY: recursive + GIT_DISCOVERY_ACROSS_FILESYSTEM: 1 + DEV_C3_SOURCE_BRANCH: "master-dev-c3" + +stages: + - build + - commit_changes + - publish + +default: + tags: + - sunnypilot + - x86 + +.default_before_script: &default_before_script + - 'export VERSION=$(date "+%Y.%m.%d")-${CI_PIPELINE_IID}' + - 'mkdir -p "${BUILD_DIR}/"' + - 'git config --global user.email "gitlab@pipeline.com"' + - 'git config --global user.name "Gitlab Pipeline"' + + +workflow: # If running on any branch other than main, use the `aws-datacontracts-dev` account; otherwise use `aws-datacontracts` + rules: + # We are an MR, but it's a draft, we won't proceed with anything. + - if: '$CI_MERGE_REQUEST_TITLE =~ /^wip:/i || $CI_MERGE_REQUEST_TITLE =~ /^draft:/i' + when: never + # We are a merge request + - if: $CI_MERGE_REQUEST_IID #|| $CI_COMMIT_REF_NAME == "gitlab-pipelines" # TBD once merged + variables: + NEW_BRANCH: ${CI_COMMIT_REF_NAME}-prebuilt + when: always + + # Below are the rules when a commit is done (code has been added to the branch) + # Commit to master-dev-c3 + - if: $CI_COMMIT_REF_NAME == $DEV_C3_SOURCE_BRANCH + variables: + NEW_BRANCH: "dev-c3" + when: always + #commit made to main (master) + - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH + variables: + NEW_BRANCH: "staging-c3" + when: always + # if tag + - if: $CI_COMMIT_TAG + variables: + NEW_BRANCH: "release-c3" + - when: always + + +build: + stage: build + cache: + key: "${CI_COMMIT_REF_SLUG}" + paths: + - "${CI_DIR}/scons_cache" + before_script: + - *default_before_script + - "echo Starting build stage..." + - "echo BUILD_DIR: ${BUILD_DIR}" + - "echo CI_DIR: ${CI_DIR}" + - "echo VERSION: ${VERSION}" + - "echo CI_COMMIT_REF_NAME: ${CI_COMMIT_REF_NAME}" + - git config --global --add safe.directory ${CI_PROJECT_DIR} + script: + - export PYTHONPATH="$BUILD_DIR" + - "echo Calling to build [${CI_DIR}/build.sh ${CI_PROJECT_DIR} ${BUILD_DIR} ${VERSION}]" + - "echo Building Panda..." + - scons -j$(nproc) cache_dir=${CI_DIR}/scons_cache ${CI_PROJECT_DIR}/panda + - mkdir -p ${BUILD_DIR} + - ls -la ${BUILD_DIR} + - "echo Building Rest..." + - cp -pR --parents $(cat release/files_common release/files_tici) $BUILD_DIR/ + - cd $BUILD_DIR + - sed -i '/from .board.jungle import PandaJungle, PandaJungleDFU/s/^/#/' panda/__init__.py # comment panda jungle when prebuilt + - scons -j$(nproc) cache_dir=${CI_DIR}/scons_cache + + # Cleanup + - echo "Doing cleanup for prebuilt\n\n" + - echo "Deleting specified files..." + - find ${BUILD_DIR} \( -name '*.a' -o -name '*.o' -o -name '*.os' -o -name '*.pyc' -o -name 'moc_*' -o -name '*.cc' -o -name "Jenkinsfile" -o -name "supercombo.onnx" -o -name ".sconsign.dblite" \) -print -delete + + - echo "Deleting Headers from selfdrive/ui path..." + - find ${BUILD_DIR}/selfdrive/ui -type f -name '*.h' -print -delete + + - echo "Deleting the following folders and subdirectories..." + - find "${BUILD_DIR}" -type d \( -path "*panda/board" -o -path "*panda/certs" -o -path "*panda/crypto" -o -path "*release" -o -path "*.github" -o -path "*selfdrive/ui/replay" -o -path "*__pycache__" \) -print -exec rm -rf {} + + + # Move back signed panda fw + - mkdir -p ${BUILD_DIR}/panda/board/obj + - cp ${CI_PROJECT_DIR}/panda/board/obj/panda.bin.signed ${BUILD_DIR}/panda/board/obj/panda.bin.signed + - cp ${CI_PROJECT_DIR}/panda/board/obj/panda_h7.bin.signed ${BUILD_DIR}/panda/board/obj/panda_h7.bin.signed + - cp ${CI_PROJECT_DIR}/panda/board/obj/bootstub.panda.bin ${BUILD_DIR}/panda/board/obj/bootstub.panda.bin + - cp ${CI_PROJECT_DIR}/panda/board/obj/bootstub.panda_h7.bin ${BUILD_DIR}/panda/board/obj/bootstub.panda_h7.bin + - touch ${BUILD_DIR}/prebuilt + - mkdir -p ${OUTPUT_DIR} + - shopt -s dotglob && mv ${BUILD_DIR}/* ${OUTPUT_DIR} + artifacts: + paths: + - ${OUTPUT_DIR}/ + tags: [ 'sunnypilot', 'tici' ] + rules: + - if: $CI_MERGE_REQUEST_IID + when: manual + - if: $NEW_BRANCH + when: always + + +commit changes for public branch: + variables: + GIT_SUBMODULE_STRATEGY: normal + GIT_ORIGIN: git@gitlab.com:sunnypilot/public/sunnypilot.git + stage: commit_changes + dependencies: + - build + before_script: + - 'apk update && apk upgrade' + - 'apk add git bash openssh' + - 'eval $(ssh-agent -s)' + - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - + - 'mkdir -p ~/.ssh/' + - 'chmod 700 ~/.ssh' + - 'ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts' # Adding gitlab to trusted + - 'chmod 644 ~/.ssh/known_hosts' + - *default_before_script + script: + #- GIT_ORIGIN=$(echo "${CI_REPOSITORY_URL}" | sed -e 's|https\?://gitlab-ci-token:.*@|ssh://git@|g') + - echo "${GIT_ORIGIN}" + - echo "Calling to publish [${CI_DIR}/publish.sh ${CI_PROJECT_DIR} ${OUTPUT_DIR} ${NEW_BRANCH} ${VERSION} ${GIT_ORIGIN}]" + - git config --global --add safe.directory ${OUTPUT_DIR} + - $CI_DIR/publish.sh "${CI_PROJECT_DIR}" "${OUTPUT_DIR}" "${NEW_BRANCH}" "${VERSION}" "${GIT_ORIGIN}" + rules: + - if: $NEW_BRANCH + when: on_success + - when: never + +publish to github: + variables: + GIT_SUBMODULE_STRATEGY: normal + stage: publish + dependencies: + - build + before_script: + - 'apk update && apk upgrade' + - 'apk add git bash openssh' + - 'eval $(ssh-agent -s)' + - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - + - 'mkdir -p ~/.ssh/' + - 'chmod 700 ~/.ssh' + - 'ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts' # Adding gitlab to trusted + - 'chmod 644 ~/.ssh/known_hosts' + - *default_before_script + script: + - echo "This part is not yet implemented intentionally, just an example" + rules: + - if: $NEW_BRANCH + when: manual diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6487cf0821..880912e271 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,7 +41,7 @@ repos: args: ['--explicit-package-bases'] exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.292 + rev: v0.1.1 hooks: - id: ruff exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 5fc9254d38..457c0a8816 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,15 +1,17 @@ -sunnypilot - 0.9.5.1 (2023-10-xx) +sunnypilot - 0.9.5.1 (2023-11-xx) ======================== -* UPDATED: Synced with commaai's master commit 41a04b4 +* UPDATED: Synced with commaai's master commit 9528383 * NEW❗: Night Strike driving model * NEW❗: Onroad Settings Panel * Onroad buttons (i.e., DLP, GAC) moved to its dedicated panel * Driving Personality * Dynamic Lane Profile (DLP) + * Dynamic Experimental Control (DEC) * Speed Limit Control (SLC) * NEW❗: Display main feature status on onroad view in real-time * GAP - Driving Personality * DLP - Dynamic Lane Profile + * DEC - Dynamic Experimental Control * SLC - Speed Limit Control * NEW❗: Dynamic Experimental Control (DEC) thanks to dragonpilot-community! * Automatically determines and selects between openpilot ACC and openpilot End to End longitudinal based on conditions for a more natural drive @@ -47,15 +49,15 @@ sunnypilot - 0.9.5.1 (2023-10-xx) * NEW❗: Custom Tuning for setting offline and live values in real-time * UPDATED: Auto-detect custom Mapbox token if a personal Mapbox token is provided * REMOVED: "Enable Mapbox Navigation" toggle -* UPDATED: Update color from Green 🟢 to Blue 🔵 for settings toggles and buttons -* UPDATED: Button redesign for Driving Personality, Dynamic Lane Profile, and Sidebar Temperature +* UI updates + * New Settings menu redesign and improved interactions * FIXED: Retain hotspot/tethering state was not consistently saved * FIXED: Map stuck in "Map Loading" if comma Prime is active -* FIXED: UI elements adjustments * FIXED: OpenStreetMap implementation on C3X devices * M-TSC * Altitude (ALT.) display on Developer UI * Current street name on top of driving screen when "OSM Debug UI" is enabled +* Hyundai Kona Non-SCC 2019 support thanks to Quex! * Kia Seltos Non-SCC 2023-24 support thanks to Moodkiller and jeroid_! sunnypilot - 0.9.4.1 (2023-08-11) diff --git a/Jenkinsfile b/Jenkinsfile index e748dc7dbf..35c16e29c9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -21,6 +21,8 @@ source ~/.bash_profile if [ -f /TICI ]; then source /etc/profile + rm -rf ~/.commacache + if ! systemctl is-active --quiet systemd-resolved; then echo "restarting resolved" sudo systemctl start systemd-resolved @@ -154,7 +156,7 @@ node { ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR ./build_devel.sh"], ["build openpilot", "cd selfdrive/manager && ./build.py"], ["check dirty", "release/check-dirty.sh"], - ["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"], + ["onroad tests", "pytest selfdrive/test/test_onroad.py -s"], ["time to onroad", "cd selfdrive/test/ && pytest test_time_to_onroad.py"], ]) }, diff --git a/RELEASES.md b/RELEASES.md index 6adc58197b..96028a9787 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,6 +2,7 @@ Version 0.9.5 (2023-XX-XX) ======================== * New driving model * Improved navigate on openpilot performance using navigation instructions as an additional model input +* Cadillac Escalade ESV 2019 support thanks to twilsonco! * Hyundai Azera 2022 support thanks to sunnyhaibin! * Hyundai Azera Hybrid 2020 support thanks to chanhojung and haram-KONA! * Hyundai Custin 2023 support thanks to sunnyhaibin and Saber422! diff --git a/SConstruct b/SConstruct index a086f8448a..9a18050122 100644 --- a/SConstruct +++ b/SConstruct @@ -230,7 +230,8 @@ if GetOption('compile_db'): env.CompilationDatabase('compile_commands.json') # Setup cache dir -cache_dir = '/data/scons_cache' if AGNOS else '/tmp/scons_cache' +default_cache_dir = '/data/scons_cache' if AGNOS else '/tmp/scons_cache' +cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir) CacheDir(cache_dir) Clean(["."], cache_dir) @@ -382,7 +383,6 @@ SConscript(['rednose/SConscript']) # Build system services SConscript([ - 'system/clocksd/SConscript', 'system/proclogd/SConscript', 'system/ubloxd/SConscript', 'system/loggerd/SConscript', diff --git a/common/params.cc b/common/params.cc index bcee157518..373e203288 100644 --- a/common/params.cc +++ b/common/params.cc @@ -211,6 +211,7 @@ std::unordered_map keys = { {"AccMadsCombo", PERSISTENT}, {"AmapKey1", PERSISTENT}, {"AmapKey2", PERSISTENT}, + {"ApiCache_DriveStats", PERSISTENT}, {"AutoLaneChangeTimer", PERSISTENT}, {"AutoLaneChangeBsmDelay", PERSISTENT}, {"BelowSpeedPause", PERSISTENT}, @@ -230,12 +231,10 @@ std::unordered_map keys = { {"CustomStockLong", PERSISTENT}, {"CustomStockLongPlanner", PERSISTENT}, {"CustomTorqueLateral", PERSISTENT}, - {"DevUI", PERSISTENT}, {"DevUIInfo", PERSISTENT}, {"DisableOnroadUploads", PERSISTENT}, {"DisengageLateralOnBrake", PERSISTENT}, {"DynamicExperimentalControl", PERSISTENT}, - {"DynamicExperimentalControlToggle", PERSISTENT}, {"DynamicLaneProfile", PERSISTENT}, {"EnableAmap", PERSISTENT}, {"EnableDebugSnapshot", PERSISTENT}, @@ -285,7 +284,6 @@ std::unordered_map keys = { {"ShowDebugUI", PERSISTENT}, {"SidebarTemperatureOptions", PERSISTENT}, {"SpeedLimitControl", PERSISTENT}, - {"SpeedLimitPercOffset", PERSISTENT}, {"SpeedLimitValueOffset", PERSISTENT}, {"SpeedLimitOffsetType", PERSISTENT}, {"StandStillTimer", PERSISTENT}, diff --git a/common/prefix.py b/common/prefix.py index 6b7e7e2fd7..c1744e8ff7 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -14,6 +14,7 @@ class OpenpilotPrefix: self.clean_dirs_on_exit = clean_dirs_on_exit def __enter__(self): + self.original_prefix = os.environ.get('OPENPILOT_PREFIX', None) os.environ['OPENPILOT_PREFIX'] = self.prefix try: os.mkdir(self.msgq_path) @@ -28,6 +29,8 @@ class OpenpilotPrefix: self.clean_dirs() try: del os.environ['OPENPILOT_PREFIX'] + if self.original_prefix is not None: + os.environ['OPENPILOT_PREFIX'] = self.original_prefix except KeyError: pass return False diff --git a/conftest.py b/conftest.py index 2cc906b919..ee46436d3c 100644 --- a/conftest.py +++ b/conftest.py @@ -10,8 +10,13 @@ def openpilot_function_fixture(): # setup a clean environment for each test with OpenpilotPrefix(): + prefix = os.environ["OPENPILOT_PREFIX"] + yield + # ensure the test doesn't change the prefix + assert "OPENPILOT_PREFIX" in os.environ and prefix == os.environ["OPENPILOT_PREFIX"] + os.environ.clear() os.environ.update(starting_env) diff --git a/docs/CARS.md b/docs/CARS.md index 670614c3fc..d6961f6f56 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 265 Supported Cars +# 267 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -20,6 +20,7 @@ A supported vehicle is one that just works when you install a comma device. All |Buick|LaCrosse 2017-19[4](#footnotes)|Driver Confidence Package 2|openpilot|18 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Cadillac|Escalade 2017[4](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Cadillac|Escalade ESV 2016[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Cadillac|Escalade ESV 2019[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Silverado 1500 2020-21|Safety 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 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -74,7 +75,8 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -139,7 +141,7 @@ A supported vehicle is one that just works when you install a comma device. All |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 RJ45 cable (7 ft)
- 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 RJ45 cable (7 ft)
- 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[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento Hybrid 2023[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 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[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 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[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 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[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/panda b/panda index cebcc2e09b..343ad38a8b 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit cebcc2e09b53296e87e582d251f62788fe45696b +Subproject commit 343ad38a8b04efd26ca0f35764d150d47b9c3db2 diff --git a/poetry.lock b/poetry.lock index 28b588606e..93e907a578 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -314,13 +314,13 @@ files = [ [[package]] name = "azure-core" -version = "1.29.4" +version = "1.29.5" description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "azure-core-1.29.4.tar.gz", hash = "sha256:500b3aa9bf2e90c5ccc88bb105d056114ca0ce7d0ce73afb8bc4d714b2fc7568"}, - {file = "azure_core-1.29.4-py3-none-any.whl", hash = "sha256:b03261bcba22c0b9290faf9999cedd23e849ed2577feee90515694cea6bc74bf"}, + {file = "azure-core-1.29.5.tar.gz", hash = "sha256:52983c89d394c6f881a121e5101c5fa67278ca3b1f339c8fb2ef39230c70e9ac"}, + {file = "azure_core-1.29.5-py3-none-any.whl", hash = "sha256:0fa04b7b1f7d44a4fb8468c4093deb2ea01fdf4faddbf802ed9205615f99d68c"}, ] [package.dependencies] @@ -350,13 +350,13 @@ msal-extensions = ">=0.3.0,<2.0.0" [[package]] name = "azure-storage-blob" -version = "12.18.2" +version = "12.18.3" description = "Microsoft Azure Blob Storage Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "azure-storage-blob-12.18.2.tar.gz", hash = "sha256:e11935348981ffc005b848b55db25c04f2d1f90e1ee33000659906b763cf14c8"}, - {file = "azure_storage_blob-12.18.2-py3-none-any.whl", hash = "sha256:ffd864bf9abf33dfc72c6ef37899a19bd9d585a946a2c61e288b4420c035df3a"}, + {file = "azure-storage-blob-12.18.3.tar.gz", hash = "sha256:d8ced0deee3367fa3d4f3d1a03cd9edadf4440c0a371f503d623fa6c807554ee"}, + {file = "azure_storage_blob-12.18.3-py3-none-any.whl", hash = "sha256:c278dde2ac41857a68d615c9f2b36d894ba877a7e84d62795603c7e79d0bb5e9"}, ] [package.dependencies] @@ -370,13 +370,13 @@ aio = ["azure-core[aio] (>=1.28.0,<2.0.0)"] [[package]] name = "babel" -version = "2.13.0" +version = "2.13.1" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ - {file = "Babel-2.13.0-py3-none-any.whl", hash = "sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec"}, - {file = "Babel-2.13.0.tar.gz", hash = "sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210"}, + {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, + {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, ] [package.extras] @@ -554,101 +554,101 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.0" +version = "3.3.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, - {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, + {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, + {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, ] [[package]] @@ -701,13 +701,13 @@ test = ["pytest-cov"] [[package]] name = "cloudpickle" -version = "2.2.1" -description = "Extended pickling support for Python objects" +version = "3.0.0" +description = "Pickler class to extend the standard pickle.Pickler functionality" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "cloudpickle-2.2.1-py3-none-any.whl", hash = "sha256:61f594d1f4c295fa5cd9014ceb3a1fc4a70b0de1164b94fbc2d854ccba056f9f"}, - {file = "cloudpickle-2.2.1.tar.gz", hash = "sha256:d89684b8de9e34a2a43b3460fbca07d09d6e25ce858df4d5a44240403b6178f5"}, + {file = "cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7"}, + {file = "cloudpickle-3.0.0.tar.gz", hash = "sha256:996d9a482c6fb4f33c1a35335cf8afd065d2a56e973270364840712d9131a882"}, ] [[package]] @@ -966,69 +966,69 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "cython" -version = "3.0.3" +version = "3.0.4" description = "The Cython compiler for writing C extensions in the Python language." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "Cython-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85073ab414ff432d2a39d36cb49c39ce69f30b53daccc7699bfad0ce3d1b539a"}, - {file = "Cython-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c1d9bd2bcb9b1a195dd23b359771857df8ebd4a1038fb37dd155d3ea38c09c"}, - {file = "Cython-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9296f332523d5c550ebae694483874d255264cff3281372f25ea5f2739b96651"}, - {file = "Cython-3.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52ed47edbf48392dd0f419135e7ff59673f6b32d27d3ffc9e61a515571c050d"}, - {file = "Cython-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6f63e959d13775472d37e731b2450d120e8db87e956e2de74475e8f17a89b1fb"}, - {file = "Cython-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22d268c3023f405e13aa0c1600389794694ab3671614f8e782d89a1055da0858"}, - {file = "Cython-3.0.3-cp310-cp310-win32.whl", hash = "sha256:51850f277660f67171135515e45edfc8815f723ff20768e39cb9785b2671062f"}, - {file = "Cython-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bff1fec968a6b2ca452ae9bff6d6d0bf8486427d4d791e85543240266b6915e0"}, - {file = "Cython-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587d664ff6bd5b03611ddc6ef320b7f8677d824c45d15553f16a69191a643843"}, - {file = "Cython-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3192cd780435fca5ae5d79006b48cbf0ea674853b5a7b0055a122045bff9d84e"}, - {file = "Cython-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7578b59ffd0d9c95ae6f7ae852309918915998b7fe0ed2f8725a683de8da276"}, - {file = "Cython-3.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f05889eb1b5a95a7adf97303279c2d13819ff62292e10337e6c940dbf570b5d"}, - {file = "Cython-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1d3416c24a1b7bf3a2d9615a7f9f12b00fac0b94fb2e61449e0c1ecf20d6ed52"}, - {file = "Cython-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4cc0f7244da06fdc6a4a7240df788805436b6fb7f20edee777eb77777d9d2eb1"}, - {file = "Cython-3.0.3-cp311-cp311-win32.whl", hash = "sha256:845e24ee70c204062e03f813114751387abf454b29410336797582e04abbc07b"}, - {file = "Cython-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e3ad109bdf40f55318e001cad12bcc00e8119569b49f72e442c082355617b036"}, - {file = "Cython-3.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14b898ec2fdeea68f81bd3838b035800b173b59ed532674f65a82724bab35d3b"}, - {file = "Cython-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:188705eeae094bb716bc3e3d0da4e13469f0a0de803b65dfd63fe7eb78ec6173"}, - {file = "Cython-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eb128fa40305f18eaa4d8dd0980033b92db86aada927181d3c3d561aa0634db"}, - {file = "Cython-3.0.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80bd3167e689419cdaf7ede0d20a9f126b9698a43b1f8d3e8f54b970c7a6cd07"}, - {file = "Cython-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d0c7b315f6feb75e2c949dc7816da5626cdca097fea1c0d9f4fdb20d2f4ffc2a"}, - {file = "Cython-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:db9d4de4cd6cd3ad1c3f455aae877ad81a92b92b7cbb01dfb32b6306b873932b"}, - {file = "Cython-3.0.3-cp312-cp312-win32.whl", hash = "sha256:be1a679c7ad90813f9206c9d62993f3bd0cba9330668e97bb3f70c87ae94d5f5"}, - {file = "Cython-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:fa08259f4d176b86561eeff6954f9924099c0b0c128fc2cbfc18343c068ad8ca"}, - {file = "Cython-3.0.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:056340c49bf7861eb1eba941423e67620b7c85e264e9a5594163f1d1e8b95acc"}, - {file = "Cython-3.0.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cfbd60137f6fca9c29101d7517d4e341e0fd279ffc2489634e5e2dd592457c2"}, - {file = "Cython-3.0.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b7e71c16cab0814945014ffb101ead2b173259098bbb1b8138e7a547da3709"}, - {file = "Cython-3.0.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42b1ff0e19fb4d1fe68b60f55d46942ed246a323f6bbeec302924b78b4c3b637"}, - {file = "Cython-3.0.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5d6af87a787d5ce063e28e508fee34755a945e438c68ecda50eb4ea34c30e13f"}, - {file = "Cython-3.0.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0147a31fb73a063bb7b6c69fd843c1a2bad18f326f58048d4ee5bdaef87c9fbf"}, - {file = "Cython-3.0.3-cp36-cp36m-win32.whl", hash = "sha256:84084fa05cf9a67a85818fa72a741d1cae2e3096551158730730a3bafc3b2f52"}, - {file = "Cython-3.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8a6a9a2d98758768052e4ac1bea4ebc20fae69b4c19cb2bc5457c9174532d302"}, - {file = "Cython-3.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:94fa403de3a413cd41b8eb4ddb4adcbd66aa0a64f9a84d1c5f696c93572c83aa"}, - {file = "Cython-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e729fd633a5225570c5480b36e7c530c8a82e2ab6d2944ddbe1ddfff5bf181b1"}, - {file = "Cython-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59bf689409b0e51ef673e3dd0348727aef5b67e40f23f806be64c49cee321de0"}, - {file = "Cython-3.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0ac9ec822fad010248b4a59ac197975de38c95378d0f13201c181dd9b0a2624"}, - {file = "Cython-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8e78fc42a6e846941d23aba1aca587520ad38c8970255242f08f9288b0eeba85"}, - {file = "Cython-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e40ac8bd6d11355d354bb4975bb88f6e923ba30f85e38f1f1234b642634e4fc4"}, - {file = "Cython-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:77a920ae19fa1db5adb8a618cebb095ca4f56adfbf9fc32cb7008a590607b62b"}, - {file = "Cython-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0630527a8c9e8fed815c38524e418dab713f5d66f6ac9dc2151b41f3a7727304"}, - {file = "Cython-3.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4e956383e57d00b1fa6449b5ec03b9fa5fce2afd41ef3e518bee8e7c89f1616c"}, - {file = "Cython-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ec9e15b821ef7e3c38abe9e4df4e6dda7af159325bc358afd5a3c2d5027ccfe"}, - {file = "Cython-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18f4fb7cc6ad8e99e8f387ebbcded171a701bfbfd8cd3fd46156bf44bb4fd968"}, - {file = "Cython-3.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b50f4f75f89e7eef2ed9c9b60746bc4ab1ba2bc0dff64587133db2b63e068f09"}, - {file = "Cython-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5545d20d7a1c0cf17559152f7f4a465c3d5caace82dd051f82e2d753ae9fd956"}, - {file = "Cython-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1571b045ec1cb15c152c3949f3bd53ee0fa66d434271ea3d225658d99b7e721a"}, - {file = "Cython-3.0.3-cp38-cp38-win32.whl", hash = "sha256:3db04801fd15d826174f63ff45878d4b1e62aff27cf1ea96b186581052d24446"}, - {file = "Cython-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:75d42c8423ab299396f3c938445730600e32e4a2f0298f6f9df4d4a698fe8e16"}, - {file = "Cython-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:48bae87b657009e5648c21d4a92de9f3dc6fed3e35e92957fa8a07a18cea2313"}, - {file = "Cython-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ccde14ddc4b424435cb5722aa1529c254bbf3611e1ad9baea12d25e9c049361"}, - {file = "Cython-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c8e5afcc19861c3b22faafbe906c7e1b23f0595073ac10e21a80dec9e60e7dd"}, - {file = "Cython-3.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e1c9385e99eef299396b9a1e39790e81819446c6a83e249f6f0fc71a64f57a0"}, - {file = "Cython-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d49d20db27c9cfcf45bb1fbf68f777bd1e04e4b949e4e5172d9ee8c9419bc792"}, - {file = "Cython-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d12591939af93c59defea6fc5320ca099eb44e4694e3b2cbe72fb24406079b97"}, - {file = "Cython-3.0.3-cp39-cp39-win32.whl", hash = "sha256:9f40b27545d583fd7df0d3c1b76b3bcaf8a72dbd8d83d5486af2384015660de8"}, - {file = "Cython-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:74ba0f11b384246b7965169f08bf67d426e4957fee5c165571340217a9b43cfc"}, - {file = "Cython-3.0.3-py2.py3-none-any.whl", hash = "sha256:176953a8a2532e34a589625a40c934ff339088f2bf4ddaa2e5cb77b05ca0c25c"}, - {file = "Cython-3.0.3.tar.gz", hash = "sha256:327309301b01f729f173a94511cb2280c87ba03c89ed428e88f913f778245030"}, + {file = "Cython-3.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:096cb461bf8d913a4327d93ea38d18bc3dbc577a71d805be04754e4b2cc2c45d"}, + {file = "Cython-3.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf671d712816b48fa2731799017ed68e5e440922d0c7e13dc825c226639ff766"}, + {file = "Cython-3.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beb367fd88fc6ba8c204786f680229106d99da72a60f5906c85fc8d73640b01a"}, + {file = "Cython-3.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6619264ed43d8d8819d4f1cdb8a62ab66f87e92f06f3ff3e2533fd95a9945e59"}, + {file = "Cython-3.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c0fb9e7cf9db38918f19a803fab9bc7b2ed3f33a9e8319c616c464a0a8501b8d"}, + {file = "Cython-3.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c214f6e88ecdc8ff5d13f0914890445fdaad21bddc34a90cd14aeb3ad5e55e2e"}, + {file = "Cython-3.0.4-cp310-cp310-win32.whl", hash = "sha256:c9b1322f0d8ce5445fcc3a625b966f10c4182190026713e217d6f38d29930cb1"}, + {file = "Cython-3.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:146bfaab567157f4aa34114a37e3f98a3d9c4527ee99d4fd730cab56482bd3cf"}, + {file = "Cython-3.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8e0f98d950987b0f9d5e10c41236bef5cb4fba701c6e680af0b9734faa3a85e"}, + {file = "Cython-3.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fe227d6d8e2ea030e82abc8a3e361e31447b66849f8c069caa783999e54a8f2"}, + {file = "Cython-3.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6da74000a672eac0d7cf02adc140b2f9c7d54eae6c196e615a1b5deb694d9203"}, + {file = "Cython-3.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48cda82eb82ad2014d2ad194442ed3c46156366be98e4e02f3e29742cdbf94a0"}, + {file = "Cython-3.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4355a2cb03b257773c0d2bb6af9818c72e836a9b09986e28f52e323d87b1fc67"}, + {file = "Cython-3.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10b426adc3027d66303f5c7aa8b254d10ed80827ff5cce9e892d550b708dc044"}, + {file = "Cython-3.0.4-cp311-cp311-win32.whl", hash = "sha256:28de18f0d07eb34e2dd7b022ac30beab0fdd277846d07b7a08e69e6294f0762b"}, + {file = "Cython-3.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:9d31d76ed777a8a85be3f8f7f1cfef09b3bc33f6ec4abee1067dcef107f49778"}, + {file = "Cython-3.0.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d5a55749509c7f9f8a33bf9cc02cf76fd6564fcb38f486e43d2858145d735953"}, + {file = "Cython-3.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58cdfdd942cf5ffcee974aabfe9b9e26c0c1538fd31c1b03596d40405f7f4d40"}, + {file = "Cython-3.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b906997e7b98d7d29b84d10a5318993eba1aaff82ff7e1a0ac00254307913d7"}, + {file = "Cython-3.0.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f24114e1777604a28ae1c7a56a2c9964655f1031edecc448ad51e5abb19a279b"}, + {file = "Cython-3.0.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:07d0e69959f267b79ffd18ece8599711ad2f3d3ed1eddd0d4812d2a97de2b912"}, + {file = "Cython-3.0.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f7fcd93d15deceb2747b10266a39deccd94f257d610f3bbd52a7e16bc5908eda"}, + {file = "Cython-3.0.4-cp312-cp312-win32.whl", hash = "sha256:0aa2a6bb3ff67794d8d1dafaed22913adcbb327e96eca3ac44e2f3ba4a0ae446"}, + {file = "Cython-3.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:0021350f6d7022a37f598320460b84b2c0daccf6bb65641bbdbc8b990bdf4ad2"}, + {file = "Cython-3.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b72c426df1586f967b1c61d2f8236702d75c6bbf34abdc258a59e09155a16414"}, + {file = "Cython-3.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a9262408f05eef039981479b38b38252d5b853992e5bc54a2d2dd05a2a0178e"}, + {file = "Cython-3.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28af4e7dff1742cb0f0a4823102c89c62a2d94115b68f718145fcfe0763c6e21"}, + {file = "Cython-3.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e8c144e2c5814e46868d1f81e2f4265ca1f314a8187d0420cd76e9563294cf8"}, + {file = "Cython-3.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:19a64bf2591272348ab08bcd4a5f884259cc3186f008c9038b8ec7d01f847fd5"}, + {file = "Cython-3.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fc96efa617184b8581a02663e261b41c13a605da8ef4ba1ed735bf46184f815e"}, + {file = "Cython-3.0.4-cp36-cp36m-win32.whl", hash = "sha256:15d52f7f9d08b264c042aa508bf457f53654b55f533e0262e146002b1c15d1cd"}, + {file = "Cython-3.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:0650460b5fd6f16da4186e0a769b47db5532601e306f3b5d17941283d5e36d24"}, + {file = "Cython-3.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b3ddfc6f05410095ec11491dde05f50973e501567d21cbfcf5832d95f141878a"}, + {file = "Cython-3.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a0b92adfcac68dcf549daddec83c87a86995caa6f87bfb6f72de4797e1a6ad6"}, + {file = "Cython-3.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ada3659608795bb36930d9a206b8dd6b865d85e2999a02ce8b34f3195d88301"}, + {file = "Cython-3.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:061dec1be8d8b601b160582609a78eb08324a4ccf21bee0d04853a3e9dfcbefd"}, + {file = "Cython-3.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:bc42004f181373cd3b39bbacfb71a5b0606ed6e4c199c940cca2212ba0f79525"}, + {file = "Cython-3.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f124ac9ee41e1bfdfb16f53f1db85de296cd2144a4e8fdee8c3560a8fe9b6d5d"}, + {file = "Cython-3.0.4-cp37-cp37m-win32.whl", hash = "sha256:48b35ab009227ee6188991b5666aae1936b82a944f707c042cef267709de12b5"}, + {file = "Cython-3.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:861979428f749faa9883dc4e11e8c3fc2c29bd0031cf49661604459b53ea7c66"}, + {file = "Cython-3.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c7a7dd7c50d07718a5ac2bdea166085565f7217cf1e030cc07c22a8b80a406a7"}, + {file = "Cython-3.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d40d4135f76fb0ed4caa2d422fdb4231616615698709d3c421ecc733f1ac7ca0"}, + {file = "Cython-3.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:207f53893ca22d8c8f5db533f38382eb7ddc2d0b4ab51699bf052423a6febdad"}, + {file = "Cython-3.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0422a40a58dcfbb54c8b4e125461d741031ff046bc678475cc7a6c801d2a7721"}, + {file = "Cython-3.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ef4b144c5b29b4ea0b40c401458b86df8d75382b2e5d03e9f67f607c05b516a9"}, + {file = "Cython-3.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0612439f810cc281e51fead69de0545c4d9772a1e82149c119d1aafc1f6210ba"}, + {file = "Cython-3.0.4-cp38-cp38-win32.whl", hash = "sha256:b86871862bd65806ba0d0aa2b9c77fcdcc6cbd8d36196688f4896a34bb626334"}, + {file = "Cython-3.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:6603a287188dcbc36358a73a7be43e8a2ecf0c6a06835bdfdd1b113943afdd6f"}, + {file = "Cython-3.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0fc9e974419cc0393072b1e9a669f71c3b34209636d2005ff8620687daa82b8c"}, + {file = "Cython-3.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e84988d384dfba678387ea7e4f68786c3703543018d473605d9299c69a07f197"}, + {file = "Cython-3.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36299ffd5663203c25d3a76980f077e23b6d4f574d142f0f43943f57be445639"}, + {file = "Cython-3.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8529cf09919263a6826adc04c5dde9f1406dd7920929b16be19ee9848110e353"}, + {file = "Cython-3.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8692249732d62e049df3884fa601b70fad3358703e137aceeb581e5860e7d9b7"}, + {file = "Cython-3.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f234bc46043856d118ebd94b13ea29df674503bc94ced3d81ca46a1ad5b5b9ae"}, + {file = "Cython-3.0.4-cp39-cp39-win32.whl", hash = "sha256:c2215f436ce3cce49e6e318cb8f7253cfc4d3bea690777c2a5dd52ae93342504"}, + {file = "Cython-3.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:003ccc40e0867770db0018274977d1874e4df64983d5e3e36937f107e0b2fdf6"}, + {file = "Cython-3.0.4-py2.py3-none-any.whl", hash = "sha256:e5e2859f97e9cceb8e70b0934c56157038b8b083245898593008162a70536d7e"}, + {file = "Cython-3.0.4.tar.gz", hash = "sha256:2e379b491ee985d31e5faaf050f79f4a8f59f482835906efe4477b33b4fbe9ff"}, ] [[package]] @@ -1132,31 +1132,35 @@ typing = ["typing-extensions (>=4.7.1)"] [[package]] name = "fiona" -version = "1.9.4.post1" +version = "1.9.5" description = "Fiona reads and writes spatial data files" optional = false python-versions = ">=3.7" files = [ - {file = "Fiona-1.9.4.post1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:d6483a20037db2209c8e9a0c6f1e552f807d03c8f42ed0c865ab500945a37c4d"}, - {file = "Fiona-1.9.4.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbe158947099a83ad16f9acd3a21f50ff01114c64e2de67805e382e6b6e0083a"}, - {file = "Fiona-1.9.4.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2c7b09eecee3bb074ef8aa518cd6ab30eb663c6fdd0eff3c88d454a9746eaa"}, - {file = "Fiona-1.9.4.post1-cp310-cp310-win_amd64.whl", hash = "sha256:1da8b954f6f222c3c782bc285586ea8dd9d7e55e1bc7861da9cd772bca671660"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c671d8832287cda397621d79c5a635d52e4631f33a8f0e6fdc732a79a93cb96c"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b633a2e550e083805c638d2ab8059c283ca112aaea8241e170c012d2ee0aa905"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1faa625d5202b8403471bbc9f9c96b1bf9099cfcb0ee02a80a3641d3d02383e"}, - {file = "Fiona-1.9.4.post1-cp311-cp311-win_amd64.whl", hash = "sha256:39baf11ff0e4318397e2b2197de427b4eebdc49d4a9a7c1366f8a7ed682978a4"}, - {file = "Fiona-1.9.4.post1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d93c993265f6378b23f47708c83bddb3377ca6814a1f0b5a0ae0bee9c8d72cf8"}, - {file = "Fiona-1.9.4.post1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:b0387cae39e27f338fd948b3b50b6e6ce198cc4cec257fc91660849697c69dc3"}, - {file = "Fiona-1.9.4.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:450561d308d3ce7c7e30294822b1de3f4f942033b703ddd4a91a7f7f5f506ca0"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:71b023ef5248ebfa5524e7a875033f7db3bbfaf634b1b5c1ae36958d1eb82083"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74511d3755695d75cea0f4ff6f5e0c6c5d5be8e0d46dafff124c6a219e99b1eb"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:285f3dd4f96aa0a3955ed469f0543375b20989731b2dddc85124453f11ac62bc"}, - {file = "Fiona-1.9.4.post1-cp38-cp38-win_amd64.whl", hash = "sha256:a670ea4262cb9140445bcfc97cbfd2f508a058be342f4a97e966b8ce7696601f"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:ea7c44c15b3a653452b9b3173181490b7afc5f153b0473c145c43c0fbf90448b"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7bfb1f49e0e53f6cd7ad64ae809d72646266b37a7b9881205977408b443a8d79"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a585002a6385cc8ab0f66ddf3caf18711f531901906abd011a67a0cc89ab7b0"}, - {file = "Fiona-1.9.4.post1-cp39-cp39-win_amd64.whl", hash = "sha256:f5da66b723a876142937e683431bbaa5c3d81bb2ed3ec98941271bc99b7f8cd0"}, - {file = "Fiona-1.9.4.post1.tar.gz", hash = "sha256:5679d3f7e0d513035eb72e59527bb90486859af4405755dfc739138633106120"}, + {file = "fiona-1.9.5-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:5f40a40529ecfca5294260316cf987a0420c77a2f0cf0849f529d1afbccd093e"}, + {file = "fiona-1.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:374efe749143ecb5cfdd79b585d83917d2bf8ecfbfc6953c819586b336ce9c63"}, + {file = "fiona-1.9.5-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:35dae4b0308eb44617cdc4461ceb91f891d944fdebbcba5479efe524ec5db8de"}, + {file = "fiona-1.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:5b4c6a3df53bee8f85bb46685562b21b43346be1fe96419f18f70fa1ab8c561c"}, + {file = "fiona-1.9.5-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:6ad04c1877b9fd742871b11965606c6a52f40706f56a48d66a87cc3073943828"}, + {file = "fiona-1.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fb9a24a8046c724787719e20557141b33049466145fc3e665764ac7caf5748c"}, + {file = "fiona-1.9.5-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:d722d7f01a66f4ab6cd08d156df3fdb92f0669cf5f8708ddcb209352f416f241"}, + {file = "fiona-1.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:7ede8ddc798f3d447536080c6db9a5fb73733ad8bdb190cb65eed4e289dd4c50"}, + {file = "fiona-1.9.5-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:8b098054a27c12afac4f819f98cb4d4bf2db9853f70b0c588d7d97d26e128c39"}, + {file = "fiona-1.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d9f29e9bcbb33232ff7fa98b4a3c2234db910c1dc6c4147fc36c0b8b930f2e0"}, + {file = "fiona-1.9.5-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:f1af08da4ecea5036cb81c9131946be4404245d1b434b5b24fd3871a1d4030d9"}, + {file = "fiona-1.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:c521e1135c78dec0d7774303e5a1b4c62e0efb0e602bb8f167550ef95e0a2691"}, + {file = "fiona-1.9.5-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:fce4b1dd98810cabccdaa1828430c7402d283295c2ae31bea4f34188ea9e88d7"}, + {file = "fiona-1.9.5-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:3ea04ec2d8c57b5f81a31200fb352cb3242aa106fc3e328963f30ffbdf0ff7c8"}, + {file = "fiona-1.9.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4877cc745d9e82b12b3eafce3719db75759c27bd8a695521202135b36b58c2e7"}, + {file = "fiona-1.9.5-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:ac2c250f509ec19fad7959d75b531984776517ef3c1222d1cc5b4f962825880b"}, + {file = "fiona-1.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4df21906235928faad856c288cfea0298e9647f09c9a69a230535cbc8eadfa21"}, + {file = "fiona-1.9.5-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:81d502369493687746cb8d3cd77e5ada4447fb71d513721c9a1826e4fb32b23a"}, + {file = "fiona-1.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:ce3b29230ef70947ead4e701f3f82be81082b7f37fd4899009b1445cc8fc276a"}, + {file = "fiona-1.9.5-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:8b53ce8de773fcd5e2e102e833c8c58479edd8796a522f3d83ef9e08b62bfeea"}, + {file = "fiona-1.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd2355e859a1cd24a3e485c6dc5003129f27a2051629def70036535ffa7e16a4"}, + {file = "fiona-1.9.5-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:9a2da52f865db1aff0eaf41cdd4c87a7c079b3996514e8e7a1ca38457309e825"}, + {file = "fiona-1.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:cfef6db5b779d463298b1113b50daa6c5b55f26f834dc9e37752116fa17277c1"}, + {file = "fiona-1.9.5.tar.gz", hash = "sha256:99e2604332caa7692855c2ae6ed91e1fffdf9b59449aa8032dd18e070e59a2f7"}, ] [package.dependencies] @@ -1165,6 +1169,7 @@ certifi = "*" click = ">=8.0,<9.0" click-plugins = ">=1.0" cligj = ">=0.5" +setuptools = "*" six = "*" [package.extras] @@ -2486,38 +2491,38 @@ files = [ [[package]] name = "mypy" -version = "1.5.1" +version = "1.6.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, - {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, - {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, - {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, - {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, - {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, - {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, - {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, - {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, - {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, - {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, - {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, - {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, - {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, - {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, - {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, - {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, - {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, - {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, - {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, - {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, - {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, - {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, - {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, - {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, - {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, - {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, + {file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"}, + {file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"}, + {file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"}, + {file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"}, + {file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"}, + {file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"}, + {file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"}, + {file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"}, + {file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"}, + {file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"}, + {file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"}, + {file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"}, + {file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"}, + {file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"}, + {file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"}, + {file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"}, + {file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"}, + {file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"}, + {file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"}, + {file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"}, + {file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"}, + {file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"}, + {file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"}, + {file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"}, + {file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"}, + {file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"}, + {file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"}, ] [package.dependencies] @@ -2746,35 +2751,35 @@ lint = ["lintrunner (>=0.10.0)", "lintrunner-adapters (>=0.3)"] [[package]] name = "onnxruntime" -version = "1.16.0" +version = "1.16.1" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime-1.16.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:69c86ba3d90c166944c4a3c8a5b2a24a7bc45e68ae5997d83279af21ffd0f5f3"}, - {file = "onnxruntime-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:604a46aa2ad6a51f2fc4df1a984ea571a43aa02424aea93464c32ce02d23b3bb"}, - {file = "onnxruntime-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a40660516b382031279fb690fc3d068ad004173c2bd12bbdc0bd0fe01ef8b7c3"}, - {file = "onnxruntime-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:349fd9c7875c1a76609d45b079484f8059adfb1fb87a30506934fb667ceab249"}, - {file = "onnxruntime-1.16.0-cp310-cp310-win32.whl", hash = "sha256:22c9e2f1a1f15b41b01195cd2520c013c22228efc4795ae4118048ea4118aad2"}, - {file = "onnxruntime-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:b9667a131abfd226a728cc1c1ecf5cc5afa4fff37422f95a84bc22f7c175b57f"}, - {file = "onnxruntime-1.16.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:f7b292726a1f3fa4a483d7e902da083a5889a86a860dbc3a6479988cad342578"}, - {file = "onnxruntime-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61eaf288a2482c5561f620fb686c80c32709e92724bbb59a5e4a0d349429e205"}, - {file = "onnxruntime-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fe2239d5821d5501eecccfe5c408485591b5d73eb76a61491a8f78179c2e65a"}, - {file = "onnxruntime-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a4924604fcdf1704b7f7e087b4c0b0e181c58367a687da55b1aec2705631943"}, - {file = "onnxruntime-1.16.0-cp311-cp311-win32.whl", hash = "sha256:55d8456f1ab28c32aec9c478b7638ed145102b03bb9b719b79e065ffc5de9c72"}, - {file = "onnxruntime-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:c2a53ffd456187028c841ac7ed0d83b4c2b7e48bd2b1cf2a42d253ecf1e97cb3"}, - {file = "onnxruntime-1.16.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:bf5769aa4095cfe2503307867fa95b5f73732909ee21b67fe24da443af445925"}, - {file = "onnxruntime-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0974deadf11ddab201d915a10517be00fa9d6816def56fa374e4c1a0008985a"}, - {file = "onnxruntime-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99dccf1d2eba5ecd7b6c0e8e80d92d0030291f3506726c156e018a4d7a187c6f"}, - {file = "onnxruntime-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0170ed05d3a8a7c24fe01fc262a6bc603837751f3bb273df7006a2da73f37fff"}, - {file = "onnxruntime-1.16.0-cp38-cp38-win32.whl", hash = "sha256:5ecd38e98ccdcbbaa7e529e96852f4c1c136559802354b76378d9a19532018ee"}, - {file = "onnxruntime-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:1c585c60e9541a9bd4fb319ba9a3ef6122a28dcf4f3dbcdf014df44570cad6f8"}, - {file = "onnxruntime-1.16.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:efe59c1e51ad647fb18860233f5971e309961d09ca10697170ef9b7d9fa728f4"}, - {file = "onnxruntime-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c9a9cccab8f6512a0c0207b2816dd8864f2f720f6e9df5cf01e30c4f80194f"}, - {file = "onnxruntime-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcf16a252308ec6e0737db7028b63fed0ac28fbad134f86216c0dfb051a31f38"}, - {file = "onnxruntime-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f533aa90ee7189e88b6b612d6adae7d290971090598cfd47ce034ab0d106fc9c"}, - {file = "onnxruntime-1.16.0-cp39-cp39-win32.whl", hash = "sha256:306c7f5d8a0c24c65afb34f7deb0bc526defde2249e53538f1dce083945a2d6e"}, - {file = "onnxruntime-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:df8a00a7b057ba497e2822175cc68731d84b89a6d50a3a2a3ec51e98e9c91125"}, + {file = "onnxruntime-1.16.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:28b2c7f444b4119950b69370801cd66067f403d19cbaf2a444735d7c269cce4a"}, + {file = "onnxruntime-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c24e04f33e7899f6aebb03ed51e51d346c1f906b05c5569d58ac9a12d38a2f58"}, + {file = "onnxruntime-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fa93b166f2d97063dc9f33c5118c5729a4a5dd5617296b6dbef42f9047b3e81"}, + {file = "onnxruntime-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042dd9201b3016ee18f8f8bc4609baf11ff34ca1ff489c0a46bcd30919bf883d"}, + {file = "onnxruntime-1.16.1-cp310-cp310-win32.whl", hash = "sha256:c20aa0591f305012f1b21aad607ed96917c86ae7aede4a4dd95824b3d124ceb7"}, + {file = "onnxruntime-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:5581873e578917bea76d6434ee7337e28195d03488dcf72d161d08e9398c6249"}, + {file = "onnxruntime-1.16.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:ef8c0c8abf5f309aa1caf35941380839dc5f7a2fa53da533be4a3f254993f120"}, + {file = "onnxruntime-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e680380bea35a137cbc3efd67a17486e96972901192ad3026ee79c8d8fe264f7"}, + {file = "onnxruntime-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e62cc38ce1a669013d0a596d984762dc9c67c56f60ecfeee0d5ad36da5863f6"}, + {file = "onnxruntime-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:025c7a4d57bd2e63b8a0f84ad3df53e419e3df1cc72d63184f2aae807b17c13c"}, + {file = "onnxruntime-1.16.1-cp311-cp311-win32.whl", hash = "sha256:9ad074057fa8d028df248b5668514088cb0937b6ac5954073b7fb9b2891ffc8c"}, + {file = "onnxruntime-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:d5e43a3478bffc01f817ecf826de7b25a2ca1bca8547d70888594ab80a77ad24"}, + {file = "onnxruntime-1.16.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3aef4d70b0930e29a8943eab248cd1565664458d3a62b2276bd11181f28fd0a3"}, + {file = "onnxruntime-1.16.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:55a7b843a57c8ca0c8ff169428137958146081d5d76f1a6dd444c4ffcd37c3c2"}, + {file = "onnxruntime-1.16.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c631af1941bf3b5f7d063d24c04aacce8cff0794e157c497e315e89ac5ad7b"}, + {file = "onnxruntime-1.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671f296c3d5c233f601e97a10ab5a1dd8e65ba35c7b7b0c253332aba9dff330"}, + {file = "onnxruntime-1.16.1-cp38-cp38-win32.whl", hash = "sha256:eb3802305023dd05e16848d4e22b41f8147247894309c0c27122aaa08793b3d2"}, + {file = "onnxruntime-1.16.1-cp38-cp38-win_amd64.whl", hash = "sha256:fecfb07443d09d271b1487f401fbdf1ba0c829af6fd4fe8f6af25f71190e7eb9"}, + {file = "onnxruntime-1.16.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:de3e12094234db6545c67adbf801874b4eb91e9f299bda34c62967ef0050960f"}, + {file = "onnxruntime-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff723c2a5621b5e7103f3be84d5aae1e03a20621e72219dddceae81f65f240af"}, + {file = "onnxruntime-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14a7fb3073aaf6b462e3d7fb433320f7700558a8892e5021780522dc4574292a"}, + {file = "onnxruntime-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:963159f1f699b0454cd72fcef3276c8a1aab9389a7b301bcd8e320fb9d9e8597"}, + {file = "onnxruntime-1.16.1-cp39-cp39-win32.whl", hash = "sha256:85771adb75190db9364b25ddec353ebf07635b83eb94b64ed014f1f6d57a3857"}, + {file = "onnxruntime-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:d32d2b30799c1f950123c60ae8390818381fd5f88bdf3627eeca10071c155dc5"}, ] [package.dependencies] @@ -2787,19 +2792,19 @@ sympy = "*" [[package]] name = "onnxruntime-gpu" -version = "1.16.0" +version = "1.16.1" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime_gpu-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd816069373c94a9c2d9554932328f17da9b76b3de8ce13c24d96ddd91f02db"}, - {file = "onnxruntime_gpu-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:0913940593442fb92aac385ddca3b712c452a4e967d7f3aa61bfa9c2ec7502af"}, - {file = "onnxruntime_gpu-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea44d83424f5b03a4d0cc76b573047b8f20cdcdfe28b978746c9d76a352e75e2"}, - {file = "onnxruntime_gpu-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:ce97adba93c265eda50873f7fabe654429efbee1e765232afd3aca129d3d92e6"}, - {file = "onnxruntime_gpu-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93978ea51e97356d18c80863bc617e08d9ebea991955106e23a08406c2005331"}, - {file = "onnxruntime_gpu-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:2bd7762d1eed28eb965dff91429ff9d91906bc0e34b9af22c5d90e6b24c3ecc9"}, - {file = "onnxruntime_gpu-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8987821c5d0d7c4052d4322a846ba6e6a8665863e2217a090b7ad5a24c89f08d"}, - {file = "onnxruntime_gpu-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:a17da3412fcda22b7ef26be4c684b6f9fe3bb79d7437fa3b386ef2af4c5f1ec3"}, + {file = "onnxruntime_gpu-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4f4b964c15832bf414bbdec272de070c6be3b1fe97f2b7fdcb18a9a1143ac0"}, + {file = "onnxruntime_gpu-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:55e3713e57203d2785f9ec3b23883a3ed13fcf795f26aa615db0e6a4a9cf0f91"}, + {file = "onnxruntime_gpu-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4de0d8ffe43339f8b0a8b528629cb7b5615786673cde526a092bfebe821fd7b"}, + {file = "onnxruntime_gpu-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:532bad0e89ec072881c5236da0a458ab2a56d333075d1a656dce206a3100775d"}, + {file = "onnxruntime_gpu-1.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a255af5f5b3e630fe0061f55da707923aa80821e24841043f69d3faf49060bf"}, + {file = "onnxruntime_gpu-1.16.1-cp38-cp38-win_amd64.whl", hash = "sha256:6158b08ac0b9abf59cf72e36d31fc0b2ed2067f627459bb878338017699841d7"}, + {file = "onnxruntime_gpu-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17c35c845831e3f4929e4e6a7c91ead0f1ceeb5dcf1bdf2680d359d03ee718d2"}, + {file = "onnxruntime_gpu-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8eb2221dff94db1815278c84cc5e84f104bbe49a2269749bc68baaa73f3583f"}, ] [package.dependencies] @@ -2827,14 +2832,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, -] +numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "opencv-python-headless" @@ -2853,14 +2851,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, -] +numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "packaging" @@ -3090,65 +3081,65 @@ dev = ["jinja2"] [[package]] name = "pillow" -version = "10.0.1" +version = "10.1.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, - {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, - {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, - {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, - {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, - {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, - {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, - {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, - {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, - {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, - {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, - {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, - {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, - {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, - {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, - {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, - {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, - {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, - {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, - {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, - {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, - {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, - {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, - {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, - {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, - {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, + {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, + {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, + {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, + {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, + {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, + {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, + {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, + {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, + {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, + {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, + {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, + {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, + {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, + {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, + {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, + {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, + {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, + {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, + {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, + {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, + {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, + {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, + {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, + {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, + {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, + {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, ] [package.extras] @@ -3231,13 +3222,13 @@ files = [ [[package]] name = "pre-commit" -version = "3.4.0" +version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"}, - {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"}, + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, ] [package.dependencies] @@ -3281,25 +3272,27 @@ files = [ [[package]] name = "psutil" -version = "5.9.5" +version = "5.9.6" description = "Cross-platform lib for process and system monitoring in Python." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, - {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, - {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, - {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, - {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, - {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, - {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, - {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, - {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, - {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, - {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, - {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, - {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, - {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, + {file = "psutil-5.9.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fb8a697f11b0f5994550555fcfe3e69799e5b060c8ecf9e2f75c69302cc35c0d"}, + {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:91ecd2d9c00db9817a4b4192107cf6954addb5d9d67a969a4f436dbc9200f88c"}, + {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:10e8c17b4f898d64b121149afb136c53ea8b68c7531155147867b7b1ac9e7e28"}, + {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:18cd22c5db486f33998f37e2bb054cc62fd06646995285e02a51b1e08da97017"}, + {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ca2780f5e038379e520281e4c032dddd086906ddff9ef0d1b9dcf00710e5071c"}, + {file = "psutil-5.9.6-cp27-none-win32.whl", hash = "sha256:70cb3beb98bc3fd5ac9ac617a327af7e7f826373ee64c80efd4eb2856e5051e9"}, + {file = "psutil-5.9.6-cp27-none-win_amd64.whl", hash = "sha256:51dc3d54607c73148f63732c727856f5febec1c7c336f8f41fcbd6315cce76ac"}, + {file = "psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a"}, + {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c"}, + {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4"}, + {file = "psutil-5.9.6-cp36-cp36m-win32.whl", hash = "sha256:3ebf2158c16cc69db777e3c7decb3c0f43a7af94a60d72e87b2823aebac3d602"}, + {file = "psutil-5.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:ff18b8d1a784b810df0b0fff3bcb50ab941c3b8e2c8de5726f9c71c601c611aa"}, + {file = "psutil-5.9.6-cp37-abi3-win32.whl", hash = "sha256:a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c"}, + {file = "psutil-5.9.6-cp37-abi3-win_amd64.whl", hash = "sha256:6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a"}, + {file = "psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57"}, + {file = "psutil-5.9.6.tar.gz", hash = "sha256:e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a"}, ] [package.extras] @@ -3443,13 +3436,13 @@ files = [ [[package]] name = "pyee" -version = "11.0.0" +version = "11.0.1" description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own" optional = false python-versions = ">=3.8" files = [ - {file = "pyee-11.0.0-py3-none-any.whl", hash = "sha256:78bb582de8083e50cbcc9aef3e40b43d5bfa321ef85bda07a1a7b8a0c78cfebe"}, - {file = "pyee-11.0.0.tar.gz", hash = "sha256:27c682bce60bdadc5d3e23eacd4101df328c0280884a3d9c07f3a4e3e595de27"}, + {file = "pyee-11.0.1-py3-none-any.whl", hash = "sha256:9bcc9647822234f42c228d88de63d0f9ffa881e87a87f9d36ddf5211f6ac977d"}, + {file = "pyee-11.0.1.tar.gz", hash = "sha256:a642c51e3885a33ead087286e35212783a4e9b8d6514a10a5db4e57ac57b2b29"}, ] [package.dependencies] @@ -3622,46 +3615,53 @@ cffi = ">=1.0.0" [[package]] name = "pyopencl" -version = "2023.1.3" +version = "2023.1.4" description = "Python wrapper for OpenCL" optional = false python-versions = "~=3.8" files = [ - {file = "pyopencl-2023.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4ed6633020de23515aac7b06124316db08b9eb659be2bdb6e6eb749b4035c86a"}, - {file = "pyopencl-2023.1.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:750c2b5d21aa62dea9b3231e84e54ec532c33f1f575dc8c0905edd78fbc9879a"}, - {file = "pyopencl-2023.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5acf05316bc4bd303243a83ce75fbf673e0f365de92d2c05b59a36129f63af63"}, - {file = "pyopencl-2023.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5e4ada8769c823b94c649cfef5a810c5f57225a3a575f3b11453027e18acaee0"}, - {file = "pyopencl-2023.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f275ec463bdbbda05131fb25d991421774d8a3a81713c0702c1c1921bf79c5f"}, - {file = "pyopencl-2023.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:c66d30d12bb4fd570fbb15ae4c48ae8f5c6adce9eb1778aeb9331202e5a2ebfb"}, - {file = "pyopencl-2023.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b52a901941ffd8edfe3193890a421d00060f1dfa6326f404acdbc75869d7d0ba"}, - {file = "pyopencl-2023.1.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c59270e431f90491a5c96279bfa15a5bf400271655fa3b46c32a5730828c9d5c"}, - {file = "pyopencl-2023.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c75dcce069343ca3d6c406866c3173212d115707ced3f4c12e41102959d809d1"}, - {file = "pyopencl-2023.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:99c07cab0f90905a9e1b12c878401421943999f0cd6cd51bc920a9fa32bffb3c"}, - {file = "pyopencl-2023.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ca11dd27d6994b915b107e988611a9a237e6a043666f973bb8eff0b55c1b7be"}, - {file = "pyopencl-2023.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:80f380922ca2bce747bec0887fd972e223a65d34c86097c7826ea9e459e86e69"}, - {file = "pyopencl-2023.1.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1936581d4e9c5dbd5afc6fc3b81efecea4fcc17b45b96bebd4927bc4bd66888"}, - {file = "pyopencl-2023.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45118c6732bac0c3a3d41977ad510a2dc81b797f5fe96b723adfed884f91dce6"}, - {file = "pyopencl-2023.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e4c6028cd7061a34111fb99c71c3fd1ad64fc183c2cec62948fd5a90ffce8c9d"}, - {file = "pyopencl-2023.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0301158f09957988c0f8b9ac81c3e9574090690cab724469bfb68bdeae7d4c53"}, - {file = "pyopencl-2023.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:00f774792a7e00211c44ba3066a2704d7177b408be3043879535fbcf278b7cc6"}, - {file = "pyopencl-2023.1.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:034233cb7fb5340e1e6f37a2cacee7daac08a1639f811efda185a42c25649e86"}, - {file = "pyopencl-2023.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e6fdd8e1e63f683e486d9db24a7c4442417cb99712fd4519eeb2763351db5af"}, - {file = "pyopencl-2023.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bfa34302676174f1ccc4868f2725c09e6e0c75c647c52e4ba47ebdeada9f980b"}, - {file = "pyopencl-2023.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7997a206a52a7072dfaf90cfa74fb77c6a7079c625f72afd099dba1be7af6abd"}, - {file = "pyopencl-2023.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:77d07800e87cb5719a4935b3ed0dd5cd2393a4a7b5725d0af3273f1664e72343"}, - {file = "pyopencl-2023.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec44e1bf36ba386ab272b7a3f9390b7777278127d0e1391d484a1145e74ae69d"}, - {file = "pyopencl-2023.1.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa873037e081bf430ddce06e612cd5599247bb50742c2bb401cb92664591cfd"}, - {file = "pyopencl-2023.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26078a2863ae116ef5c1bd7c6c7e9bf2b4be2dc14181fc0b328a427f6c04fde4"}, - {file = "pyopencl-2023.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e01f09c443e2539769894dafe090b6d226d011cd5fb73b61a651027cf13c6ac0"}, - {file = "pyopencl-2023.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:405002eeab47de726222ccdb68ea5034b21c52ce1139d58ef2b0582e7bb74752"}, - {file = "pyopencl-2023.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:13db9b3082731341adc8471064add8265d1755cf8e80ec8935241bfcc39ffb3c"}, - {file = "pyopencl-2023.1.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a3309d716ccc76b02a454315d999384278aba98c7617227d90fe025c2b209e"}, - {file = "pyopencl-2023.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ed9db4eabec28950faa5f1eddb6e7e1b9f83464dd942bea7532fcb2e1aa15cc"}, - {file = "pyopencl-2023.1.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1df111d4d23013937be3eed2ae08744045f2e68f73a8951a03fc018b110f7de0"}, - {file = "pyopencl-2023.1.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:069b37fef6e6aa95d2c1210877b86de776d6dcf893f7b2ce53bb3fb22975c3b4"}, - {file = "pyopencl-2023.1.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342b5d710a69e32f1b40be1d611ebb4d8b9a61f58514ab4c9f1bc2721f00897c"}, - {file = "pyopencl-2023.1.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d46e9c63a5b80b23528f2437006732717e80b0afcca9f2f409301871317eb630"}, - {file = "pyopencl-2023.1.3.tar.gz", hash = "sha256:b67216f5d28e7ae8573db28c0846c330e23b0e0465b6f3d8112d09f2ee294997"}, + {file = "pyopencl-2023.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce71196cee0171a923d9ef6a8c21ce26fd7342ddaee88a29aa372ae3295f5257"}, + {file = "pyopencl-2023.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7bdf472b8e36f81145ac526d51f550ba539b765199da9da16732a222c7b85bee"}, + {file = "pyopencl-2023.1.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e99bb71267ce5223814177ff8c73e98057a70d20fc6555050d77d98b01bba5"}, + {file = "pyopencl-2023.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d8cbe0d2121babf7daf0bcd6ded687c191c6d2effbac85c5421659fbc26947"}, + {file = "pyopencl-2023.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08ebc3d5776ec2a8853c11234d605754bdf0d5af8322a08d558d661eb85c6fa3"}, + {file = "pyopencl-2023.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f199eff64be353f5badac62576d14cb40cc137c9f92f5260452de097dd9fe7a0"}, + {file = "pyopencl-2023.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:38a0a3c8389f44b51c0a916dc8e532d77ea37ab5a11f10e1ba97cc8f9a634695"}, + {file = "pyopencl-2023.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a1158956a59e73a57e98acc681ff01a64574d0b3715634703437aa8b7c785c"}, + {file = "pyopencl-2023.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55f09e9d12a036830d71f60af9233ff493c83ff2f99b472baa1f779688c816b7"}, + {file = "pyopencl-2023.1.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:742fdb18bcc933f20b795c787fe513c69b83f074626bc62ff3c9cd1c8b243022"}, + {file = "pyopencl-2023.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4102eb3f5b9fe4b08c7900820a908cec3004e9a6990cc7202162ae46e07869d"}, + {file = "pyopencl-2023.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:36d79cd6445a86b4db7399488c301b49bda5fde6c1455c36b1ce58e03c690916"}, + {file = "pyopencl-2023.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a561e42ee3030b2966e7dc5cc764e705d9ff1bb5aa1124fa7d6ba4009ebfb96"}, + {file = "pyopencl-2023.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:0f7889dce94dba10738225fabec929fa977bfe8777a64f5699f6f2fe2b00742c"}, + {file = "pyopencl-2023.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ab31b35875cecd2b1c10ac47ea06e224c5881cca942fba94387317357d73c0b9"}, + {file = "pyopencl-2023.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:230c63354fe6a18043e67042769ddc5e329002fb55fb99f692e4f5c8fcf3007c"}, + {file = "pyopencl-2023.1.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef091d4a4802b267b0aa5ec46c4ebc00fd664d9178ad9866b485006eeff180b4"}, + {file = "pyopencl-2023.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c11fc7445e21dcd97bd8f5534531ed7bbd09ded853b520157623f48fad5b739"}, + {file = "pyopencl-2023.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0b6a0ebbf67b2ef7098bdf3632177b71c7430883b2a48b2b09b84a02d8cbb4b0"}, + {file = "pyopencl-2023.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1767616f795c598531cd19d89f451db7e25393add300204e7d1a7dd2a017709f"}, + {file = "pyopencl-2023.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:8bbb02f230b969109bdf00f36e463fed1de6e1c70e088f2f2f9b41fab128f20c"}, + {file = "pyopencl-2023.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0e444320491744fe52e49f87674c931219a5be254a8a129175db61378f5b6f18"}, + {file = "pyopencl-2023.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0649a9d6249612e79cffef46618cccaaa9eac7b0c3f1833a3576ea0bd985d887"}, + {file = "pyopencl-2023.1.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f790dda26812cad5288fe1831158f55c48e6ede46ae4a37db66394645b07ef8e"}, + {file = "pyopencl-2023.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:571c6430c6fb046643040d4a6d99ee677e4c6c99b09577dbb51177001e93c21c"}, + {file = "pyopencl-2023.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0ca2d4f14e1fc716b1928679c3182595a4862f577fa0f4a5d8edf37ef8db059b"}, + {file = "pyopencl-2023.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6ac9e8e84dc11b82df51916cf41f34a9ca327adbaf9f4a03a8d1f4f1e1f35382"}, + {file = "pyopencl-2023.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:802e5eb27fd311af42133bb2da83b5777a84f5c7c11e0a4b8d1d50d265b22e37"}, + {file = "pyopencl-2023.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ae4b7f34aa56a4dc5ce5b4d795dc872f9a1aac66f15ded575aeabdfd15da0bc"}, + {file = "pyopencl-2023.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3f5861de88a7d5054cfc8a0f78c42c7b7cd7c65c43a1426a72411111b024658"}, + {file = "pyopencl-2023.1.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c658eb9198235e8866afedb9b32bad4c6a4988c7dff2103e61794cd9ea261b2a"}, + {file = "pyopencl-2023.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d148b21de9f7aa542b576c09ba3c68106658c8a3429f41c0120c7cd4cb55970f"}, + {file = "pyopencl-2023.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:63be569b566ad627e7a1331db6cfda3eb82a2076872549f1c89f4e24ee12601a"}, + {file = "pyopencl-2023.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d61a6ca8c2d8f2d7bcf106abff6ac58cb79f335303b02b90b66591b25d1af4aa"}, + {file = "pyopencl-2023.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:daeff57a66c7a2be03345dd919507f2a2b2ed4ce64c3d8416fc01fa947807e59"}, + {file = "pyopencl-2023.1.4-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c059f15d71c680e35704650bc02d7026b5566687fd45ca9f4c789567d0731cfc"}, + {file = "pyopencl-2023.1.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38c1ab4ed770eb2b2f9c34bced444fc81e96dddd188848f028d36cd16fe9fcb9"}, + {file = "pyopencl-2023.1.4-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d195dad3d3a0473373bbf173671900d4519662824b5a81ced5b491cfae5c5e23"}, + {file = "pyopencl-2023.1.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4917db2d0ec5ea7dd3c0cc66dc6a5acadc39a577a6b532293dde57ba3b23fb"}, + {file = "pyopencl-2023.1.4-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c926fb5886a561fe01be907575e396096a75fd35a2a6df5fc080c19959ae8c5c"}, + {file = "pyopencl-2023.1.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35fc3161b2f609018b6e1b22f4aa79fb571a388b61e90177c1d474975af15ce8"}, + {file = "pyopencl-2023.1.4.tar.gz", hash = "sha256:220174efca900e9d5de5aef2aa1b77a6f2550501de92b035a91013aeae4d4c5e"}, ] [package.dependencies] @@ -3757,63 +3757,49 @@ certifi = "*" [[package]] name = "pyqt5" -version = "5.15.9" +version = "5.15.2" description = "Python bindings for the Qt cross platform application toolkit" optional = false -python-versions = ">=3.7" +python-versions = ">=3.5" files = [ - {file = "PyQt5-5.15.9-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:883ba5c8a348be78c8be6a3d3ba014c798e679503bce00d76c666c2dc6afe828"}, - {file = "PyQt5-5.15.9-cp37-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:dd5ce10e79fbf1df29507d2daf99270f2057cdd25e4de6fbf2052b46c652e3a5"}, - {file = "PyQt5-5.15.9-cp37-abi3-win32.whl", hash = "sha256:e45c5cc15d4fd26ab5cb0e5cdba60691a3e9086411f8e3662db07a5a4222a696"}, - {file = "PyQt5-5.15.9-cp37-abi3-win_amd64.whl", hash = "sha256:e030d795df4cbbfcf4f38b18e2e119bcc9e177ef658a5094b87bb16cac0ce4c5"}, - {file = "PyQt5-5.15.9.tar.gz", hash = "sha256:dc41e8401a90dc3e2b692b411bd5492ab559ae27a27424eed4bd3915564ec4c0"}, + {file = "PyQt5-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-abi3-macosx_10_13_intel.whl", hash = "sha256:894ca4ae767a8d6cf5903784b71f755073c78cb8c167eecf6e4ed6b3b055ac6a"}, + {file = "PyQt5-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:29889845688a54d62820585ad5b2e0200a36b304ff3d7a555e95599f110ba4ce"}, + {file = "PyQt5-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win32.whl", hash = "sha256:ea24f24b7679bf393dd2e4f53fe0ce65021be18304c1ff7a226c2fc5c356d0da"}, + {file = "PyQt5-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:faaecb76ec65e12673a968e7f5bc02495957e6996f0a3fa0d98895f9e4113746"}, + {file = "PyQt5-5.15.2.tar.gz", hash = "sha256:372b08dc9321d1201e4690182697c5e7ffb2e0770e6b4a45519025134b12e4fc"}, ] [package.dependencies] -PyQt5-Qt5 = ">=5.15.2" -PyQt5-sip = ">=12.11,<13" - -[[package]] -name = "pyqt5-qt5" -version = "5.15.2" -description = "The subset of a Qt installation needed by PyQt5." -optional = false -python-versions = "*" -files = [ - {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, -] +PyQt5-sip = ">=12.8,<13" [[package]] name = "pyqt5-sip" -version = "12.12.2" +version = "12.13.0" description = "The sip module support for PyQt5" optional = false python-versions = ">=3.7" files = [ - {file = "PyQt5_sip-12.12.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1cc49c8498c34649325d53bcd243c854391f828d9bab4f2f3afd3ee3451cab72"}, - {file = "PyQt5_sip-12.12.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c8f6e7a697d0ddf754798988fae7b2a0da04f6a59fb13ae863e5d1da4b280c4f"}, - {file = "PyQt5_sip-12.12.2-cp310-cp310-win32.whl", hash = "sha256:7e572c8104e75db2c69609d195daf44c7b965dcb1c5b48e30fc376868909be56"}, - {file = "PyQt5_sip-12.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:6a65697aa0fdb66e20d7b1ef8adfacc1caf1e61655530920172bf3a2fb1148cd"}, - {file = "PyQt5_sip-12.12.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:761e018dbbc46daccdb01f8f0dcc0d055c76834d839f0343cbec4b0ecbbde512"}, - {file = "PyQt5_sip-12.12.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9d2b127ba5155bff452944b8a96ba06d7ec2161f48a2f9cc190425bfca94ab6b"}, - {file = "PyQt5_sip-12.12.2-cp311-cp311-win32.whl", hash = "sha256:26e75bc4ffd8e6b19ae96fe93dc135eb5aea03e4570724d4b3c40dbf36f3a2e6"}, - {file = "PyQt5_sip-12.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:d9548f353f17407d00f67d08c737de9f5c067352c3bdac8571492c614c2893eb"}, - {file = "PyQt5_sip-12.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e640b7636d86271ba8969b260e1655068b44750f20801ebc80f49a1aa737bf9"}, - {file = "PyQt5_sip-12.12.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e46d957fbeecaa1437f2dd715407b1e59e0918cc29382c7ea79784c5f3cbe0d2"}, - {file = "PyQt5_sip-12.12.2-cp37-cp37m-win32.whl", hash = "sha256:cb4523097f1ccabb95b3197a58278a40fc944b33791d3406bfa397e12303b6c6"}, - {file = "PyQt5_sip-12.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:ed04bd0065d870912c1b0a4b34b8a78698c76d77f15474c3e841b0b6dd2f429f"}, - {file = "PyQt5_sip-12.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:71795c177010e52109812b03ec919020461ec42a7d9d241a45fe6d708529b5a6"}, - {file = "PyQt5_sip-12.12.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:de06b6bd8241a189f729b8c093ce5dcf5928489eb7748bda28e28324e57544b0"}, - {file = "PyQt5_sip-12.12.2-cp38-cp38-win32.whl", hash = "sha256:7050ad8f94370eb7e4caa022b7e6d8b2de615e0714b557ca2098c82c0132074a"}, - {file = "PyQt5_sip-12.12.2-cp38-cp38-win_amd64.whl", hash = "sha256:67eed70427d3291e5c52c349fb4619c57c9a8810ab8d78a142c00edcbfd20d3b"}, - {file = "PyQt5_sip-12.12.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cf74db9a1542f66793ccc00e403c8c2c36c67c0cff0fb01d23fe71cc1c56c788"}, - {file = "PyQt5_sip-12.12.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:23e983119f760dc6c1a1e6cb21fd4c268d14c4ee497de6da9ce2b9d46f9779b2"}, - {file = "PyQt5_sip-12.12.2-cp39-cp39-win32.whl", hash = "sha256:a88ce85176639723f04cf5ce59157ecf3a9faca5d5dd1fe82d5ef46a3bd1d102"}, - {file = "PyQt5_sip-12.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:7f13e71f5171f30d8b4176c081f0203a43e1704746b4cdaa837477945177b2a0"}, - {file = "PyQt5_sip-12.12.2.tar.gz", hash = "sha256:10d9bfa9f59f0fd1cad81be187479316ffc95684f573efea94512cb4257d2b17"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a7e3623b2c743753625c4650ec7696362a37fb36433b61824cf257f6d3d43cca"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6e4ac714252370ca037c7d609da92388057165edd4f94e63354f6d65c3ed9d53"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-win32.whl", hash = "sha256:d5032da3fff62da055104926ffe76fd6044c1221f8ad35bb60804bcb422fe866"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a8cdd6cb66adcbe5c941723ed1544eba05cf19b6c961851b58ccdae1c894afb"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f85fb633a522f04e48008de49dce1ff1d947011b48885b8428838973fbca412"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec60162e034c42fb99859206d62b83b74f987d58937b3a82bdc07b5c3d190dec"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-win32.whl", hash = "sha256:205cd449d08a2b024a468fb6100cd7ed03e946b4f49706f508944006f955ae1a"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:1c8371682f77852256f1f2d38c41e2e684029f43330f0635870895ab01c02f6c"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7fe3375b508c5bc657d73b9896bba8a768791f1f426c68053311b046bcebdddf"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:773731b1b5ab1a7cf5621249f2379c95e3d2905e9bd96ff3611b119586daa876"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-win32.whl", hash = "sha256:fb4a5271fa3f6bc2feb303269a837a95a6d8dd16be553aa40e530de7fb81bfdf"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a4498f3b1b15f43f5d12963accdce0fd652b0bcaae6baf8008663365827444c"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b984c2620a7a7eaf049221b09ae50a345317add2624c706c7d2e9e6632a9587"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3188a06956aef86f604fb0d14421a110fad70d2a9e943dbacbfc3303f651dade"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-win32.whl", hash = "sha256:108a15f603e1886988c4b0d9d41cb74c9f9815bf05cefc843d559e8c298a10ce"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:db228cd737f5cbfc66a3c3e50042140cb80b30b52edc5756dbbaa2346ec73137"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5338773bbaedaa4f16a73c142fb23cc18c327be6c338813af70260b756c7bc92"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:29fa9cc964517c9fc3f94f072b9a2aeef4e7a2eda1879cb835d9e06971161cdf"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-win32.whl", hash = "sha256:96414c93f3d33963887cf562d50d88b955121fbfd73f937c8eca46643e77bf61"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:bbc7cd498bf19e0862097be1ad2243e824dea56726f00c11cff1b547c2d31d01"}, + {file = "PyQt5_sip-12.13.0.tar.gz", hash = "sha256:7f321daf84b9c9dbca61b80e1ef37bdaffc0e93312edae2cd7da25b953971d91"}, ] [[package]] @@ -4211,28 +4197,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.0.292" +version = "0.1.1" description = "An extremely fast Python linter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.292-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:02f29db018c9d474270c704e6c6b13b18ed0ecac82761e4fcf0faa3728430c96"}, - {file = "ruff-0.0.292-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:69654e564342f507edfa09ee6897883ca76e331d4bbc3676d8a8403838e9fade"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c3c91859a9b845c33778f11902e7b26440d64b9d5110edd4e4fa1726c41e0a4"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4476f1243af2d8c29da5f235c13dca52177117935e1f9393f9d90f9833f69e4"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be8eb50eaf8648070b8e58ece8e69c9322d34afe367eec4210fdee9a555e4ca7"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9889bac18a0c07018aac75ef6c1e6511d8411724d67cb879103b01758e110a81"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bdfabd4334684a4418b99b3118793f2c13bb67bf1540a769d7816410402a205"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7c77c53bfcd75dbcd4d1f42d6cabf2485d2e1ee0678da850f08e1ab13081a8"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e087b24d0d849c5c81516ec740bf4fd48bf363cfb104545464e0fca749b6af9"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f160b5ec26be32362d0774964e218f3fcf0a7da299f7e220ef45ae9e3e67101a"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ac153eee6dd4444501c4bb92bff866491d4bfb01ce26dd2fff7ca472c8df9ad0"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87616771e72820800b8faea82edd858324b29bb99a920d6aa3d3949dd3f88fb0"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b76deb3bdbea2ef97db286cf953488745dd6424c122d275f05836c53f62d4016"}, - {file = "ruff-0.0.292-py3-none-win32.whl", hash = "sha256:e854b05408f7a8033a027e4b1c7f9889563dd2aca545d13d06711e5c39c3d003"}, - {file = "ruff-0.0.292-py3-none-win_amd64.whl", hash = "sha256:f27282bedfd04d4c3492e5c3398360c9d86a295be00eccc63914438b4ac8a83c"}, - {file = "ruff-0.0.292-py3-none-win_arm64.whl", hash = "sha256:7f67a69c8f12fbc8daf6ae6d36705037bde315abf8b82b6e1f4c9e74eb750f68"}, - {file = "ruff-0.0.292.tar.gz", hash = "sha256:1093449e37dd1e9b813798f6ad70932b57cf614e5c2b5c51005bf67d55db33ac"}, + {file = "ruff-0.1.1-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b7cdc893aef23ccc14c54bd79a8109a82a2c527e11d030b62201d86f6c2b81c5"}, + {file = "ruff-0.1.1-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:620d4b34302538dbd8bbbe8fdb8e8f98d72d29bd47e972e2b59ce6c1e8862257"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a909d3930afdbc2e9fd893b0034479e90e7981791879aab50ce3d9f55205bd6"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3305d1cb4eb8ff6d3e63a48d1659d20aab43b49fe987b3ca4900528342367145"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c34ae501d0ec71acf19ee5d4d889e379863dcc4b796bf8ce2934a9357dc31db7"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6aa7e63c3852cf8fe62698aef31e563e97143a4b801b57f920012d0e07049a8d"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d68367d1379a6b47e61bc9de144a47bcdb1aad7903bbf256e4c3d31f11a87ae"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc11955f6ce3398d2afe81ad7e49d0ebf0a581d8bcb27b8c300281737735e3a3"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd8eead88ea83a250499074e2a8e9d80975f0b324b1e2e679e4594da318c25"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f4780e2bb52f3863a565ec3f699319d3493b83ff95ebbb4993e59c62aaf6e75e"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8f5b24daddf35b6c207619301170cae5d2699955829cda77b6ce1e5fc69340df"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d3f9ac658ba29e07b95c80fa742b059a55aefffa8b1e078bc3c08768bdd4b11a"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3521bf910104bf781e6753282282acc145cbe3eff79a1ce6b920404cd756075a"}, + {file = "ruff-0.1.1-py3-none-win32.whl", hash = "sha256:ba3208543ab91d3e4032db2652dcb6c22a25787b85b8dc3aeff084afdc612e5c"}, + {file = "ruff-0.1.1-py3-none-win_amd64.whl", hash = "sha256:3ff3006c97d9dc396b87fb46bb65818e614ad0181f059322df82bbfe6944e264"}, + {file = "ruff-0.1.1-py3-none-win_arm64.whl", hash = "sha256:e140bd717c49164c8feb4f65c644046fe929c46f42493672853e3213d7bdbce2"}, + {file = "ruff-0.1.1.tar.gz", hash = "sha256:c90461ae4abec261609e5ea436de4a4b5f2822921cf04c16d2cc9327182dbbcc"}, ] [[package]] @@ -4514,49 +4500,52 @@ test = ["build", "pytest", "rich", "wheel"] [[package]] name = "shapely" -version = "2.0.1" +version = "2.0.2" description = "Manipulation and analysis of geometric objects" optional = false python-versions = ">=3.7" files = [ - {file = "shapely-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b06d031bc64149e340448fea25eee01360a58936c89985cf584134171e05863f"}, - {file = "shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9a6ac34c16f4d5d3c174c76c9d7614ec8fe735f8f82b6cc97a46b54f386a86bf"}, - {file = "shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:865bc3d7cc0ea63189d11a0b1120d1307ed7a64720a8bfa5be2fde5fc6d0d33f"}, - {file = "shapely-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45b4833235b90bc87ee26c6537438fa77559d994d2d3be5190dd2e54d31b2820"}, - {file = "shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce88ec79df55430e37178a191ad8df45cae90b0f6972d46d867bf6ebbb58cc4d"}, - {file = "shapely-2.0.1-cp310-cp310-win32.whl", hash = "sha256:01224899ff692a62929ef1a3f5fe389043e262698a708ab7569f43a99a48ae82"}, - {file = "shapely-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:da71de5bf552d83dcc21b78cc0020e86f8d0feea43e202110973987ffa781c21"}, - {file = "shapely-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:502e0a607f1dcc6dee0125aeee886379be5242c854500ea5fd2e7ac076b9ce6d"}, - {file = "shapely-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7d3bbeefd8a6a1a1017265d2d36f8ff2d79d0162d8c141aa0d37a87063525656"}, - {file = "shapely-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f470a130d6ddb05b810fc1776d918659407f8d025b7f56d2742a596b6dffa6c7"}, - {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4641325e065fd3e07d55677849c9ddfd0cf3ee98f96475126942e746d55b17c8"}, - {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90cfa4144ff189a3c3de62e2f3669283c98fb760cfa2e82ff70df40f11cadb39"}, - {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70a18fc7d6418e5aea76ac55dce33f98e75bd413c6eb39cfed6a1ba36469d7d4"}, - {file = "shapely-2.0.1-cp311-cp311-win32.whl", hash = "sha256:09d6c7763b1bee0d0a2b84bb32a4c25c6359ad1ac582a62d8b211e89de986154"}, - {file = "shapely-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:d8f55f355be7821dade839df785a49dc9f16d1af363134d07eb11e9207e0b189"}, - {file = "shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:83a8ec0ee0192b6e3feee9f6a499d1377e9c295af74d7f81ecba5a42a6b195b7"}, - {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a529218e72a3dbdc83676198e610485fdfa31178f4be5b519a8ae12ea688db14"}, - {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91575d97fd67391b85686573d758896ed2fc7476321c9d2e2b0c398b628b961c"}, - {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8b0d834b11be97d5ab2b4dceada20ae8e07bcccbc0f55d71df6729965f406ad"}, - {file = "shapely-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:b4f0711cc83734c6fad94fc8d4ec30f3d52c1787b17d9dca261dc841d4731c64"}, - {file = "shapely-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:05c51a29336e604c084fb43ae5dbbfa2c0ef9bd6fedeae0a0d02c7b57a56ba46"}, - {file = "shapely-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b519cf3726ddb6c67f6a951d1bb1d29691111eaa67ea19ddca4d454fbe35949c"}, - {file = "shapely-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:193a398d81c97a62fc3634a1a33798a58fd1dcf4aead254d080b273efbb7e3ff"}, - {file = "shapely-2.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e55698e0ed95a70fe9ff9a23c763acfe0bf335b02df12142f74e4543095e9a9b"}, - {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32a748703e7bf6e92dfa3d2936b2fbfe76f8ce5f756e24f49ef72d17d26ad02"}, - {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a34a23d6266ca162499e4a22b79159dc0052f4973d16f16f990baa4d29e58b6"}, - {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d173d24e85e51510e658fb108513d5bc11e3fd2820db6b1bd0522266ddd11f51"}, - {file = "shapely-2.0.1-cp38-cp38-win32.whl", hash = "sha256:3cb256ae0c01b17f7bc68ee2ffdd45aebf42af8992484ea55c29a6151abe4386"}, - {file = "shapely-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:c7eed1fb3008a8a4a56425334b7eb82651a51f9e9a9c2f72844a2fb394f38a6c"}, - {file = "shapely-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac1dfc397475d1de485e76de0c3c91cc9d79bd39012a84bb0f5e8a199fc17bef"}, - {file = "shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33403b8896e1d98aaa3a52110d828b18985d740cc9f34f198922018b1e0f8afe"}, - {file = "shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2569a4b91caeef54dd5ae9091ae6f63526d8ca0b376b5bb9fd1a3195d047d7d4"}, - {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a70a614791ff65f5e283feed747e1cc3d9e6c6ba91556e640636bbb0a1e32a71"}, - {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43755d2c46b75a7b74ac6226d2cc9fa2a76c3263c5ae70c195c6fb4e7b08e79"}, - {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad81f292fffbd568ae71828e6c387da7eb5384a79db9b4fde14dd9fdeffca9a"}, - {file = "shapely-2.0.1-cp39-cp39-win32.whl", hash = "sha256:b50c401b64883e61556a90b89948297f1714dbac29243d17ed9284a47e6dd731"}, - {file = "shapely-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:bca57b683e3d94d0919e2f31e4d70fdfbb7059650ef1b431d9f4e045690edcd5"}, - {file = "shapely-2.0.1.tar.gz", hash = "sha256:66a6b1a3e72ece97fc85536a281476f9b7794de2e646ca8a4517e2e3c1446893"}, + {file = "shapely-2.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6ca8cffbe84ddde8f52b297b53f8e0687bd31141abb2c373fd8a9f032df415d6"}, + {file = "shapely-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:baa14fc27771e180c06b499a0a7ba697c7988c7b2b6cba9a929a19a4d2762de3"}, + {file = "shapely-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36480e32c434d168cdf2f5e9862c84aaf4d714a43a8465ae3ce8ff327f0affb7"}, + {file = "shapely-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef753200cbffd4f652efb2c528c5474e5a14341a473994d90ad0606522a46a2"}, + {file = "shapely-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9a41ff4323fc9d6257759c26eb1cf3a61ebc7e611e024e6091f42977303fd3a"}, + {file = "shapely-2.0.2-cp310-cp310-win32.whl", hash = "sha256:72b5997272ae8c25f0fd5b3b967b3237e87fab7978b8d6cd5fa748770f0c5d68"}, + {file = "shapely-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:34eac2337cbd67650248761b140d2535855d21b969d76d76123317882d3a0c1a"}, + {file = "shapely-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b0c052709c8a257c93b0d4943b0b7a3035f87e2d6a8ac9407b6a992d206422f"}, + {file = "shapely-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2d217e56ae067e87b4e1731d0dc62eebe887ced729ba5c2d4590e9e3e9fdbd88"}, + {file = "shapely-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94ac128ae2ab4edd0bffcd4e566411ea7bdc738aeaf92c32a8a836abad725f9f"}, + {file = "shapely-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa3ee28f5e63a130ec5af4dc3c4cb9c21c5788bb13c15e89190d163b14f9fb89"}, + {file = "shapely-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:737dba15011e5a9b54a8302f1748b62daa207c9bc06f820cd0ad32a041f1c6f2"}, + {file = "shapely-2.0.2-cp311-cp311-win32.whl", hash = "sha256:45ac6906cff0765455a7b49c1670af6e230c419507c13e2f75db638c8fc6f3bd"}, + {file = "shapely-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:dc9342fc82e374130db86a955c3c4525bfbf315a248af8277a913f30911bed9e"}, + {file = "shapely-2.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:06f193091a7c6112fc08dfd195a1e3846a64306f890b151fa8c63b3e3624202c"}, + {file = "shapely-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eebe544df5c018134f3c23b6515877f7e4cd72851f88a8d0c18464f414d141a2"}, + {file = "shapely-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7e92e7c255f89f5cdf777690313311f422aa8ada9a3205b187113274e0135cd8"}, + {file = "shapely-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be46d5509b9251dd9087768eaf35a71360de6afac82ce87c636990a0871aa18b"}, + {file = "shapely-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5533a925d8e211d07636ffc2fdd9a7f9f13d54686d00577eeb11d16f00be9c4"}, + {file = "shapely-2.0.2-cp312-cp312-win32.whl", hash = "sha256:084b023dae8ad3d5b98acee9d3bf098fdf688eb0bb9b1401e8b075f6a627b611"}, + {file = "shapely-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:ea84d1cdbcf31e619d672b53c4532f06253894185ee7acb8ceb78f5f33cbe033"}, + {file = "shapely-2.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ed1e99702125e7baccf401830a3b94d810d5c70b329b765fe93451fe14cf565b"}, + {file = "shapely-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7d897e6bdc6bc64f7f65155dbbb30e49acaabbd0d9266b9b4041f87d6e52b3a"}, + {file = "shapely-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0521d76d1e8af01e712db71da9096b484f081e539d4f4a8c97342e7971d5e1b4"}, + {file = "shapely-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:5324be299d4c533ecfcfd43424dfd12f9428fd6f12cda38a4316da001d6ef0ea"}, + {file = "shapely-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:78128357a0cee573257a0c2c388d4b7bf13cb7dbe5b3fe5d26d45ebbe2a39e25"}, + {file = "shapely-2.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87dc2be34ac3a3a4a319b963c507ac06682978a5e6c93d71917618b14f13066e"}, + {file = "shapely-2.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:42997ac806e4583dad51c80a32d38570fd9a3d4778f5e2c98f9090aa7db0fe91"}, + {file = "shapely-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ccfd5fa10a37e67dbafc601c1ddbcbbfef70d34c3f6b0efc866ddbdb55893a6c"}, + {file = "shapely-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7c95d3379ae3abb74058938a9fcbc478c6b2e28d20dace38f8b5c587dde90aa"}, + {file = "shapely-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a21353d28209fb0d8cc083e08ca53c52666e0d8a1f9bbe23b6063967d89ed24"}, + {file = "shapely-2.0.2-cp38-cp38-win32.whl", hash = "sha256:03e63a99dfe6bd3beb8d5f41ec2086585bb969991d603f9aeac335ad396a06d4"}, + {file = "shapely-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:c6fd29fbd9cd76350bd5cc14c49de394a31770aed02d74203e23b928f3d2f1aa"}, + {file = "shapely-2.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f217d28ecb48e593beae20a0082a95bd9898d82d14b8fcb497edf6bff9a44d7"}, + {file = "shapely-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:394e5085b49334fd5b94fa89c086edfb39c3ecab7f669e8b2a4298b9d523b3a5"}, + {file = "shapely-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fd3ad17b64466a033848c26cb5b509625c87d07dcf39a1541461cacdb8f7e91c"}, + {file = "shapely-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d41a116fcad58048d7143ddb01285e1a8780df6dc1f56c3b1e1b7f12ed296651"}, + {file = "shapely-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dea9a0651333cf96ef5bb2035044e3ad6a54f87d90e50fe4c2636debf1b77abc"}, + {file = "shapely-2.0.2-cp39-cp39-win32.whl", hash = "sha256:b8eb0a92f7b8c74f9d8fdd1b40d395113f59bd8132ca1348ebcc1f5aece94b96"}, + {file = "shapely-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:794affd80ca0f2c536fc948a3afa90bd8fb61ebe37fe873483ae818e7f21def4"}, + {file = "shapely-2.0.2.tar.gz", hash = "sha256:1713cc04c171baffc5b259ba8531c58acc2a301707b7f021d88a15ed090649e7"}, ] [package.dependencies] @@ -4971,13 +4960,13 @@ files = [ [[package]] name = "types-requests" -version = "2.31.0.8" +version = "2.31.0.10" description = "Typing stubs for requests" optional = false python-versions = ">=3.7" files = [ - {file = "types-requests-2.31.0.8.tar.gz", hash = "sha256:e1b325c687b3494a2f528ab06e411d7092cc546cc9245c000bacc2fca5ae96d4"}, - {file = "types_requests-2.31.0.8-py3-none-any.whl", hash = "sha256:39894cbca3fb3d032ed8bdd02275b4273471aa5668564617cc1734b0a65ffdf8"}, + {file = "types-requests-2.31.0.10.tar.gz", hash = "sha256:dc5852a76f1eaf60eafa81a2e50aefa3d1f015c34cf0cba130930866b1b22a92"}, + {file = "types_requests-2.31.0.10-py3-none-any.whl", hash = "sha256:b32b9a86beffa876c0c3ac99a4cd3b8b51e973fb8e3bd4e0a6bb32c7efad80fc"}, ] [package.dependencies] @@ -5018,13 +5007,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.6" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, - {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] @@ -5035,13 +5024,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.24.5" +version = "20.24.6" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, - {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, + {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, + {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, ] [package.dependencies] @@ -5190,4 +5179,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "173a9329d8f42b01b7d62684fadb257e604fda7e584bfcdd4fcdf71ae6aa99f9" +content-hash = "acb0688e485872194c21e1313e20fc4a67084893b26e9b8cde1d66e3fdbb1282" diff --git a/pyproject.toml b/pyproject.toml index d61a45c9d0..b87211cc92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -162,7 +162,9 @@ types-pycurl = "*" types-PyYAML = "*" types-requests = "*" types-tabulate = "*" -pyqt5 = { version = "*", markers = "platform_machine == 'x86_64'" } # no aarch64 wheels for macOS/linux + +# this is only pinned since 5.15.11 is broken +pyqt5 = { version = "==5.15.2", markers = "platform_machine == 'x86_64'" } # no aarch64 wheels for macOS/linux [tool.poetry.group.carla] optional = true diff --git a/release/ci/build.sh b/release/ci/build.sh new file mode 100755 index 0000000000..3502f31c61 --- /dev/null +++ b/release/ci/build.sh @@ -0,0 +1,45 @@ +# UNUSED YET BUT I WANT TO KEEP IT BECAUSE I INTEND TO USE IT SOON + +#!/usr/bin/env bash + +# set -e + +# DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +# cd $DIR + +# # Take parameters as arguments +# SOURCE_DIR=$1 +# OUTPUT_DIR=$2 + +# # Check parameters +# if [ -z "$SOURCE_DIR" ] || [ -z "$OUTPUT_DIR" ]; then +# echo "Error: No source or output directory provided." +# exit 1 +# fi + +# if [ -z "$TARGETPLATFORM" ]; then +# # Detect the platform (e.g., x86_64, aarch64, etc.) +# platform=$(uname -m) + +# # Map to Docker's platform syntax +# if [ "$platform" = "x86_64" ]; then +# TARGETPLATFORM="linux/amd64" +# elif [ "$platform" = "aarch64" ]; then +# TARGETPLATFORM="linux/arm64" +# # Add here more elif statements for other architectures if needed. +# else +# echo "Unknown platform: $platform" +# exit 1 +# fi + +# export TARGETPLATFORM +# fi + +# echo "TARGETPLATFORM IS: [${TARGETPLATFORM}]" + +# # Let's bring the submodules! +# git submodule update --init --recursive + +# # Build +# docker compose -f ${SOURCE_DIR}/docker-compose.yml build +# docker compose -f ${SOURCE_DIR}/docker-compose.yml up diff --git a/release/ci/build_and_publish.sh b/release/ci/build_and_publish.sh new file mode 100755 index 0000000000..4063a54bd7 --- /dev/null +++ b/release/ci/build_and_publish.sh @@ -0,0 +1,27 @@ +# UNUSED YET BUT I WANT TO KEEP IT BECAUSE I INTEND TO USE IT SOON + +# #!/usr/bin/env bash +# DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +# cd $DIR + +# DEV_BRANCH=$1 +# GIT_ORIGIN=$2 +# if [ -z "$DEV_BRANCH" ]; then +# DEV_BRANCH="dev-c3-test" +# fi + +# if [ -z "$GIT_ORIGIN" ]; then +# GIT_ORIGIN="git@github.com:devtekve/openpilot-og.git" #This is a default that should be changed +# echo "No GIT_ORIGIN provided, we will use the default [${GIT_ORIGIN}]" +# fi + +# VERSION=$(date '+%Y.%m.%d') +# SOURCE_DIR="$(git rev-parse --show-toplevel)" +# OUTPUT_DIR=${SOURCE_DIR}/output + + +# echo "Calling to build [${DIR}/build.sh ${SOURCE_DIR} ${OUTPUT_DIR} ${VERSION}]" +# $DIR/build.sh "${SOURCE_DIR}" "${OUTPUT_DIR}" "${VERSION}" + +# echo "Calling to publish [${DIR}/publish.sh ${SOURCE_DIR} ${OUTPUT_DIR} ${DEV_BRANCH} ${VERSION} ${GIT_ORIGIN}]" +# $DIR/publish.sh "${SOURCE_DIR}" "${OUTPUT_DIR}" "${DEV_BRANCH}" "${VERSION}" "${GIT_ORIGIN}" diff --git a/release/ci/install_gitlab_runner.sh b/release/ci/install_gitlab_runner.sh new file mode 100755 index 0000000000..c8bac10c39 --- /dev/null +++ b/release/ci/install_gitlab_runner.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# We need RW for the install process +sudo mount -o remount rw / + +# Ensure filesystem is remounted as read-only on script exit +trap "sudo mount -o remount ro /" EXIT + +# Define directories +BASE_DIR="/data/gitlab" +BIN_DIR="$BASE_DIR/bin" +CONFIG_DIR="$BASE_DIR" +BUILDS_DIR="$BASE_DIR/builds" +OPENPILOT_DIR="$BASE_DIR/openpilot" +LOGS_DIR="$BASE_DIR/logs" +CACHE_DIR="$BASE_DIR/cache" +GITLAB_RUNNER_USERNAME="gitlab-runner" +GROUPS_NEEDED="comma,gpu,gpio" + +# Create necessary directories +sudo mkdir -p "$BIN_DIR" "$BUILDS_DIR" "$LOGS_DIR" "$CACHE_DIR" "$OPENPILOT_DIR" + +# Download the GitLab Runner binary +sudo curl -L --output "$BIN_DIR/gitlab-runner" "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64" + +# Give it permission to execute +sudo chmod +x "$BIN_DIR/gitlab-runner" + +# Create a GitLab Runner user +sudo useradd --comment 'GitLab Runner' --create-home --home-dir ${BASE_DIR} ${GITLAB_RUNNER_USERNAME} --shell /bin/bash -G ${GROUPS_NEEDED} || sudo usermod -aG ${GROUPS_NEEDED} gitlab-runner + +# Clean bash_logout as it break gitlab pipelines +sudo truncate -s 0 ${BASE_DIR}/.bash_logout + +# Create a configuration file +cat </dev/null && pwd)" +cd $DIR + +# Take parameters as arguments +SOURCE_DIR=$1 +OUTPUT_DIR=$2 +DEV_BRANCH=$3 +VERSION=$4 +GIT_ORIGIN=$5 + +# Check parameters +if [ -z "$SOURCE_DIR" ] || [ -z "$OUTPUT_DIR" ]; then + echo "Error: No source or output directory provided." + exit 1 +fi + +if [ -z "$DEV_BRANCH" ] || [ -z "$VERSION" ]; then + echo "Error: No dev branch or version provided." + exit 1 +fi + +if [ -z "$GIT_ORIGIN" ]; then + echo "Error: No GIT_ORIGIN provided" + exit 1 +fi + +# "Tagging" +echo "#define COMMA_VERSION \"$VERSION-dev\"" > ${OUTPUT_DIR}/common/version.h + +## set git identity +#source $DIR/identity.sh +#export GIT_SSH_COMMAND="ssh -i /data/gitkey" + +echo "[-] Setting up repo T=$SECONDS" +cd $OUTPUT_DIR +git init + +# set git username/password +#source /data/identity.sh + +git rm -rf $OUTPUT_DIR/.git || true # Doing cleanup, but it might fail if the .git doesn't exist or not allowed to delete +git remote remove origin || true # ensure cleanup +git remote add origin $GIT_ORIGIN +#git push origin -d $DEV_BRANCH || true # Ensuring we delete the remote branch if it exists as we are wiping it out +git fetch origin $DEV_BRANCH || (git checkout -b $DEV_BRANCH && git commit --allow-empty -m "sunnypilot v$VERSION release" && git push -u origin $DEV_BRANCH) + +echo "[-] committing version $VERSION T=$SECONDS" +git add -f . +git commit -a -m "sunnypilot v$VERSION release" +git branch --set-upstream-to=origin/$DEV_BRANCH + +# include source commit hash and build date in commit +GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD) +DATETIME=$(date '+%Y-%m-%dT%H:%M:%S') +SP_VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}') + +# Add built files to git +git add -f . +git commit --amend -m "sunnypilot v$VERSION +version: sunnypilot v$SP_VERSION release +date: $DATETIME +master commit: $GIT_HASH +" +git branch -m $DEV_BRANCH + +# Push! +echo "[-] pushing T=$SECONDS" +git push -f origin $DEV_BRANCH diff --git a/release/files_common b/release/files_common index b4cbd8e373..69a1a4dea0 100644 --- a/release/files_common +++ b/release/files_common @@ -140,10 +140,6 @@ selfdrive/car/tesla/*.py selfdrive/car/toyota/*.py selfdrive/car/volkswagen/*.py -system/clocksd/.gitignore -system/clocksd/SConscript -system/clocksd/clocksd.cc - selfdrive/debug/can_printer.py selfdrive/debug/check_freq.py selfdrive/debug/dump.py @@ -257,7 +253,6 @@ system/ubloxd/*.cc selfdrive/locationd/__init__.py selfdrive/locationd/SConscript selfdrive/locationd/.gitignore -selfdrive/locationd/laikad.py selfdrive/locationd/locationd.h selfdrive/locationd/locationd.cc selfdrive/locationd/paramsd.py @@ -348,8 +343,12 @@ selfdrive/ui/qt/network/*.h selfdrive/ui/qt/offroad/*.cc selfdrive/ui/qt/offroad/*.h selfdrive/ui/qt/offroad/*.qml +selfdrive/ui/qt/offroad/sunnypilot/*.cc +selfdrive/ui/qt/offroad/sunnypilot/*.h selfdrive/ui/qt/widgets/*.cc selfdrive/ui/qt/widgets/*.h +selfdrive/ui/qt/widgets/sunnypilot/*.cc +selfdrive/ui/qt/widgets/sunnypilot/*.h selfdrive/ui/qt/maps/*.cc selfdrive/ui/qt/maps/*.h selfdrive/ui/qt/screenrecorder/* @@ -390,6 +389,9 @@ selfdrive/modeld/.gitignore selfdrive/modeld/__init__.py selfdrive/modeld/SConscript selfdrive/modeld/modeld.py +selfdrive/modeld/parse_model_outputs.py +selfdrive/modeld/fill_model_msg.py +selfdrive/modeld/get_model_metadata.py selfdrive/modeld/navmodeld.py selfdrive/modeld/dmonitoringmodeld.py selfdrive/modeld/constants.py @@ -402,8 +404,6 @@ selfdrive/modeld/models/*.pyx selfdrive/modeld/models/commonmodel.cc selfdrive/modeld/models/commonmodel.h -selfdrive/modeld/models/driving.cc -selfdrive/modeld/models/driving.h selfdrive/modeld/models/supercombo.onnx selfdrive/modeld/models/dmonitoring_model_q.dlc diff --git a/selfdrive/assets/offroad/icon_display.png b/selfdrive/assets/offroad/icon_display.png new file mode 100644 index 0000000000..809f1daa09 Binary files /dev/null and b/selfdrive/assets/offroad/icon_display.png differ diff --git a/selfdrive/assets/offroad/icon_software.png b/selfdrive/assets/offroad/icon_software.png new file mode 100644 index 0000000000..c098c7999c Binary files /dev/null and b/selfdrive/assets/offroad/icon_software.png differ diff --git a/selfdrive/assets/offroad/icon_toggle.png b/selfdrive/assets/offroad/icon_toggle.png new file mode 100644 index 0000000000..5c774db6f4 Binary files /dev/null and b/selfdrive/assets/offroad/icon_toggle.png differ diff --git a/selfdrive/assets/offroad/icon_trips.png b/selfdrive/assets/offroad/icon_trips.png new file mode 100644 index 0000000000..a042cf94da Binary files /dev/null and b/selfdrive/assets/offroad/icon_trips.png differ diff --git a/selfdrive/assets/offroad/icon_vehicle.png b/selfdrive/assets/offroad/icon_vehicle.png new file mode 100644 index 0000000000..68ef2d0c0b Binary files /dev/null and b/selfdrive/assets/offroad/icon_vehicle.png differ diff --git a/selfdrive/assets/offroad/icon_visuals.png b/selfdrive/assets/offroad/icon_visuals.png new file mode 100644 index 0000000000..8d066c90ba Binary files /dev/null and b/selfdrive/assets/offroad/icon_visuals.png differ diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 4d478d2153..e14cae4ebe 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -284,6 +284,7 @@ FW_VERSIONS = { ], (Ecu.eps, 0x75A, None): [ b'21590101AA', + b'21590101AB', b'68273275AF', b'68273275AG', b'68273275AH', diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index df6900f5ff..2e03dc39f5 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -198,17 +198,22 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.5 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - elif candidate == CAR.ESCALADE_ESV: + elif candidate in (CAR.ESCALADE_ESV, CAR.ESCALADE_ESV_2019): ret.minEnableSpeed = -1. # engage speed is decided by pcm ret.mass = 2739. ret.wheelbase = 3.302 ret.steerRatio = 17.3 ret.centerToFront = ret.wheelbase * 0.5 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[10., 41.0], [10., 41.0]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.13, 0.24], [0.01, 0.02]] - ret.lateralTuning.pid.kf = 0.000045 ret.tireStiffnessFactor = 1.0 + if candidate == CAR.ESCALADE_ESV: + ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[10., 41.0], [10., 41.0]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.13, 0.24], [0.01, 0.02]] + ret.lateralTuning.pid.kf = 0.000045 + else: + ret.steerActuatorDelay = 0.2 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + elif candidate == CAR.BOLT_EUV: ret.mass = 1669. ret.wheelbase = 2.63779 diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 837d664dad..e9ec8de9c5 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -71,6 +71,7 @@ class CAR(StrEnum): BUICK_REGAL = "BUICK REGAL ESSENCE 2018" ESCALADE = "CADILLAC ESCALADE 2017" ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016" + ESCALADE_ESV_2019 = "CADILLAC ESCALADE ESV 2019" BOLT_EUV = "CHEVROLET BOLT EUV 2022" SILVERADO = "CHEVROLET SILVERADO 1500 2020" EQUINOX = "CHEVROLET EQUINOX 2019" @@ -106,6 +107,7 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"), CAR.ESCALADE: GMCarInfo("Cadillac Escalade 2017", "Driver Assist Package"), CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"), + CAR.ESCALADE_ESV_2019: GMCarInfo("Cadillac Escalade ESV 2019", "Adaptive Cruise Control (ACC) & LKAS"), CAR.BOLT_EUV: [ GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video_link="https://youtu.be/xvwzGMUA210"), GMCarInfo("Chevrolet Bolt EV 2022-23", "2LT Trim with Adaptive Cruise Control Package"), @@ -198,6 +200,10 @@ FINGERPRINTS = { { 309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8 }], + CAR.ESCALADE_ESV_2019: [ + { + 715: 8, 840: 5, 717: 5, 869: 4, 880: 6, 289: 8, 454: 8, 842: 5, 460: 5, 463: 3, 801: 8, 170: 8, 190: 6, 241: 6, 201: 8, 417: 7, 211: 2, 419: 1, 398: 8, 426: 7, 487: 8, 442: 8, 451: 8, 452: 8, 453: 6, 479: 3, 311: 8, 500: 6, 647: 6, 193: 8, 707: 8, 197: 8, 209: 7, 199: 4, 455: 7, 313: 8, 481: 7, 485: 8, 489: 8, 249: 8, 393: 7, 407: 7, 413: 8, 422: 4, 431: 8, 501: 8, 499: 3, 810: 8, 508: 8, 381: 8, 462: 4, 532: 6, 562: 8, 386: 8, 761: 7, 573: 1, 554: 3, 719: 5, 560: 8, 1279: 4, 388: 8, 288: 5, 1005: 6, 497: 8, 844: 8, 961: 8, 967: 4, 977: 8, 979: 8, 985: 5, 1001: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 510: 8, 866: 4, 304: 1, 969: 8, 384: 4, 1033: 7, 1009: 8, 1034: 7, 1296: 4, 1930: 7, 1105: 5, 1013: 5, 1225: 7, 1919: 7, 320: 3, 534: 2, 352: 5, 298: 8, 1223: 2, 1233: 8, 608: 8, 1265: 8, 609: 6, 1267: 1, 1417: 8, 610: 6, 1906: 7, 611: 6, 612: 8, 613: 8, 208: 8, 564: 5, 309: 8, 1221: 5, 1280: 4, 1249: 8, 1907: 7, 1257: 6, 1300: 8, 1920: 7, 563: 5, 1322: 6, 1323: 4, 1328: 4, 1917: 7, 328: 1, 1912: 7, 1914: 7, 804: 3, 1918: 7 + }], CAR.BOLT_EUV: [ { 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 0c91bd8db9..deb8685ae8 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -4,8 +4,8 @@ from openpilot.common.conversions import Conversions as CV from openpilot.common.params import Params from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, HyundaiFlagsSP, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, \ - EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, UNSUPPORTED_LONGITUDINAL_CAR, NON_SCC_CAR, \ - Buttons + CANFD_UNSUPPORTED_LONGITUDINAL_CAR, NON_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, \ + UNSUPPORTED_LONGITUDINAL_CAR, Buttons from openpilot.selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR from openpilot.selfdrive.car import create_button_events, get_safety_config, create_mads_event from openpilot.selfdrive.car.interfaces import CarInterfaceBase @@ -95,7 +95,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.90 ret.steerRatio = 15.6 * 1.15 ret.tireStiffnessFactor = 0.63 - elif candidate == CAR.ELANTRA: + elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30): ret.mass = 1275. ret.wheelbase = 2.7 ret.steerRatio = 15.4 # 14 is Stock | Settled Params Learner values are steerRatio: 15.401566348670535 @@ -116,7 +116,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 3.01 ret.steerRatio = 16.5 ret.minSteerSpeed = 60 * CV.KPH_TO_MS - elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.KONA_EV_2ND_GEN): + elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.KONA_EV_2ND_GEN, CAR.KONA_NON_SCC): ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425., CAR.KONA_EV_2022: 1743., CAR.KONA_EV_2ND_GEN: 1740.}.get(candidate, 1275.) ret.wheelbase = {CAR.KONA_EV_2ND_GEN: 2.66, }.get(candidate, 2.6) ret.steerRatio = {CAR.KONA_EV_2ND_GEN: 13.6, }.get(candidate, 13.42) # Spec @@ -270,7 +270,8 @@ class CarInterface(CarInterfaceBase): if candidate in CANFD_CAR: ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] - ret.experimentalLongitudinalAvailable = candidate in (HYBRID_CAR | EV_CAR) and candidate not in (CANFD_RADAR_SCC_CAR | NON_SCC_CAR) + ret.experimentalLongitudinalAvailable = (candidate in (HYBRID_CAR | EV_CAR) and candidate not in + (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR | NON_SCC_CAR)) else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] @@ -339,7 +340,7 @@ class CarInterface(CarInterfaceBase): elif candidate in EV_CAR: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_EV_GAS - if candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): + if candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.KONA_NON_SCC): ret.flags |= HyundaiFlags.ALT_LIMITS.value ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_ALT_LIMITS diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py index 5fa92624af..dbf2b38615 100755 --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -23,6 +23,7 @@ NO_DATES_PLATFORMS = { CAR.TUCSON_HYBRID_4TH_GEN, # CAN CAR.ELANTRA, + CAR.ELANTRA_GT_I30, CAR.KIA_CEED, CAR.KIA_FORTE, CAR.KIA_OPTIMA_G4, @@ -99,7 +100,7 @@ class TestHyundaiFingerprint(unittest.TestCase): def test_platform_code_ecus_available(self): # TODO: add queries for these non-CAN FD cars to get EPS no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H, - CAR.KIA_OPTIMA_H_G4_FL, CAR.SONATA_LF, CAR.TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80} + CAR.KIA_OPTIMA_H_G4_FL, CAR.SONATA_LF, CAR.TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80, CAR.ELANTRA} # Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms for car_model, ecus in FW_VERSIONS.items(): diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index b8a7b3b3a8..a3896ac813 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -37,7 +37,7 @@ class CarControllerParams: # To determine the limit for your car, find the maximum value that the stock LKAS will request. # If the max stock LKAS request is <384, add your car to this list. - elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.IONIQ, + elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL, CAR.KIA_SORENTO): self.STEER_MAX = 255 @@ -79,6 +79,7 @@ class CAR(StrEnum): AZERA_6TH_GEN = "HYUNDAI AZERA 6TH GEN" AZERA_HEV_6TH_GEN = "HYUNDAI AZERA HYBRID 6TH GEN" ELANTRA = "HYUNDAI ELANTRA 2017" + ELANTRA_GT_I30 = "HYUNDAI I30 N LINE 2019 & GT 2018 DCT" ELANTRA_2021 = "HYUNDAI ELANTRA 2021" ELANTRA_2022_NON_SCC = "HYUNDAI ELANTRA SE 2022 NON-SCC" ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021" @@ -90,6 +91,7 @@ class CAR(StrEnum): IONIQ_PHEV_2019 = "HYUNDAI IONIQ PLUG-IN HYBRID 2019" IONIQ_PHEV = "HYUNDAI IONIQ PHEV 2020" KONA = "HYUNDAI KONA 2020" + KONA_NON_SCC = "HYUNDAI KONA 2019 NON-SCC" KONA_EV = "HYUNDAI KONA ELECTRIC 2019" KONA_EV_2022 = "HYUNDAI KONA ELECTRIC 2022" KONA_EV_2ND_GEN = "HYUNDAI KONA ELECTRIC 2ND GEN" @@ -172,7 +174,11 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.AZERA_6TH_GEN: HyundaiCarInfo("Hyundai Azera 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), CAR.AZERA_HEV_6TH_GEN: HyundaiCarInfo("Hyundai Azera Hybrid 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), CAR.ELANTRA: [ - HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])), + # TODO: 2017-18 could be Hyundai G + HyundaiCarInfo("Hyundai Elantra 2017-18", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])), + HyundaiCarInfo("Hyundai Elantra 2019", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_g])), + ], + CAR.ELANTRA_GT_I30: [ HyundaiCarInfo("Hyundai Elantra GT 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])), HyundaiCarInfo("Hyundai i30 2017-19", car_parts=CarParts.common([CarHarness.hyundai_e])), ], @@ -269,7 +275,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", car_parts=CarParts.common([CarHarness.hyundai_e])), ], CAR.KIA_SORENTO_4TH_GEN: HyundaiCarInfo("Kia Sorento 2021-23", car_parts=CarParts.common([CarHarness.hyundai_k])), - CAR.KIA_SORENTO_HEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Hybrid 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), + CAR.KIA_SORENTO_HEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Hybrid 2021-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), CAR.KIA_SORENTO_PHEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", car_parts=CarParts.common([CarHarness.hyundai_n])), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", @@ -303,6 +309,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { # Non-SCC Cars CAR.ELANTRA_2022_NON_SCC: HyundaiCarInfo("Hyundai Elantra Non-SCC 2022", "No Smart Cruise Control (SCC)", car_parts=CarParts.common([CarHarness.hyundai_k])), + CAR.KONA_NON_SCC: HyundaiCarInfo("Hyundai Kona Non-SCC 2019", "No Smart Cruise Control (SCC)", car_parts=CarParts.common([CarHarness.hyundai_b])), CAR.KIA_FORTE_2019_NON_SCC: HyundaiCarInfo("Kia Forte Non-SCC 2019", "No Smart Cruise Control (SCC)", car_parts=CarParts.common([CarHarness.hyundai_g])), CAR.KIA_FORTE_2021_NON_SCC: HyundaiCarInfo("Kia Forte Non-SCC 2021", "No Smart Cruise Control (SCC)", car_parts=CarParts.common([CarHarness.hyundai_g])), CAR.KIA_SELTOS_2023_NON_SCC: HyundaiCarInfo("Kia Seltos Non-SCC 2023-24", "No Smart Cruise Control (SCC)", car_parts=CarParts.common([CarHarness.hyundai_l])), @@ -317,9 +324,6 @@ class Buttons: CANCEL = 4 # on newer models, this is a pause/resume button FINGERPRINTS = { - CAR.ELANTRA: [{ - 66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 897: 8, 832: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1345: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2001: 8, 2003: 8, 2004: 8, 2009: 8, 2012: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }], CAR.HYUNDAI_GENESIS: [{ 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1342: 6, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4 }, @@ -1391,6 +1395,23 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9300 g21', ], (Ecu.transmission, 0x7e1, None): [b'\xf1\x816U2VE051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VE051\x00\x00DOS4T16NS3\x00\x00\x00\x00', ], }, + CAR.KONA_NON_SCC: { + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x816V5RAJ00040.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8161699051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00OS MDPS C 1.00 1.05 56310J9030\x00 4OSDC105', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9200 g30', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006T6J0_C2\x00\x006T6K1051\x00\x00TOS4N20NS2\x00\x00\x00\x00', + ], + }, CAR.KIA_CEED: { (Ecu.fwdRadar, 0x7D0, None): [b'\xf1\000CD__ SCC F-CUP 1.00 1.02 99110-J7000 ', ], (Ecu.eps, 0x7D4, None): [b'\xf1\000CD MDPS C 1.00 1.06 56310-XX000 4CDEC106', ], @@ -1791,6 +1812,33 @@ FW_VERSIONS = { ], }, CAR.ELANTRA: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00ADP LKAS AT USA LHD 1.00 1.03 99211-F2000 X31', + b'\xf1\x00AD LKAS AT USA LHD 1.01 1.01 95895-F2000 251', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD(\xfcA\x9d', + b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD\x00\x00\x00\x00', + b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\x00\x00\x00\x00', + b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\xc5\x92\x9e\x8a', + b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20SS2.~\x90\x87', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8161698051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x8161657051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816165D051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816165E051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00AD ESC \x11 11 \x18\x05\x06 58910-F2840', + b'\xf1\x00AD ESC \x11 12 \x15\t\t 58920-F2810', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00AD__ SCC H-CUP 1.00 1.00 99110-F2100 ', + b'\xf1\x00AD__ SCC H-CUP 1.00 1.01 96400-F2100 ', + ], + }, + CAR.ELANTRA_GT_I30: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54', b'\xf1\x00PD LKAS AT KOR LHD 1.00 1.02 95740-G3000 A51', @@ -2145,9 +2193,11 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.12 99210-P2000 230331', b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', + b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.04 99210-P2000 200330', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00MQhe SCC FHCUP 1.00 1.07 99110-P4000 ', + b'\xf1\x00MQhe SCC FHCUP 1.00 1.04 99110-P4000 ', ], }, CAR.KIA_K8_HEV_1ST_GEN: { @@ -2169,7 +2219,7 @@ CHECKSUM = { CAN_GEARS = { # which message has the gear. hybrid and EV use ELECT_GEAR - "use_cluster_gears": {CAR.ELANTRA, CAR.KONA}, + "use_cluster_gears": {CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA}, "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, } @@ -2180,7 +2230,11 @@ CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.TUCS # The radar does SCC on these cars when HDA I, rather than the camera CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80, - CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.IONIQ_6} + CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN} + +# These CAN FD cars do not accept communication control to disable the ADAS ECU, +# responds with 0x7F2822 - 'conditions not correct' +CANFD_UNSUPPORTED_LONGITUDINAL_CAR = {CAR.IONIQ_6, CAR.KONA_EV_2ND_GEN} # The camera does SCC on these cars, rather than the radar CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } @@ -2198,14 +2252,14 @@ EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR # these cars require a special panda safety mode due to missing counters and checksums in the messages LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_LTD, CAR.KIA_OPTIMA_G4, CAR.VELOSTER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022, - CAR.KIA_OPTIMA_H} + CAR.KIA_OPTIMA_H, CAR.ELANTRA_GT_I30} # these cars have not been verified to work with longitudinal yet - radar disable, sending correct messages, etc. UNSUPPORTED_LONGITUDINAL_CAR = LEGACY_SAFETY_MODE_CAR | {CAR.KIA_NIRO_PHEV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H_G4_FL} NON_SCC_CAR = {CAR.KIA_FORTE_2021_NON_SCC, CAR.ELANTRA_2022_NON_SCC, CAR.KIA_FORTE_2019_NON_SCC, CAR.GENESIS_G70_2021_NON_SCC, - CAR.KIA_SELTOS_2023_NON_SCC} + CAR.KIA_SELTOS_2023_NON_SCC, CAR.KONA_NON_SCC} NON_SCC_NO_FCA_CAR = {CAR.KIA_FORTE_2019_NON_SCC, } NON_SCC_RADAR_FCA_CAR = {CAR.GENESIS_G70_2021_NON_SCC, } @@ -2215,6 +2269,7 @@ DBC = { CAR.AZERA_6TH_GEN: dbc_dict('hyundai_kia_generic', None), CAR.AZERA_HEV_6TH_GEN: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA: dbc_dict('hyundai_kia_generic', None), + CAR.ELANTRA_GT_I30: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_2021: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_2022_NON_SCC: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_HEV_2021: dbc_dict('hyundai_kia_generic', None), @@ -2248,6 +2303,7 @@ DBC = { CAR.KIA_STINGER: dbc_dict('hyundai_kia_generic', None), CAR.KIA_STINGER_2022: dbc_dict('hyundai_kia_generic', None), CAR.KONA: dbc_dict('hyundai_kia_generic', None), + CAR.KONA_NON_SCC: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV_2022: dbc_dict('hyundai_kia_generic', None), CAR.KONA_HEV: dbc_dict('hyundai_kia_generic', None), diff --git a/selfdrive/car/sunnypilot_carname.json b/selfdrive/car/sunnypilot_carname.json index de48bc420d..f77ba9adcc 100644 --- a/selfdrive/car/sunnypilot_carname.json +++ b/selfdrive/car/sunnypilot_carname.json @@ -12,6 +12,7 @@ "Buick LaCrosse 2017-19": "BUICK LACROSSE 2017", "Cadillac Escalade 2017": "CADILLAC ESCALADE 2017", "Cadillac Escalade ESV 2016": "CADILLAC ESCALADE ESV 2016", + "Cadillac Escalade ESV 2019": "CADILLAC ESCALADE ESV 2019", "Chevrolet Bolt EUV 2022-23": "CHEVROLET BOLT EUV 2022", "Chevrolet Bolt EV 2022-23": "CHEVROLET BOLT EUV 2022", "Chevrolet Silverado 1500 2020-21": "CHEVROLET SILVERADO 1500 2020", @@ -66,13 +67,16 @@ "Honda Pilot 2016-22": "HONDA PILOT 2017", "Honda Ridgeline 2017-23": "HONDA RIDGELINE 2017", "Hyundai Azera 2022": "HYUNDAI AZERA 6TH GEN", - "Hyundai Elantra 2017-19": "HYUNDAI ELANTRA 2017", + "Hyundai Azera Hybrid 2020": "HYUNDAI AZERA HYBRID 6TH GEN", + "Hyundai Custin 2023": "HYUNDAI CUSTIN 1ST GEN", + "Hyundai Elantra 2017-18": "HYUNDAI ELANTRA 2017", + "Hyundai Elantra 2019": "HYUNDAI ELANTRA 2017", "Hyundai Elantra 2021-23": "HYUNDAI ELANTRA 2021", - "Hyundai Elantra GT 2017-19": "HYUNDAI ELANTRA 2017", + "Hyundai Elantra GT 2017-19": "HYUNDAI I30 N LINE 2019 & GT 2018 DCT", "Hyundai Elantra Hybrid 2021-23": "HYUNDAI ELANTRA HYBRID 2021", "Hyundai Elantra Non-SCC 2022": "HYUNDAI ELANTRA SE 2022 NON-SCC", "Hyundai Genesis 2015-16": "HYUNDAI GENESIS 2015-2016", - "Hyundai i30 2017-19": "HYUNDAI ELANTRA 2017", + "Hyundai i30 2017-19": "HYUNDAI I30 N LINE 2019 & GT 2018 DCT", "Hyundai Ioniq 5 (Southeast Asia only) 2022-23": "HYUNDAI IONIQ 5 2022", "Hyundai Ioniq 5 (with HDA II) 2022-23": "HYUNDAI IONIQ 5 2022", "Hyundai Ioniq 5 (without HDA II) 2022-23": "HYUNDAI IONIQ 5 2022", @@ -88,12 +92,13 @@ "Hyundai Kona Electric 2022-23": "HYUNDAI KONA ELECTRIC 2022", "Hyundai Kona Electric (with HDA II, Korea only) 2023": "HYUNDAI KONA ELECTRIC 2ND GEN", "Hyundai Kona Hybrid 2020": "HYUNDAI KONA HYBRID 2020", + "Hyundai Kona Non-SCC 2019": "HYUNDAI KONA 2019 NON-SCC", "Hyundai Palisade 2020-22": "HYUNDAI PALISADE 2020", "Hyundai Santa Cruz 2022-23": "HYUNDAI SANTA CRUZ 1ST GEN", "Hyundai Santa Fe 2019-20": "HYUNDAI SANTA FE 2019", "Hyundai Santa Fe 2021-23": "HYUNDAI SANTA FE 2022", "Hyundai Santa Fe Hybrid 2022-23": "HYUNDAI SANTA FE HYBRID 2022", - "Hyundai Santa Fe Plug-in Hybrid 2022": "HYUNDAI SANTA FE PlUG-IN HYBRID 2022", + "Hyundai Santa Fe Plug-in Hybrid 2022-23": "HYUNDAI SANTA FE PlUG-IN HYBRID 2022", "Hyundai Sonata 2018-19": "HYUNDAI SONATA 2019", "Hyundai Sonata 2020-23": "HYUNDAI SONATA 2020", "Hyundai Sonata Hybrid 2020-23": "HYUNDAI SONATA HYBRID 2021", @@ -115,8 +120,8 @@ "Kia Forte 2023": "KIA FORTE E 2018 & GT 2021", "Kia Forte Non-SCC 2019": "KIA FORTE 2019 NON-SCC", "Kia Forte Non-SCC 2021": "KIA FORTE LXS 2021 NON-SCC", - "Kia K5 2021-22": "KIA K5 2021", - "Kia K5 Hybrid 2020": "KIA K5 HYBRID 2020", + "Kia K5 2021-24": "KIA K5 2021", + "Kia K5 Hybrid 2020-22": "KIA K5 HYBRID 2020", "Kia K8 Hybrid (with HDA II) 2023": "KIA K8 HYBRID 1ST GEN", "Kia Niro EV 2019": "KIA NIRO EV 2020", "Kia Niro EV 2020": "KIA NIRO EV 2020", @@ -129,12 +134,13 @@ "Kia Niro Plug-in Hybrid 2020": "KIA NIRO HYBRID 2019", "Kia Optima 2017": "KIA OPTIMA 4TH GEN", "Kia Optima 2019-20": "KIA OPTIMA 4TH GEN FACELIFT", + "Kia Optima Hybrid 2019": "KIA OPTIMA HYBRID 4TH GEN FACELIFT", "Kia Seltos 2021": "KIA SELTOS 2021", "Kia Seltos Non-SCC 2023-24": "KIA SELTOS 2023 NON-SCC", "Kia Sorento 2018": "KIA SORENTO GT LINE 2018", "Kia Sorento 2019": "KIA SORENTO GT LINE 2018", "Kia Sorento 2021-23": "KIA SORENTO 4TH GEN", - "Kia Sorento Hybrid 2023": "KIA SORENTO HYBRID 4TH GEN", + "Kia Sorento Hybrid 2021-23": "KIA SORENTO HYBRID 4TH GEN", "Kia Sorento Plug-in Hybrid 2022-23": "KIA SORENTO PLUG-IN HYBRID 4TH GEN", "Kia Sportage 2023": "KIA SPORTAGE 5TH GEN", "Kia Sportage Hybrid 2023": "KIA SPORTAGE HYBRID 5TH GEN", @@ -143,27 +149,27 @@ "Kia Telluride 2020-22": "HYUNDAI PALISADE 2020", "Lexus CT Hybrid 2017-18": "LEXUS CT HYBRID 2018", "Lexus ES 2017-18": "LEXUS ES 2018", - "Lexus ES 2019-22": "LEXUS ES 2019", + "Lexus ES 2019-24": "LEXUS ES 2019", "Lexus ES Hybrid 2017-18": "LEXUS ES HYBRID 2018", - "Lexus ES Hybrid 2019-23": "LEXUS ES HYBRID 2019", + "Lexus ES Hybrid 2019-23": "LEXUS ES 2019", "Lexus IS 2017-19": "LEXUS IS 2018", - "Lexus IS 2023": "LEXUS IS 2023", + "Lexus IS 2022-23": "LEXUS IS 2023", "Lexus NX 2018-19": "LEXUS NX 2018", "Lexus NX 2020-21": "LEXUS NX 2020", - "Lexus NX Hybrid 2018-19": "LEXUS NX HYBRID 2018", - "Lexus NX Hybrid 2020-21": "LEXUS NX HYBRID 2020", + "Lexus NX Hybrid 2018-19": "LEXUS NX 2018", + "Lexus NX Hybrid 2020-21": "LEXUS NX 2020", "Lexus RC 2018-20": "LEXUS RC 2020", "Lexus RX 2016": "LEXUS RX 2016", "Lexus RX 2017-19": "LEXUS RX 2016", "Lexus RX 2020-22": "LEXUS RX 2020", "Lexus RX Hybrid 2016": "LEXUS RX HYBRID 2017", "Lexus RX Hybrid 2017-19": "LEXUS RX HYBRID 2017", - "Lexus RX Hybrid 2020-22": "LEXUS RX HYBRID 2020", - "Lexus UX Hybrid 2019-23": "TOYOTA COROLLA HYBRID TSS2 2019", + "Lexus RX Hybrid 2020-22": "LEXUS RX 2020", + "Lexus UX Hybrid 2019-23": "TOYOTA COROLLA TSS2 2019", "Lincoln Aviator 2020-21": "FORD EXPLORER 6TH GEN", "MAN eTGE 2020-23": "VOLKSWAGEN CRAFTER 2ND GEN", "MAN TGE 2017-23": "VOLKSWAGEN CRAFTER 2ND GEN", - "Mazda CX-5 2022-23": "MAZDA CX-5 2022", + "Mazda CX-5 2022-24": "MAZDA CX-5 2022", "Mazda CX-9 2021-23": "MAZDA CX-9 2021", "Nissan Altima 2019-20": "NISSAN ALTIMA 2020", "Nissan Leaf 2018-23": "NISSAN LEAF 2018", @@ -197,32 +203,32 @@ "Škoda Scala 2020-23": "SKODA SCALA 1ST GEN", "Škoda Superb 2015-22": "SKODA SUPERB 3RD GEN", "Toyota Alphard 2019-20": "TOYOTA ALPHARD 2020", - "Toyota Alphard Hybrid 2021": "TOYOTA ALPHARD HYBRID 2021", + "Toyota Alphard Hybrid 2021": "TOYOTA ALPHARD 2020", "Toyota Avalon 2016": "TOYOTA AVALON 2016", "Toyota Avalon 2017-18": "TOYOTA AVALON 2016", "Toyota Avalon 2019-21": "TOYOTA AVALON 2019", "Toyota Avalon 2022": "TOYOTA AVALON 2022", - "Toyota Avalon Hybrid 2019-21": "TOYOTA AVALON HYBRID 2019", - "Toyota Avalon Hybrid 2022": "TOYOTA AVALON HYBRID 2022", + "Toyota Avalon Hybrid 2019-21": "TOYOTA AVALON 2019", + "Toyota Avalon Hybrid 2022": "TOYOTA AVALON 2022", "Toyota C-HR 2017-20": "TOYOTA C-HR 2018", "Toyota C-HR 2021": "TOYOTA C-HR 2021", - "Toyota C-HR Hybrid 2017-20": "TOYOTA C-HR HYBRID 2018", - "Toyota C-HR Hybrid 2021-22": "TOYOTA C-HR HYBRID 2022", + "Toyota C-HR Hybrid 2017-20": "TOYOTA C-HR 2018", + "Toyota C-HR Hybrid 2021-22": "TOYOTA C-HR 2021", "Toyota Camry 2018-20": "TOYOTA CAMRY 2018", "Toyota Camry 2021-23": "TOYOTA CAMRY 2021", - "Toyota Camry Hybrid 2018-20": "TOYOTA CAMRY HYBRID 2018", - "Toyota Camry Hybrid 2021-23": "TOYOTA CAMRY HYBRID 2021", + "Toyota Camry Hybrid 2018-20": "TOYOTA CAMRY 2018", + "Toyota Camry Hybrid 2021-24": "TOYOTA CAMRY 2021", "Toyota Corolla 2017-19": "TOYOTA COROLLA 2017", "Toyota Corolla 2020-22": "TOYOTA COROLLA TSS2 2019", "Toyota Corolla Cross (Non-US only) 2020-23": "TOYOTA COROLLA TSS2 2019", - "Toyota Corolla Cross Hybrid (Non-US only) 2020-22": "TOYOTA COROLLA HYBRID TSS2 2019", + "Toyota Corolla Cross Hybrid (Non-US only) 2020-22": "TOYOTA COROLLA TSS2 2019", "Toyota Corolla Hatchback 2019-22": "TOYOTA COROLLA TSS2 2019", - "Toyota Corolla Hybrid 2020-22": "TOYOTA COROLLA HYBRID TSS2 2019", - "Toyota Corolla Hybrid (Non-US only) 2020-23": "TOYOTA COROLLA HYBRID TSS2 2019", + "Toyota Corolla Hybrid 2020-22": "TOYOTA COROLLA TSS2 2019", + "Toyota Corolla Hybrid (Non-US only) 2020-23": "TOYOTA COROLLA TSS2 2019", "Toyota Highlander 2017-19": "TOYOTA HIGHLANDER 2017", "Toyota Highlander 2020-23": "TOYOTA HIGHLANDER 2020", "Toyota Highlander Hybrid 2017-19": "TOYOTA HIGHLANDER HYBRID 2018", - "Toyota Highlander Hybrid 2020-23": "TOYOTA HIGHLANDER HYBRID 2020", + "Toyota Highlander Hybrid 2020-23": "TOYOTA HIGHLANDER 2020", "Toyota Mirai 2021": "TOYOTA MIRAI 2021", "Toyota Prius 2016": "TOYOTA PRIUS 2017", "Toyota Prius 2017-20": "TOYOTA PRIUS 2017", @@ -236,15 +242,15 @@ "Toyota RAV4 2022": "TOYOTA RAV4 2022", "Toyota RAV4 Hybrid 2016": "TOYOTA RAV4 HYBRID 2017", "Toyota RAV4 Hybrid 2017-18": "TOYOTA RAV4 HYBRID 2017", - "Toyota RAV4 Hybrid 2019-21": "TOYOTA RAV4 HYBRID 2019", - "Toyota RAV4 Hybrid 2022": "TOYOTA RAV4 HYBRID 2022", + "Toyota RAV4 Hybrid 2019-21": "TOYOTA RAV4 2019", + "Toyota RAV4 Hybrid 2022": "TOYOTA RAV4 2022", "Toyota Sienna 2018-20": "TOYOTA SIENNA 2018", "Volkswagen Arteon 2018-23": "VOLKSWAGEN ARTEON 1ST GEN", "Volkswagen Arteon eHybrid 2020-23": "VOLKSWAGEN ARTEON 1ST GEN", "Volkswagen Arteon R 2020-23": "VOLKSWAGEN ARTEON 1ST GEN", "Volkswagen Atlas 2018-23": "VOLKSWAGEN ATLAS 1ST GEN", "Volkswagen Atlas Cross Sport 2021-22": "VOLKSWAGEN ATLAS 1ST GEN", - "Volkswagen California 2021": "VOLKSWAGEN TRANSPORTER T6.1", + "Volkswagen California 2021-23": "VOLKSWAGEN TRANSPORTER T6.1", "Volkswagen Caravelle 2020": "VOLKSWAGEN TRANSPORTER T6.1", "Volkswagen CC 2018-22": "VOLKSWAGEN ARTEON 1ST GEN", "Volkswagen Crafter 2017-23": "VOLKSWAGEN CRAFTER 2ND GEN", diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 298e2bfec4..da5d760dfe 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -25,7 +25,6 @@ non_tested_cars = [ HONDA.ODYSSEY_CHN, VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter TOYOTA.RAV4_TSS2_2023, - TOYOTA.RAV4H_TSS2_2023, SUBARU.FORESTER_HYBRID, ] @@ -61,6 +60,7 @@ routes = [ CarTestRoute("75a6bcb9b8b40373|2023-03-11--22-47-33", GM.BUICK_LACROSSE), CarTestRoute("ef8f2185104d862e|2023-02-09--18-37-13", GM.ESCALADE), CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV), + CarTestRoute("168f8b3be57f66ae|2023-09-12--21-44-42", GM.ESCALADE_ESV_2019), CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT), CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV, segment=1), CarTestRoute("555d4087cf86aa91|2022-12-02--12-15-07", GM.BOLT_EUV, segment=14), # Bolt EV @@ -159,7 +159,7 @@ routes = [ CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE), CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN), CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA), - CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA), # 2019 Elantra GT + CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA_GT_I30), CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021), CarTestRoute("715ac05b594e9c59|2021-06-20--16-21-07", HYUNDAI.ELANTRA_HEV_2021), CarTestRoute("7120aa90bbc3add7|2021-08-02--07-12-31", HYUNDAI.SONATA_HYBRID), @@ -167,17 +167,17 @@ routes = [ CarTestRoute("6b0d44d22df18134|2023-05-06--10-36-55", HYUNDAI.GENESIS_GV80), CarTestRoute("00c829b1b7613dea|2021-06-24--09-10-10", TOYOTA.ALPHARD_TSS2), - CarTestRoute("912119ebd02c7a42|2022-03-19--07-24-50", TOYOTA.ALPHARDH_TSS2), + CarTestRoute("912119ebd02c7a42|2022-03-19--07-24-50", TOYOTA.ALPHARD_TSS2), # hybrid CarTestRoute("000cf3730200c71c|2021-05-24--10-42-05", TOYOTA.AVALON), CarTestRoute("0bb588106852abb7|2021-05-26--12-22-01", TOYOTA.AVALON_2019), - CarTestRoute("87bef2930af86592|2021-05-30--09-40-54", TOYOTA.AVALONH_2019), + CarTestRoute("87bef2930af86592|2021-05-30--09-40-54", TOYOTA.AVALON_2019), # hybrid CarTestRoute("e9966711cfb04ce3|2022-01-11--07-59-43", TOYOTA.AVALON_TSS2), - CarTestRoute("eca1080a91720a54|2022-03-17--13-32-29", TOYOTA.AVALONH_TSS2), + CarTestRoute("eca1080a91720a54|2022-03-17--13-32-29", TOYOTA.AVALON_TSS2), # hybrid CarTestRoute("6cdecc4728d4af37|2020-02-23--15-44-18", TOYOTA.CAMRY), CarTestRoute("2f37c007683e85ba|2023-09-02--14-39-44", TOYOTA.CAMRY), # openpilot longitudinal, with radar CAN filter + CarTestRoute("54034823d30962f5|2021-05-24--06-37-34", TOYOTA.CAMRY), # hybrid CarTestRoute("3456ad0cd7281b24|2020-12-13--17-45-56", TOYOTA.CAMRY_TSS2), - CarTestRoute("ffccc77938ddbc44|2021-01-04--16-55-41", TOYOTA.CAMRYH_TSS2), - CarTestRoute("54034823d30962f5|2021-05-24--06-37-34", TOYOTA.CAMRYH), + CarTestRoute("ffccc77938ddbc44|2021-01-04--16-55-41", TOYOTA.CAMRY_TSS2), # hybrid CarTestRoute("4e45c89c38e8ec4d|2021-05-02--02-49-28", TOYOTA.COROLLA), CarTestRoute("5f5afb36036506e4|2019-05-14--02-09-54", TOYOTA.COROLLA_TSS2), CarTestRoute("5ceff72287a5c86c|2019-10-19--10-59-02", TOYOTA.COROLLA_TSS2), # hybrid @@ -186,7 +186,7 @@ routes = [ CarTestRoute("cdf2f7de565d40ae|2019-04-25--03-53-41", TOYOTA.RAV4_TSS2), CarTestRoute("a5c341bb250ca2f0|2022-05-18--16-05-17", TOYOTA.RAV4_TSS2_2022), CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.RAV4_TSS2), # hybrid - CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4H_TSS2_2022), + CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4_TSS2_2022), # hybrid CarTestRoute("7a31f030957b9c85|2023-04-01--14-12-51", TOYOTA.LEXUS_ES), CarTestRoute("e6a24be49a6cd46e|2019-10-29--10-52-42", TOYOTA.LEXUS_ES_TSS2), CarTestRoute("f49e8041283f2939|2019-05-30--11-51-51", TOYOTA.LEXUS_ES_TSS2), # hybrid @@ -202,7 +202,7 @@ routes = [ CarTestRoute("3fd5305f8b6ca765|2021-04-28--19-26-49", TOYOTA.LEXUS_NX_TSS2), CarTestRoute("09ae96064ed85a14|2022-06-09--12-22-31", TOYOTA.LEXUS_NX_TSS2), # hybrid CarTestRoute("0a302ffddbb3e3d3|2020-02-08--16-19-08", TOYOTA.HIGHLANDER_TSS2), - CarTestRoute("437e4d2402abf524|2021-05-25--07-58-50", TOYOTA.HIGHLANDERH_TSS2), + CarTestRoute("437e4d2402abf524|2021-05-25--07-58-50", TOYOTA.HIGHLANDER_TSS2), # hybrid CarTestRoute("3183cd9b021e89ce|2021-05-25--10-34-44", TOYOTA.HIGHLANDER), CarTestRoute("80d16a262e33d57f|2021-05-23--20-01-43", TOYOTA.HIGHLANDERH), CarTestRoute("eb6acd681135480d|2019-06-20--20-00-00", TOYOTA.SIENNA), @@ -214,8 +214,8 @@ routes = [ CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHR), # hybrid CarTestRoute("ea8fbe72b96a185c|2023-02-08--15-11-46", TOYOTA.CHR_TSS2), CarTestRoute("ea8fbe72b96a185c|2023-02-22--09-20-34", TOYOTA.CHR_TSS2), # openpilot longitudinal, with smartDSU - CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.CHRH_TSS2), - # CarTestRoute("6719965b0e1d1737|2023-08-29--06-40-05", TOYOTA.CHRH_TSS2), # openpilot longitudinal, radar disabled + CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.CHR_TSS2), # hybrid + # CarTestRoute("6719965b0e1d1737|2023-08-29--06-40-05", TOYOTA.CHR_TSS2), # hybrid, openpilot longitudinal, radar disabled CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V), CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1), diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 787b5d6316..5fa1a02897 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -133,7 +133,7 @@ class TestCarInterfaces(unittest.TestCase): # Make sure we can combine dicts ret = get_interface_attr('DBC', combine_brands=True) - self.assertGreaterEqual(len(ret), 170) + self.assertGreaterEqual(len(ret), 160) # We don't support combining non-dicts ret = get_interface_attr('CAR', combine_brands=True) diff --git a/selfdrive/car/tests/test_models_segs.txt b/selfdrive/car/tests/test_models_segs.txt index d838c14765..27a2104cf7 100644 --- a/selfdrive/car/tests/test_models_segs.txt +++ b/selfdrive/car/tests/test_models_segs.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e57357c95937475fe92c13709ff2ee067f036643d5d455383bd9c3335525614 -size 126281 +oid sha256:8674d9a8304d501aee2bfaea2d668d746fd424b5b0872b83ab265d15d3a3d887 +size 125301 diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index b72a05f93b..61f8dc2b3c 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -14,7 +14,6 @@ SUBARU ASCENT 2023: [.nan, 3.0, .nan] # Toyota LTA also has torque TOYOTA RAV4 2023: [.nan, 3.0, .nan] -TOYOTA RAV4 HYBRID 2023: [.nan, 3.0, .nan] # Tesla has high torque TESLA AP1 MODEL S: [.nan, 2.5, .nan] @@ -37,6 +36,7 @@ RAM 1500 5TH GEN: [2.0, 2.0, 0.05] RAM HD 5TH GEN: [1.4, 1.4, 0.05] SUBARU OUTBACK 6TH GEN: [2.0, 2.0, 0.2] CADILLAC ESCALADE 2017: [1.899999976158142, 1.842270016670227, 0.1120000034570694] +CADILLAC ESCALADE ESV 2019: [1.15, 1.3, 0.2] CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05] CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] CHEVROLET TRAILBLAZER 2021: [1.33, 1.9, 0.16] diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index df529e812f..9386bc3bef 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -62,16 +62,13 @@ TOYOTA AVALON 2019: [1.7036141952825095, 1.239619084240008, 0.08459830394899492] TOYOTA AVALON 2022: [2.3154403649717357, 2.7777922854327124, 0.11453999639164605] TOYOTA C-HR 2018: [1.5591084333664578, 1.271271459066948, 0.20259087058453193] TOYOTA C-HR 2021: [1.7678810166088303, 1.3742176337919942, 0.2319674583741509] -TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.105192506] -TOYOTA CAMRY 2021: [2.446083, 2.3476510120007434, 0.121615] -TOYOTA CAMRY HYBRID 2018: [1.996333, 1.7996193116697359, 0.112565] -TOYOTA CAMRY HYBRID 2021: [2.263582, 2.3901492458927986, 0.115257] +TOYOTA CAMRY 2018: [2.0568162685952505, 1.7576185169559122, 0.108878753] +TOYOTA CAMRY 2021: [2.3548324999999997, 2.368900128946771, 0.118436] TOYOTA COROLLA 2017: [3.117154369115421, 1.8438132575043773, 0.12289685869250652] TOYOTA COROLLA TSS2 2019: [1.991132339206426, 1.868866242720403, 0.19570063298031432] TOYOTA HIGHLANDER 2017: [1.8696367437248915, 1.626293990451463, 0.17485372210240796] -TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054457] +TOYOTA HIGHLANDER 2020: [1.9617570834136164, 1.8611643317268927, 0.14519673256119725] TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600] -TOYOTA HIGHLANDER HYBRID 2020: [1.901174, 2.104015182965606, 0.14447040132184993] TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565] TOYOTA PRIUS 2017: [1.60, 1.5023147650693636, 0.151515] TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968] @@ -80,8 +77,8 @@ TOYOTA RAV4 2019: [2.279239424615458, 2.087101966779332, 0.13682208413446817] TOYOTA RAV4 2019 8965: [2.3080951748210854, 2.1189367835820603, 0.12942102328134028] TOYOTA RAV4 2019 x02: [2.762293266024922, 2.243615865975329, 0.11113568178327986] TOYOTA RAV4 HYBRID 2017: [1.9796257271652042, 1.7503987331707576, 0.14628860048885406] -TOYOTA RAV4 HYBRID 2022: [2.241883248393209, 1.9304407208090029, 0.112174] -TOYOTA RAV4 HYBRID 2022 x02: [3.044930631831037, 2.3979189796380918, 0.14023209146703736] +TOYOTA RAV4 2022: [2.241883248393209, 1.9304407208090029, 0.112174] +TOYOTA RAV4 2022 x02: [3.044930631831037, 2.3979189796380918, 0.14023209146703736] TOYOTA SIENNA 2018: [1.689726, 1.3208264576110418, 0.140456] VOLKSWAGEN ARTEON 1ST GEN: [1.45136518053819, 1.3639364049316804, 0.23806361745695032] VOLKSWAGEN ATLAS 1ST GEN: [1.4677006726964945, 1.6733266634075656, 0.12959584092073367] diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index 215bd609f8..79fe68f999 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -4,18 +4,13 @@ MAZDA CX-5: MAZDA CX-9 2021 MAZDA CX-5 2022: MAZDA CX-9 2021 MAZDA CX-9: MAZDA CX-9 2021 -TOYOTA ALPHARD HYBRID 2021 : TOYOTA SIENNA 2018 TOYOTA ALPHARD 2020: TOYOTA SIENNA 2018 TOYOTA PRIUS v 2017 : TOYOTA PRIUS 2017 -TOYOTA RAV4 2022: TOYOTA RAV4 HYBRID 2022 -TOYOTA C-HR HYBRID 2022: TOYOTA C-HR 2021 LEXUS IS 2018: LEXUS NX 2018 LEXUS CT HYBRID 2018 : LEXUS NX 2018 -LEXUS ES 2018: TOYOTA CAMRY HYBRID 2018 -LEXUS ES HYBRID 2018: TOYOTA CAMRY HYBRID 2018 +LEXUS ES 2018: TOYOTA CAMRY 2018 +LEXUS ES HYBRID 2018: TOYOTA CAMRY 2018 LEXUS RC 2020: LEXUS NX 2020 -TOYOTA AVALON HYBRID 2019: TOYOTA AVALON 2019 -TOYOTA AVALON HYBRID 2022: TOYOTA AVALON 2022 KIA OPTIMA 4TH GEN: HYUNDAI SONATA 2020 KIA OPTIMA 4TH GEN FACELIFT: HYUNDAI SONATA 2020 @@ -30,6 +25,7 @@ KIA SELTOS 2023 NON-SCC: HYUNDAI SONATA 2020 KIA NIRO HYBRID 2019: KIA NIRO EV 2020 KIA NIRO HYBRID 2021: KIA NIRO EV 2020 HYUNDAI VELOSTER 2019: HYUNDAI SONATA 2019 +HYUNDAI KONA 2019 NON-SCC: HYUNDAI KONA ELECTRIC 2019 HYUNDAI KONA 2020: HYUNDAI KONA ELECTRIC 2019 HYUNDAI KONA HYBRID 2020: HYUNDAI KONA ELECTRIC 2019 HYUNDAI KONA ELECTRIC 2022: HYUNDAI KONA ELECTRIC 2019 @@ -37,6 +33,7 @@ HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 +HYUNDAI I30 N LINE 2019 & GT 2018 DCT: HYUNDAI SONATA 2019 HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 HYUNDAI ELANTRA SE 2022 NON-SCC: HYUNDAI ELANTRA 2021 HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 1dc7e3926a..f94847be0e 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -119,6 +119,9 @@ class CarController: if not (CC.enabled and CS.out.cruiseState.enabled) and CS.pcm_acc_status: pcm_cancel_cmd = 1 + if self.CP.pcmCruise and not CS.pcm_acc_status and CS.out.cruiseState.enabled and self.CP.carFingerprint not in UNSUPPORTED_DSU_CAR: + pcm_cancel_cmd = 1 + # on entering standstill, send standstill request if CS.out.standstill and not self.last_standstill and (self.CP.carFingerprint not in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptor) and \ not self._sng_hack: diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 19467fca6d..5edccd9f98 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -116,6 +116,7 @@ class CarState(CarStateBase): self.prev_lkas_enabled = self.lkas_enabled ret.steeringAngleDeg = cp.vl["STEER_ANGLE_SENSOR"]["STEER_ANGLE"] + cp.vl["STEER_ANGLE_SENSOR"]["STEER_FRACTION"] + ret.steeringRateDeg = cp.vl["STEER_ANGLE_SENSOR"]["STEER_RATE"] torque_sensor_angle_deg = cp.vl["STEER_TORQUE_SENSOR"]["STEER_ANGLE"] # On some cars, the angle measurement is non-zero while initializing @@ -123,8 +124,8 @@ class CarState(CarStateBase): self.accurate_steer_angle_seen = True if self.accurate_steer_angle_seen: - # Offset seems to be invalid for large steering angles - if abs(ret.steeringAngleDeg) < 90 and cp.can_valid: + # Offset seems to be invalid for large steering angles and high angle rates + if abs(ret.steeringAngleDeg) < 90 and abs(ret.steeringRateDeg) < 100 and cp.can_valid: self.angle_offset.update(torque_sensor_angle_deg - ret.steeringAngleDeg) if self.angle_offset.initialized: @@ -146,8 +147,6 @@ class CarState(CarStateBase): # Apply offset ret.steeringAngleDeg = zorro_steer - self.zss_angle_offset - ret.steeringRateDeg = cp.vl["STEER_ANGLE_SENSOR"]["STEER_RATE"] - can_gear = int(cp.vl["GEAR_PACKET"]["GEAR"]) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) ret.leftBlinker = ret.leftBlinkerOn = cp.vl["BLINKERS_STATE"]["TURN_SIGNALS"] == 1 @@ -195,7 +194,8 @@ class CarState(CarStateBase): if self.CP.flags & ToyotaFlags.SMART_DSU: self.gap_dist_button = cp.vl["SDSU"]["FD_BUTTON"] - self.follow_distance = cp.vl["PCM_CRUISE_2"]["PCM_FOLLOW_DISTANCE"] + fd_src = "PCM_CRUISE_ALT" if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR else "PCM_CRUISE_2" + self.follow_distance = cp.vl[fd_src]["PCM_FOLLOW_DISTANCE"] # some TSS2 cars have low speed lockout permanently set, so ignore on those cars # these cars are identified by an ACC_TYPE value of 2. @@ -357,7 +357,7 @@ class CarState(CarStateBase): messages.append(("SDSU", 33)) if CP.spFlags & ToyotaFlagsSP.SP_ZSS: - messages.apend(("SECONDARY_STEER_ANGLE", 0)) + messages.append(("SECONDARY_STEER_ANGLE", 0)) return CANParser(DBC[CP.carFingerprint]["pt"], messages, 0) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index c1d324f729..496996964c 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -96,28 +96,28 @@ class CarInterface(CarInterfaceBase): ret.tireStiffnessFactor = 0.5533 ret.mass = 4481. * CV.LB_TO_KG # mean between min and max - elif candidate in (CAR.CHR, CAR.CHR_TSS2, CAR.CHRH_TSS2): + elif candidate in (CAR.CHR, CAR.CHR_TSS2): stop_and_go = True ret.wheelbase = 2.63906 ret.steerRatio = 13.6 ret.tireStiffnessFactor = 0.7933 ret.mass = 3300. * CV.LB_TO_KG - elif candidate in (CAR.CAMRY, CAR.CAMRYH, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2): + elif candidate in (CAR.CAMRY, CAR.CAMRY_TSS2): stop_and_go = True ret.wheelbase = 2.82448 ret.steerRatio = 13.7 ret.tireStiffnessFactor = 0.7933 ret.mass = 3400. * CV.LB_TO_KG # mean between normal and hybrid - elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2): + elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.HIGHLANDER_TSS2): stop_and_go = True ret.wheelbase = 2.8194 # average of 109.8 and 112.2 in ret.steerRatio = 16.0 ret.tireStiffnessFactor = 0.8 ret.mass = 4516. * CV.LB_TO_KG # mean between normal and hybrid - elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALONH_2019, CAR.AVALON_TSS2, CAR.AVALONH_TSS2): + elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALON_TSS2): # starting from 2019, all Avalon variants have stop and go # https://engage.toyota.com/static/images/toyota_safety_sense/TSS_Applicability_Chart.pdf stop_and_go = candidate != CAR.AVALON @@ -126,8 +126,7 @@ class CarInterface(CarInterfaceBase): ret.tireStiffnessFactor = 0.7983 ret.mass = 3505. * CV.LB_TO_KG # mean between normal and hybrid - elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2_2022, - CAR.RAV4_TSS2_2023, CAR.RAV4H_TSS2_2023): + elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023): ret.wheelbase = 2.68986 ret.steerRatio = 14.3 ret.tireStiffnessFactor = 0.7933 @@ -202,7 +201,7 @@ class CarInterface(CarInterfaceBase): ret.tireStiffnessFactor = 0.8 ret.mass = 4300. * CV.LB_TO_KG - elif candidate in (CAR.ALPHARD_TSS2, CAR.ALPHARDH_TSS2): + elif candidate == CAR.ALPHARD_TSS2: ret.wheelbase = 3.00 ret.steerRatio = 14.2 ret.tireStiffnessFactor = 0.444 @@ -257,6 +256,9 @@ class CarInterface(CarInterfaceBase): if not ret.openpilotLongitudinalControl: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL + if candidate in UNSUPPORTED_DSU_CAR: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_UNSUPPORTED_DSU_CAR + # min speed to enable ACC. if car can do stop and go, then set enabling speed # to a negative value, so it won't matter. ret.minEnableSpeed = -1. if (stop_and_go or ret.enableGasInterceptor) else MIN_ACC_SPEED diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index 07f6298839..a4db91747c 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -30,7 +30,7 @@ def create_lta_steer_command(packer, steer_angle, steer_req, frame, setme_x64): def create_accel_command(packer, accel, pcm_cancel, standstill_req, lead, acc_type, fcw_alert, reverse_acc, gac_send): # TODO: find the exact canceling bit that does not create a chime values = { - "ACCEL_CMD": accel, + "ACCEL_CMD": 0 if pcm_cancel else accel, "ACC_TYPE": acc_type, "DISTANCE": gac_send, "MINI_CAR": lead, diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index ac252a1bb4..745faf7493 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -53,26 +53,19 @@ class ToyotaFlagsSP(IntFlag): class CAR(StrEnum): # Toyota ALPHARD_TSS2 = "TOYOTA ALPHARD 2020" - ALPHARDH_TSS2 = "TOYOTA ALPHARD HYBRID 2021" AVALON = "TOYOTA AVALON 2016" AVALON_2019 = "TOYOTA AVALON 2019" - AVALONH_2019 = "TOYOTA AVALON HYBRID 2019" AVALON_TSS2 = "TOYOTA AVALON 2022" # TSS 2.5 - AVALONH_TSS2 = "TOYOTA AVALON HYBRID 2022" CAMRY = "TOYOTA CAMRY 2018" - CAMRYH = "TOYOTA CAMRY HYBRID 2018" CAMRY_TSS2 = "TOYOTA CAMRY 2021" # TSS 2.5 - CAMRYH_TSS2 = "TOYOTA CAMRY HYBRID 2021" CHR = "TOYOTA C-HR 2018" CHR_TSS2 = "TOYOTA C-HR 2021" - CHRH_TSS2 = "TOYOTA C-HR HYBRID 2022" COROLLA = "TOYOTA COROLLA 2017" # LSS2 Lexus UX Hybrid is same as a TSS2 Corolla Hybrid COROLLA_TSS2 = "TOYOTA COROLLA TSS2 2019" HIGHLANDER = "TOYOTA HIGHLANDER 2017" HIGHLANDER_TSS2 = "TOYOTA HIGHLANDER 2020" HIGHLANDERH = "TOYOTA HIGHLANDER HYBRID 2018" - HIGHLANDERH_TSS2 = "TOYOTA HIGHLANDER HYBRID 2020" PRIUS = "TOYOTA PRIUS 2017" PRIUS_V = "TOYOTA PRIUS v 2017" PRIUS_TSS2 = "TOYOTA PRIUS TSS2 2021" @@ -81,8 +74,6 @@ class CAR(StrEnum): RAV4_TSS2 = "TOYOTA RAV4 2019" RAV4_TSS2_2022 = "TOYOTA RAV4 2022" RAV4_TSS2_2023 = "TOYOTA RAV4 2023" - RAV4H_TSS2_2022 = "TOYOTA RAV4 HYBRID 2022" - RAV4H_TSS2_2023 = "TOYOTA RAV4 HYBRID 2023" MIRAI = "TOYOTA MIRAI 2021" # TSS 2.5 SIENNA = "TOYOTA SIENNA 2018" @@ -115,26 +106,38 @@ class ToyotaCarInfo(CarInfo): CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { # Toyota - CAR.ALPHARD_TSS2: ToyotaCarInfo("Toyota Alphard 2019-20"), - CAR.ALPHARDH_TSS2: ToyotaCarInfo("Toyota Alphard Hybrid 2021"), + CAR.ALPHARD_TSS2: [ + ToyotaCarInfo("Toyota Alphard 2019-20"), + ToyotaCarInfo("Toyota Alphard Hybrid 2021"), + ], CAR.AVALON: [ ToyotaCarInfo("Toyota Avalon 2016", "Toyota Safety Sense P"), ToyotaCarInfo("Toyota Avalon 2017-18"), ], - CAR.AVALON_2019: ToyotaCarInfo("Toyota Avalon 2019-21"), - CAR.AVALONH_2019: ToyotaCarInfo("Toyota Avalon Hybrid 2019-21"), - CAR.AVALON_TSS2: ToyotaCarInfo("Toyota Avalon 2022"), - CAR.AVALONH_TSS2: ToyotaCarInfo("Toyota Avalon Hybrid 2022"), - CAR.CAMRY: ToyotaCarInfo("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]), - CAR.CAMRYH: ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"), - CAR.CAMRY_TSS2: ToyotaCarInfo("Toyota Camry 2021-23", footnotes=[Footnote.CAMRY]), - CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-24"), + CAR.AVALON_2019: [ + ToyotaCarInfo("Toyota Avalon 2019-21"), + ToyotaCarInfo("Toyota Avalon Hybrid 2019-21"), + ], + CAR.AVALON_TSS2: [ + ToyotaCarInfo("Toyota Avalon 2022"), + ToyotaCarInfo("Toyota Avalon Hybrid 2022"), + ], + CAR.CAMRY: [ + ToyotaCarInfo("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]), + ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"), + ], + CAR.CAMRY_TSS2: [ + ToyotaCarInfo("Toyota Camry 2021-23", footnotes=[Footnote.CAMRY]), + ToyotaCarInfo("Toyota Camry Hybrid 2021-24"), + ], CAR.CHR: [ ToyotaCarInfo("Toyota C-HR 2017-20"), ToyotaCarInfo("Toyota C-HR Hybrid 2017-20"), ], - CAR.CHR_TSS2: ToyotaCarInfo("Toyota C-HR 2021"), - CAR.CHRH_TSS2: ToyotaCarInfo("Toyota C-HR Hybrid 2021-22"), + CAR.CHR_TSS2: [ + ToyotaCarInfo("Toyota C-HR 2021"), + ToyotaCarInfo("Toyota C-HR Hybrid 2021-22"), + ], CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"), CAR.COROLLA_TSS2: [ ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), @@ -147,9 +150,11 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ToyotaCarInfo("Lexus UX Hybrid 2019-23"), ], CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"), - CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-23"), + CAR.HIGHLANDER_TSS2: [ + ToyotaCarInfo("Toyota Highlander 2020-23"), + ToyotaCarInfo("Toyota Highlander Hybrid 2020-23"), + ], CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"), - CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-23"), CAR.PRIUS: [ ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), @@ -172,10 +177,14 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ToyotaCarInfo("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"), ToyotaCarInfo("Toyota RAV4 Hybrid 2019-21"), ], - CAR.RAV4_TSS2_2022: ToyotaCarInfo("Toyota RAV4 2022"), - CAR.RAV4_TSS2_2023: ToyotaCarInfo("Toyota RAV4 2023"), - CAR.RAV4H_TSS2_2022: ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"), - CAR.RAV4H_TSS2_2023: ToyotaCarInfo("Toyota RAV4 Hybrid 2023"), + CAR.RAV4_TSS2_2022: [ + ToyotaCarInfo("Toyota RAV4 2022"), + ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"), + ], + CAR.RAV4_TSS2_2023: [ + ToyotaCarInfo("Toyota RAV4 2023"), + ToyotaCarInfo("Toyota RAV4 Hybrid 2023"), + ], CAR.MIRAI: ToyotaCarInfo("Toyota Mirai 2021"), CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", min_enable_speed=MIN_ACC_SPEED), @@ -350,7 +359,7 @@ FW_CHUNK_LEN = 16 PLATFORM_CODE_ECUS = [Ecu.fwdCamera, Ecu.abs, Ecu.eps] # These platforms have at least one platform code for all ECUs shared with another platform. -FUZZY_EXCLUDED_PLATFORMS = {CAR.LEXUS_ES_TSS2, CAR.LEXUS_RX_TSS2} +FUZZY_EXCLUDED_PLATFORMS: set[CAR] = set() # Some ECUs that use KWP2000 have their FW versions on non-standard data identifiers. # Toyota diagnostic software first gets the supported data ids, then queries them one by one. @@ -385,7 +394,7 @@ FW_QUERY_CONFIG = FwQueryConfig( ], non_essential_ecus={ # FIXME: On some models, abs can sometimes be missing - Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS], + Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS, CAR.ALPHARD_TSS2], # On some models, the engine can show on two different addresses Ecu.engine: [CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.CHR_TSS2, CAR.LEXUS_IS, CAR.LEXUS_RC, CAR.LEXUS_NX, CAR.LEXUS_NX_TSS2, CAR.LEXUS_RX_TSS2], @@ -457,44 +466,26 @@ FW_VERSIONS = { b'F152607171\x00\x00\x00\x00\x00\x00', b'F152607110\x00\x00\x00\x00\x00\x00', b'F152607180\x00\x00\x00\x00\x00\x00', + b'F152641040\x00\x00\x00\x00\x00\x00', + b'F152641061\x00\x00\x00\x00\x00\x00', + b'F152641050\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ b'881510703200\x00\x00\x00\x00', + b'881510704200\x00\x00\x00\x00', + b'881514107100\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B41080\x00\x00\x00\x00\x00\x00', b'8965B07010\x00\x00\x00\x00\x00\x00', b'8965B41090\x00\x00\x00\x00\x00\x00', + b'8965B41070\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x01896630725200\x00\x00\x00\x00', b'\x01896630725300\x00\x00\x00\x00', b'\x01896630735100\x00\x00\x00\x00', b'\x01896630738000\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F0702100\x00\x00\x00\x00', - ], - }, - CAR.AVALONH_2019: { - (Ecu.abs, 0x7b0, None): [ - b'F152641040\x00\x00\x00\x00\x00\x00', - b'F152641061\x00\x00\x00\x00\x00\x00', - b'F152641050\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881510704200\x00\x00\x00\x00', - b'881514107100\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B07010\x00\x00\x00\x00\x00\x00', - b'8965B41090\x00\x00\x00\x00\x00\x00', - b'8965B41070\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ b'\x02896630724000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', b'\x02896630737000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x02896630728000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', @@ -511,6 +502,7 @@ FW_VERSIONS = { b'\x01F152607240\x00\x00\x00\x00\x00\x00', b'\x01F152607250\x00\x00\x00\x00\x00\x00', b'\x01F152607280\x00\x00\x00\x00\x00\x00', + b'F152641080\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B41110\x00\x00\x00\x00\x00\x00', @@ -518,24 +510,6 @@ FW_VERSIONS = { (Ecu.engine, 0x700, None): [ b'\x01896630742000\x00\x00\x00\x00', b'\x01896630743000\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F6201200\x00\x00\x00\x00', - b'\x018821F6201300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', - b'\x028646F4104100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - ], - }, - CAR.AVALONH_TSS2: { - (Ecu.abs, 0x7b0, None): [ - b'F152641080\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B41110\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ b'\x018966306Q6000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ @@ -571,65 +545,6 @@ FW_VERSIONS = { b'\x018966333Q6300\x00\x00\x00\x00', b'\x018966333Q6500\x00\x00\x00\x00', b'\x018966333W6000\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x02333P1100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'8821F0601200 ', - b'8821F0601300 ', - b'8821F0602000 ', - b'8821F0603300 ', - b'8821F0604100 ', - b'8821F0605200 ', - b'8821F0607200 ', - b'8821F0608000 ', - b'8821F0608200 ', - b'8821F0609100 ', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152606210\x00\x00\x00\x00\x00\x00', - b'F152606230\x00\x00\x00\x00\x00\x00', - b'F152606270\x00\x00\x00\x00\x00\x00', - b'F152606290\x00\x00\x00\x00\x00\x00', - b'F152606410\x00\x00\x00\x00\x00\x00', - b'F152633540\x00\x00\x00\x00\x00\x00', - b'F152633A10\x00\x00\x00\x00\x00\x00', - b'F152633A20\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B33540\x00\x00\x00\x00\x00\x00', - b'8965B33542\x00\x00\x00\x00\x00\x00', - b'8965B33580\x00\x00\x00\x00\x00\x00', - b'8965B33581\x00\x00\x00\x00\x00\x00', - b'8965B33621\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ # Same as 0x791 - b'8821F0601200 ', - b'8821F0601300 ', - b'8821F0602000 ', - b'8821F0603300 ', - b'8821F0604100 ', - b'8821F0605200 ', - b'8821F0607200 ', - b'8821F0608000 ', - b'8821F0608200 ', - b'8821F0609100 ', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F0601200 ', - b'8646F0601300 ', - b'8646F0601400 ', - b'8646F0603400 ', - b'8646F0604100 ', - b'8646F0605000 ', - b'8646F0606000 ', - b'8646F0606100 ', - b'8646F0607100 ', - ], - }, - CAR.CAMRYH: { - (Ecu.engine, 0x700, None): [ b'\x018966306Q6000\x00\x00\x00\x00', b'\x018966333N1100\x00\x00\x00\x00', b'\x018966333N4300\x00\x00\x00\x00', @@ -652,7 +567,35 @@ FW_VERSIONS = { b'\x028966306S0100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x028966306S1100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', ], + (Ecu.engine, 0x7e0, None): [ + b'\x02333P1100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'8821F0601200 ', + b'8821F0601300 ', + b'8821F0602000 ', + b'8821F0603300 ', + b'8821F0604100 ', + b'8821F0605200 ', + b'8821F0607200 ', + b'8821F0608000 ', + b'8821F0608200 ', + b'8821F0609100 ', + b'8821F0603400 ', + b'8821F0604000 ', + b'8821F0604200 ', + b'8821F0606200 ', + b'8821F0609000 ', + ], (Ecu.abs, 0x7b0, None): [ + b'F152606210\x00\x00\x00\x00\x00\x00', + b'F152606230\x00\x00\x00\x00\x00\x00', + b'F152606270\x00\x00\x00\x00\x00\x00', + b'F152606290\x00\x00\x00\x00\x00\x00', + b'F152606410\x00\x00\x00\x00\x00\x00', + b'F152633540\x00\x00\x00\x00\x00\x00', + b'F152633A10\x00\x00\x00\x00\x00\x00', + b'F152633A20\x00\x00\x00\x00\x00\x00', b'F152633214\x00\x00\x00\x00\x00\x00', b'F152633660\x00\x00\x00\x00\x00\x00', b'F152633712\x00\x00\x00\x00\x00\x00', @@ -660,70 +603,61 @@ FW_VERSIONS = { b'F152633B51\x00\x00\x00\x00\x00\x00', b'F152633B60\x00\x00\x00\x00\x00\x00', ], - (Ecu.dsu, 0x791, None): [ - b'8821F0601200 ', - b'8821F0601300 ', - b'8821F0603400 ', - b'8821F0604000 ', - b'8821F0604100 ', - b'8821F0604200 ', - b'8821F0605200 ', - b'8821F0606200 ', - b'8821F0607200 ', - b'8821F0608000 ', - b'8821F0608200 ', - b'8821F0609000 ', - b'8821F0609100 ', - ], (Ecu.eps, 0x7a1, None): [ b'8965B33540\x00\x00\x00\x00\x00\x00', b'8965B33542\x00\x00\x00\x00\x00\x00', - b'8965B33550\x00\x00\x00\x00\x00\x00', - b'8965B33551\x00\x00\x00\x00\x00\x00', b'8965B33580\x00\x00\x00\x00\x00\x00', b'8965B33581\x00\x00\x00\x00\x00\x00', - b'8965B33611\x00\x00\x00\x00\x00\x00', b'8965B33621\x00\x00\x00\x00\x00\x00', + b'8965B33550\x00\x00\x00\x00\x00\x00', + b'8965B33551\x00\x00\x00\x00\x00\x00', + b'8965B33611\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ # Same as 0x791 b'8821F0601200 ', b'8821F0601300 ', - b'8821F0603400 ', - b'8821F0604000 ', + b'8821F0602000 ', + b'8821F0603300 ', b'8821F0604100 ', - b'8821F0604200 ', b'8821F0605200 ', - b'8821F0606200 ', b'8821F0607200 ', b'8821F0608000 ', b'8821F0608200 ', - b'8821F0609000 ', b'8821F0609100 ', + b'8821F0603400 ', + b'8821F0604000 ', + b'8821F0604200 ', + b'8821F0606200 ', + b'8821F0609000 ', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'8646F0601200 ', b'8646F0601300 ', b'8646F0601400 ', b'8646F0603400 ', - b'8646F0603500 ', b'8646F0604100 ', b'8646F0605000 ', b'8646F0606000 ', b'8646F0606100 ', - b'8646F0607000 ', b'8646F0607100 ', + b'8646F0603500 ', + b'8646F0607000 ', ], }, CAR.CAMRY_TSS2: { (Ecu.eps, 0x7a1, None): [ b'8965B33630\x00\x00\x00\x00\x00\x00', b'8965B33640\x00\x00\x00\x00\x00\x00', + b'8965B33650\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F152606370\x00\x00\x00\x00\x00\x00', b'\x01F152606390\x00\x00\x00\x00\x00\x00', b'\x01F152606400\x00\x00\x00\x00\x00\x00', b'\x01F152606431\x00\x00\x00\x00\x00\x00', + b'F152633D00\x00\x00\x00\x00\x00\x00', + b'F152633D60\x00\x00\x00\x00\x00\x00', + b'F152633310\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x018966306Q5000\x00\x00\x00\x00', @@ -734,10 +668,16 @@ FW_VERSIONS = { b'\x018966306T3200\x00\x00\x00\x00', b'\x018966306T4000\x00\x00\x00\x00', b'\x018966306T4100\x00\x00\x00\x00', + b'\x018966306Q6000\x00\x00\x00\x00', + b'\x018966306Q7000\x00\x00\x00\x00', + b'\x018966306T0000\x00\x00\x00\x00', + b'\x018966306V1000\x00\x00\x00\x00', + b'\x01896633T20000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F6201200\x00\x00\x00\x00', b'\x018821F6201300\x00\x00\x00\x00', + b'\x018821F6201400\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F0602100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', @@ -747,35 +687,7 @@ FW_VERSIONS = { b'\x028646F3305200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - ], - }, - CAR.CAMRYH_TSS2: { - (Ecu.eps, 0x7a1, None): [ - b'8965B33630\x00\x00\x00\x00\x00\x00', - b'8965B33650\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152633D00\x00\x00\x00\x00\x00\x00', - b'F152633D60\x00\x00\x00\x00\x00\x00', - b'F152633310\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x018966306Q6000\x00\x00\x00\x00', - b'\x018966306Q7000\x00\x00\x00\x00', - b'\x018966306T0000\x00\x00\x00\x00', - b'\x018966306V1000\x00\x00\x00\x00', - b'\x01896633T20000\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 15): [ - b'\x018821F6201200\x00\x00\x00\x00', - b'\x018821F6201300\x00\x00\x00\x00', - b'\x018821F6201400\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 109): [ - b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', - b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.CHR: { @@ -867,13 +779,19 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'F152610260\x00\x00\x00\x00\x00\x00', b'F1526F4270\x00\x00\x00\x00\x00\x00', + b'F152610041\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B10091\x00\x00\x00\x00\x00\x00', b'8965B10110\x00\x00\x00\x00\x00\x00', + b'8965B10092\x00\x00\x00\x00\x00\x00', + b'8965B10111\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x0189663F459000\x00\x00\x00\x00', + b'\x0189663F438000\x00\x00\x00\x00', + b'\x02896631025000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x0289663F453000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\x0331014000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', @@ -881,33 +799,12 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821FF410200\x00\x00\x00\x00', b'\x018821FF410300\x00\x00\x00\x00', + b'\x018821FF410500\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646FF410200\x00\x00\x00\x008646GF408200\x00\x00\x00\x00', b'\x028646FF411100\x00\x00\x00\x008646GF409000\x00\x00\x00\x00', - ], - }, - CAR.CHRH_TSS2: { - (Ecu.eps, 0x7a1, None): [ - b'8965B10092\x00\x00\x00\x00\x00\x00', - b'8965B10091\x00\x00\x00\x00\x00\x00', - b'8965B10111\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152610041\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x0189663F438000\x00\x00\x00\x00', - b'\x02896631025000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x0289663F453000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 15): [ - b'\x018821FF410500\x00\x00\x00\x00', - b'\x018821FF410300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 109): [ b'\x028646FF413100\x00\x00\x00\x008646GF411100\x00\x00\x00\x00', - b'\x028646FF411100\x00\x00\x00\x008646GF409000\x00\x00\x00\x00', ], }, CAR.COROLLA: { @@ -1193,6 +1090,18 @@ FW_VERSIONS = { b'\x01F15260E110\x00\x00\x00\x00\x00\x00', b'\x01F15260E170\x00\x00\x00\x00\x00\x00', b'\x01F15260E05300\x00\x00\x00\x00', + b'\x01F15264872300\x00\x00\x00\x00', + b'\x01F15264872400\x00\x00\x00\x00', + b'\x01F15264872500\x00\x00\x00\x00', + b'\x01F15264872600\x00\x00\x00\x00', + b'\x01F15264872700\x00\x00\x00\x00', + b'\x01F15264873500\x00\x00\x00\x00', + b'\x01F152648C6300\x00\x00\x00\x00', + b'\x01F152648J4000\x00\x00\x00\x00', + b'\x01F152648J5000\x00\x00\x00\x00', + b'\x01F152648J6000\x00\x00\x00\x00', + b'\x01F152648J7000\x00\x00\x00\x00', + b'\x01F152648L5000\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x01896630E62100\x00\x00\x00\x00', @@ -1213,44 +1122,13 @@ FW_VERSIONS = { b'\x01896630EE1100\x00\x00\x00\x00', b'\x01896630EG3000\x00\x00\x00\x00', b'\x01896630EG5000\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F3301400\x00\x00\x00\x00', - b'\x018821F6201200\x00\x00\x00\x00', - b'\x018821F6201300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', - b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - ], - }, - CAR.HIGHLANDERH_TSS2: { - (Ecu.eps, 0x7a1, None): [ - b'8965B48241\x00\x00\x00\x00\x00\x00', - b'8965B48310\x00\x00\x00\x00\x00\x00', - b'8965B48400\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'\x01F15264872300\x00\x00\x00\x00', - b'\x01F15264872400\x00\x00\x00\x00', - b'\x01F15264872500\x00\x00\x00\x00', - b'\x01F15264872600\x00\x00\x00\x00', - b'\x01F15264873500\x00\x00\x00\x00', - b'\x01F152648C6300\x00\x00\x00\x00', - b'\x01F152648J4000\x00\x00\x00\x00', - b'\x01F152648J5000\x00\x00\x00\x00', - b'\x01F152648J6000\x00\x00\x00\x00', - b'\x01F15264872700\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ b'\x01896630E67000\x00\x00\x00\x00', b'\x01896630EA1000\x00\x00\x00\x00', b'\x01896630EE4000\x00\x00\x00\x00', b'\x01896630EE4100\x00\x00\x00\x00', b'\x01896630EE5000\x00\x00\x00\x00', b'\x01896630EE6000\x00\x00\x00\x00', + b'\x01896630EE7000\x00\x00\x00\x00', b'\x01896630EF8000\x00\x00\x00\x00', b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', @@ -1638,46 +1516,6 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'\x01F15260R350\x00\x00\x00\x00\x00\x00', b'\x01F15260R361\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x01896634AA0000\x00\x00\x00\x00', - b'\x01896634AA0100\x00\x00\x00\x00', - b'\x01896634AA1000\x00\x00\x00\x00', - b'\x01896634A88000\x00\x00\x00\x00', - b'\x01896634A89000\x00\x00\x00\x00', - b'\x01896634A89100\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F0R01100\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00', - ], - }, - CAR.RAV4_TSS2_2023: { - (Ecu.abs, 0x7b0, None): [ - b'\x01F15260R450\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x01896634A88100\x00\x00\x00\x00', - b'\x01896634AJ2000\x00\x00\x00\x00', - b'\x01896634A89100\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F0R03100\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F0R05100\x00\x00\x00\x008646G0R02100\x00\x00\x00\x00', - ], - }, - CAR.RAV4H_TSS2_2022: { - (Ecu.abs, 0x7b0, None): [ b'\x01F15264283100\x00\x00\x00\x00', b'\x01F15264286200\x00\x00\x00\x00', b'\x01F15264286100\x00\x00\x00\x00', @@ -1689,6 +1527,12 @@ FW_VERSIONS = { b'8965B42172\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ + b'\x01896634AA0000\x00\x00\x00\x00', + b'\x01896634AA0100\x00\x00\x00\x00', + b'\x01896634AA1000\x00\x00\x00\x00', + b'\x01896634A88000\x00\x00\x00\x00', + b'\x01896634A89000\x00\x00\x00\x00', + b'\x01896634A89100\x00\x00\x00\x00', b'\x01896634A02001\x00\x00\x00\x00', b'\x01896634A03000\x00\x00\x00\x00', b'\x01896634A08000\x00\x00\x00\x00', @@ -1704,8 +1548,9 @@ FW_VERSIONS = { b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00', ], }, - CAR.RAV4H_TSS2_2023: { + CAR.RAV4_TSS2_2023: { (Ecu.abs, 0x7b0, None): [ + b'\x01F15260R450\x00\x00\x00\x00\x00\x00', b'\x01F15264283200\x00\x00\x00\x00', b'\x01F15264283300\x00\x00\x00\x00', b'\x01F152642F1000\x00\x00\x00\x00', @@ -1715,15 +1560,12 @@ FW_VERSIONS = { b'8965B42371\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ + b'\x01896634A88100\x00\x00\x00\x00', + b'\x01896634AJ2000\x00\x00\x00\x00', + b'\x01896634A89100\x00\x00\x00\x00', b'\x01896634AE1001\x00\x00\x00\x00', b'\x01896634AF0000\x00\x00\x00\x00', ], - (Ecu.hybrid, 0x7d2, None): [ - b'\x02899830R39000\x00\x00\x00\x00899850R20000\x00\x00\x00\x00', - b'\x02899830R41000\x00\x00\x00\x00899850R20000\x00\x00\x00\x00', - b'\x028998342C0000\x00\x00\x00\x00899854224000\x00\x00\x00\x00', - b'\x028998342C6000\x00\x00\x00\x00899854224000\x00\x00\x00\x00', - ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F0R03100\x00\x00\x00\x00', ], @@ -1788,7 +1630,6 @@ FW_VERSIONS = { CAR.LEXUS_ES_TSS2: { (Ecu.engine, 0x700, None): [ b'\x018966306U6000\x00\x00\x00\x00', - b'\x01896630EC9100\x00\x00\x00\x00', b'\x018966333T5000\x00\x00\x00\x00', b'\x018966333T5100\x00\x00\x00\x00', b'\x018966333X6000\x00\x00\x00\x00', @@ -1806,7 +1647,6 @@ FW_VERSIONS = { b'\x01F152606281\x00\x00\x00\x00\x00\x00', b'\x01F152606340\x00\x00\x00\x00\x00\x00', b'\x01F152606461\x00\x00\x00\x00\x00\x00', - b'\x01F15260E031\x00\x00\x00\x00\x00\x00', b'\x01F15260646200\x00\x00\x00\x00', b'F152633423\x00\x00\x00\x00\x00\x00', b'F152633680\x00\x00\x00\x00\x00\x00', @@ -1819,7 +1659,6 @@ FW_VERSIONS = { b'8965B33590\x00\x00\x00\x00\x00\x00', b'8965B33690\x00\x00\x00\x00\x00\x00', b'8965B33721\x00\x00\x00\x00\x00\x00', - b'8965B48271\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301100\x00\x00\x00\x00', @@ -1835,7 +1674,6 @@ FW_VERSIONS = { b'\x028646F3304100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F3304300\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F3309100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F4810200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F0610000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F3303100\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3304200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', @@ -2090,6 +1928,7 @@ FW_VERSIONS = { b'\x01896630EA9000\x00\x00\x00\x00', b'\x01896630EB0000\x00\x00\x00\x00', b'\x01896630EC9000\x00\x00\x00\x00', + b'\x01896630EC9100\x00\x00\x00\x00', b'\x01896630ED0000\x00\x00\x00\x00', b'\x01896630ED0100\x00\x00\x00\x00', b'\x01896630ED6000\x00\x00\x00\x00', @@ -2185,11 +2024,15 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\x0235870000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0235883000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B58040\x00\x00\x00\x00\x00\x00', b'8965B58052\x00\x00\x00\x00\x00\x00', ], + (Ecu.abs, 0x7b0, None): [ + b'F152658341\x00\x00\x00\x00\x00\x00' + ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301200\x00\x00\x00\x00', b'\x018821F3301400\x00\x00\x00\x00', @@ -2197,22 +2040,6 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F58010C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F5803200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - ], - }, - CAR.ALPHARDH_TSS2: { - (Ecu.engine, 0x7e0, None): [ - b'\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B58040\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152658341\x00\x00\x00\x00\x00\x00' - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F3301400\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646FV201000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, @@ -2232,20 +2059,14 @@ DBC = { CAR.LEXUS_RX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.CHR: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.CHR_TSS2: dbc_dict('toyota_nodsu_pt_generated', None), - CAR.CHRH_TSS2: dbc_dict('toyota_nodsu_pt_generated', None), CAR.CAMRY: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), - CAR.CAMRYH: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.CAMRY_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.CAMRYH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.HIGHLANDER: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.HIGHLANDER_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.HIGHLANDERH: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), - CAR.HIGHLANDERH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.AVALON: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.AVALON_2019: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), - CAR.AVALONH_2019: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.AVALON_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.AVALONH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.RAV4_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.RAV4_TSS2_2022: dbc_dict('toyota_nodsu_pt_generated', None), CAR.RAV4_TSS2_2023: dbc_dict('toyota_nodsu_pt_generated', None), @@ -2257,14 +2078,11 @@ DBC = { CAR.LEXUS_IS: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.LEXUS_IS_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.LEXUS_CTH: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), - CAR.RAV4H_TSS2_2022: dbc_dict('toyota_nodsu_pt_generated', None), - CAR.RAV4H_TSS2_2023: dbc_dict('toyota_nodsu_pt_generated', None), CAR.LEXUS_NX: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.LEXUS_NX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.PRIUS_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.MIRAI: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.ALPHARD_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.ALPHARDH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), } # These cars have non-standard EPS torque scale factors. All others are 73 @@ -2272,20 +2090,19 @@ EPS_SCALE = defaultdict(lambda: 73, {CAR.PRIUS: 66, CAR.COROLLA: 88, CAR.LEXUS_I # Toyota/Lexus Safety Sense 2.0 and 2.5 TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.COROLLA_TSS2, CAR.LEXUS_ES_TSS2, - CAR.RAV4H_TSS2_2022, CAR.RAV4H_TSS2_2023, CAR.LEXUS_RX_TSS2, CAR.HIGHLANDER_TSS2, - CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2, CAR.LEXUS_IS_TSS2, CAR.MIRAI, CAR.LEXUS_NX_TSS2, - CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2, CAR.CHR_TSS2, CAR.CHRH_TSS2} + CAR.LEXUS_RX_TSS2, CAR.HIGHLANDER_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.LEXUS_IS_TSS2, + CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.CHR_TSS2} -NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CAMRY, CAR.CAMRYH} +NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CAMRY} # the DSU uses the AEB message for longitudinal on these cars UNSUPPORTED_DSU_CAR = {CAR.LEXUS_IS, CAR.LEXUS_RC} # these cars have a radar which sends ACC messages instead of the camera -RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2_2023, CAR.RAV4_TSS2_2023, CAR.CHR_TSS2, CAR.CHRH_TSS2} +RADAR_ACC_CAR = {CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.CHR_TSS2} # these cars use the Lane Tracing Assist (LTA) message for lateral control -ANGLE_CONTROL_CAR = {CAR.RAV4H_TSS2_2023, CAR.RAV4_TSS2_2023} +ANGLE_CONTROL_CAR = {CAR.RAV4_TSS2_2023} # no resume button press required NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.PRIUS_V, CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH} diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index a1bdb4dfe8..8ca562a720 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -170,12 +170,9 @@ class CarController: can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, ext_bus, CS.gra_stock_values, cancel=CC.cruiseControl.cancel, resume=CC.cruiseControl.resume)) if not (CC.cruiseControl.cancel or CC.cruiseControl.resume) and CS.out.cruiseState.enabled: - if gra_send_ready and not self.CP.pcmCruiseSpeed: + if not self.CP.pcmCruiseSpeed: self.cruise_button = self.get_cruise_buttons(CS, CC.vCruise) if self.cruise_button is not None: - send_freq = (self.frame % 10 == 0) - if not (self.v_tsc_state != 0 or self.m_tsc_state > 1) and abs(self.target_speed - self.v_set_dis) <= 2: - send_freq = (self.frame - self.last_button_frame) * DT_CTRL > 0.01 if self.acc_type == -1: if self.button_count >= 2 and self.v_set_dis_prev != self.v_set_dis: self.acc_type = 1 if abs(self.v_set_dis - self.v_set_dis_prev) >= 10 and self.last_cruise_button in (1, 2) else \ @@ -186,10 +183,9 @@ class CarController: self.cruise_button = 1 if self.cruise_button == 1 else 2 # accel, decel elif self.acc_type == 1: self.cruise_button = 3 if self.cruise_button == 1 else 4 # resume, set - if send_freq: - can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, ext_bus, CS.gra_stock_values, - buttons=self.cruise_button)) - self.last_button_frame = self.frame + if self.frame % self.CCP.BTN_STEP == 0: + can_sends.append(self.CCS.create_acc_buttons_control(self.packer_pt, ext_bus, CS.gra_stock_values, frame=(self.frame // self.CCP.BTN_STEP), + buttons=self.cruise_button, custom_stock_long=True)) self.send_count += 1 else: self.send_count = 0 diff --git a/selfdrive/car/volkswagen/mqbcan.py b/selfdrive/car/volkswagen/mqbcan.py index f633aae349..1dc852ba90 100644 --- a/selfdrive/car/volkswagen/mqbcan.py +++ b/selfdrive/car/volkswagen/mqbcan.py @@ -31,7 +31,7 @@ def create_lka_hud_control(packer, bus, ldw_stock_values, enabled, lat_active, s return packer.make_can_msg("LDW_02", bus, values) -def create_acc_buttons_control(packer, bus, gra_stock_values, buttons=0, cancel=False, resume=False): +def create_acc_buttons_control(packer, bus, gra_stock_values, frame=0, buttons=0, cancel=False, resume=False, custom_stock_long=False): values = {s: gra_stock_values[s] for s in [ "GRA_Hauptschalter", # ACC button, on/off "GRA_Typ_Hauptschalter", # ACC main button type @@ -46,7 +46,7 @@ def create_acc_buttons_control(packer, bus, gra_stock_values, buttons=0, cancel= set_cruise = 1 if buttons == 4 else 0 values.update({ - "COUNTER": (gra_stock_values["COUNTER"] + 1) % 16, + "COUNTER": (frame + 1) % 0x10 if custom_stock_long else (gra_stock_values["COUNTER"] + 1) % 16, "GRA_Abbrechen": cancel, "GRA_Tip_Wiederaufnahme": resume or resume_cruise, "GRA_Tip_Setzen": set_cruise, diff --git a/selfdrive/car/volkswagen/pqcan.py b/selfdrive/car/volkswagen/pqcan.py index f1f4d1dc20..b5be66e957 100644 --- a/selfdrive/car/volkswagen/pqcan.py +++ b/selfdrive/car/volkswagen/pqcan.py @@ -31,7 +31,7 @@ def create_lka_hud_control(packer, bus, ldw_stock_values, enabled, lat_active, s return packer.make_can_msg("LDW_Status", bus, values) -def create_acc_buttons_control(packer, bus, gra_stock_values, buttons=0, cancel=False, resume=False): +def create_acc_buttons_control(packer, bus, gra_stock_values, frame=0, buttons=0, cancel=False, resume=False, custom_stock_long=False): values = {s: gra_stock_values[s] for s in [ "GRA_Hauptschalt", # ACC button, on/off "GRA_Typ_Hauptschalt", # ACC button, momentary vs latching @@ -45,7 +45,7 @@ def create_acc_buttons_control(packer, bus, gra_stock_values, buttons=0, cancel= set_cruise = 1 if buttons == 4 else 0 values.update({ - "COUNTER": (gra_stock_values["COUNTER"] + 1) % 16, + "COUNTER": (frame + 1) % 0x10 if custom_stock_long else (gra_stock_values["COUNTER"] + 1) % 16, "GRA_Abbrechen": cancel, "GRA_Recall": resume or resume_cruise, "GRA_Neu_Setzen": set_cruise, diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 0b9f44df9d..52b1f4495c 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -46,6 +46,7 @@ class CarControllerParams: self.STEER_DRIVER_ALLOWANCE = 80 # Driver intervention threshold 0.8 Nm self.STEER_DELTA_UP = 6 # Max HCA reached in 1.00s (STEER_MAX / (50Hz * 1.00)) self.STEER_DELTA_DOWN = 10 # Min HCA reached in 0.60s (STEER_MAX / (50Hz * 0.60)) + self.BTN_STEP = 2 if CP.transmissionType == TransmissionType.automatic: self.shifter_values = can_define.dv["Getriebe_1"]["Waehlhebelposition__Getriebe_1_"] @@ -75,6 +76,7 @@ class CarControllerParams: self.STEER_DRIVER_ALLOWANCE = 80 # Driver intervention threshold 0.8 Nm self.STEER_DELTA_UP = 4 # Max HCA reached in 1.50s (STEER_MAX / (50Hz * 1.50)) self.STEER_DELTA_DOWN = 10 # Min HCA reached in 0.60s (STEER_MAX / (50Hz * 0.60)) + self.BTN_STEP = 3 if CP.transmissionType == TransmissionType.automatic: self.shifter_values = can_define.dv["Getriebe_11"]["GE_Fahrstufe"] @@ -842,6 +844,7 @@ FW_VERSIONS = { b'\xf1\x8783A907115F \xf1\x890002', b'\xf1\x8783A907115G \xf1\x890001', b'\xf1\x8783A907115K \xf1\x890001', + b'\xf1\x8783A907115K \xf1\x890002', b'\xf1\x8704E906024AP\xf1\x891461', b'\xf1\x8783A907115 \xf1\x890007', ], @@ -851,6 +854,7 @@ FW_VERSIONS = { b'\xf1\x8709G927158GC\xf1\x893821', b'\xf1\x8709G927158GD\xf1\x893820', b'\xf1\x8709G927158GM\xf1\x893936', + b'\xf1\x8709G927158GN\xf1\x893938', b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870DL300011N \xf1\x892001', b'\xf1\x870DL300011N \xf1\x892012', @@ -889,6 +893,7 @@ FW_VERSIONS = { b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A60804A1', + b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002RA60A2ROM', b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002SA6092SOM', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60804A1', @@ -1081,11 +1086,13 @@ FW_VERSIONS = { b'\xf1\x8783A906259 \xf1\x890001', b'\xf1\x8783A906259 \xf1\x890005', b'\xf1\x8783A906259C \xf1\x890002', + b'\xf1\x8783A906259D \xf1\x890001', b'\xf1\x8783A906259F \xf1\x890001', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158CN\xf1\x893608', b'\xf1\x8709G927158FL\xf1\x893758', + b'\xf1\x8709G927158GG\xf1\x893825', b'\xf1\x8709G927158GP\xf1\x893937', b'\xf1\x870GC300045D \xf1\x892802', b'\xf1\x870GC300046F \xf1\x892701', @@ -1095,6 +1102,7 @@ FW_VERSIONS = { b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218C219321532111', b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111224118A119321532111', b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111237116A119321532111', + b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218A219321532111', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000300', diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index ecf00f8fd1..be92065118 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -4,7 +4,7 @@ from cereal import car, log from openpilot.common.conversions import Conversions as CV from openpilot.common.numpy_fast import clip, interp from openpilot.common.realtime import DT_MDL -from openpilot.selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.modeld.constants import ModelConstants # WARNING: this value was determined based on the model's training distribution, # model predictions above this speed can be unpredictable @@ -243,7 +243,7 @@ def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates): # in high delay cases some corrections never even get commanded. So just use # psi to calculate a simple linearization of desired curvature current_curvature_desired = curvatures[0] - psi = interp(delay, T_IDXS[:CONTROL_N], psis) + psi = interp(delay, ModelConstants.T_IDXS[:CONTROL_N], psis) average_curvature_desired = psi / (v_ego * delay) desired_curvature = 2 * average_curvature_desired - current_curvature_desired diff --git a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py index 5e6f884df4..83ec3b3a13 100755 --- a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py +++ b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py @@ -5,7 +5,7 @@ import numpy as np from casadi import SX, vertcat, sin, cos # WARNING: imports outside of constants will not trigger a rebuild -from openpilot.selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.modeld.constants import ModelConstants if __name__ == '__main__': # generating code from openpilot.third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver @@ -66,7 +66,7 @@ def gen_lat_ocp(): ocp = AcadosOcp() ocp.model = gen_lat_model() - Tf = np.array(T_IDXS)[N] + Tf = np.array(ModelConstants.T_IDXS)[N] # set dimensions ocp.dims.N = N @@ -122,7 +122,7 @@ def gen_lat_ocp(): # set prediction horizon ocp.solver_options.tf = Tf - ocp.solver_options.shooting_nodes = np.array(T_IDXS)[:N+1] + ocp.solver_options.shooting_nodes = np.array(ModelConstants.T_IDXS)[:N+1] ocp.code_export_directory = EXPORT_DIR return ocp diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index 61c150aadc..ee65c4a69e 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -3,7 +3,7 @@ from openpilot.common.numpy_fast import clip, interp from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, apply_deadzone from openpilot.selfdrive.controls.lib.pid import PIDController -from openpilot.selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.modeld.constants import ModelConstants LongCtrlState = car.CarControl.Actuators.LongControlState @@ -70,19 +70,19 @@ class LongControl: # Interp control trajectory speeds = long_plan.speeds if len(speeds) == CONTROL_N: - v_target_now = interp(t_since_plan, T_IDXS[:CONTROL_N], speeds) - a_target_now = interp(t_since_plan, T_IDXS[:CONTROL_N], long_plan.accels) + v_target_now = interp(t_since_plan, ModelConstants.T_IDXS[:CONTROL_N], speeds) + a_target_now = interp(t_since_plan, ModelConstants.T_IDXS[:CONTROL_N], long_plan.accels) - v_target_lower = interp(self.CP.longitudinalActuatorDelayLowerBound + t_since_plan, T_IDXS[:CONTROL_N], speeds) + v_target_lower = interp(self.CP.longitudinalActuatorDelayLowerBound + t_since_plan, ModelConstants.T_IDXS[:CONTROL_N], speeds) a_target_lower = 2 * (v_target_lower - v_target_now) / self.CP.longitudinalActuatorDelayLowerBound - a_target_now - v_target_upper = interp(self.CP.longitudinalActuatorDelayUpperBound + t_since_plan, T_IDXS[:CONTROL_N], speeds) + v_target_upper = interp(self.CP.longitudinalActuatorDelayUpperBound + t_since_plan, ModelConstants.T_IDXS[:CONTROL_N], speeds) a_target_upper = 2 * (v_target_upper - v_target_now) / self.CP.longitudinalActuatorDelayUpperBound - a_target_now v_target = min(v_target_lower, v_target_upper) a_target = min(a_target_lower, a_target_upper) - v_target_1sec = interp(self.CP.longitudinalActuatorDelayUpperBound + t_since_plan + 1.0, T_IDXS[:CONTROL_N], speeds) + v_target_1sec = interp(self.CP.longitudinalActuatorDelayUpperBound + t_since_plan + 1.0, ModelConstants.T_IDXS[:CONTROL_N], speeds) else: v_target = 0.0 v_target_now = 0.0 diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index ec0b898b8c..cccd8e870d 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -11,7 +11,9 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.controls.lib.sunnypilot.common import Source from openpilot.selfdrive.controls.lib.sunnypilot.speed_limit_controller import SpeedLimitController -from openpilot.selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.modeld.constants import ModelConstants +from openpilot.selfdrive.controls.lib.sunnypilot.common import Source +from openpilot.selfdrive.controls.lib.sunnypilot.speed_limit_controller import SpeedLimitController from openpilot.selfdrive.car.interfaces import ACCEL_MIN, ACCEL_MAX from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc @@ -85,8 +87,7 @@ class LongitudinalPlanner: def read_param(self): try: - self.dynamic_experimental_controller.set_enabled(self.params.get_bool("DynamicExperimentalControl") and - self.params.get_bool("DynamicExperimentalControlToggle")) + self.dynamic_experimental_controller.set_enabled(self.params.get_bool("DynamicExperimentalControl")) except AttributeError: self.dynamic_experimental_controller = DynamicExperimentalController() try: @@ -99,9 +100,9 @@ class LongitudinalPlanner: if (len(model_msg.position.x) == 33 and len(model_msg.velocity.x) == 33 and len(model_msg.acceleration.x) == 33): - x = np.interp(T_IDXS_MPC, T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC - v = np.interp(T_IDXS_MPC, T_IDXS, model_msg.velocity.x) - model_error - a = np.interp(T_IDXS_MPC, T_IDXS, model_msg.acceleration.x) + x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC + v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x) - model_error + a = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.acceleration.x) j = np.zeros(len(T_IDXS_MPC)) else: x = np.zeros(len(T_IDXS_MPC)) @@ -167,11 +168,11 @@ class LongitudinalPlanner: x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) self.mpc.update(sm['radarState'], v_cruise_sol, x, v, a, j, personality=self.personality) - self.v_desired_trajectory_full = np.interp(T_IDXS, T_IDXS_MPC, self.mpc.v_solution) - self.a_desired_trajectory_full = np.interp(T_IDXS, T_IDXS_MPC, self.mpc.a_solution) + self.v_desired_trajectory_full = np.interp(ModelConstants.T_IDXS, T_IDXS_MPC, self.mpc.v_solution) + self.a_desired_trajectory_full = np.interp(ModelConstants.T_IDXS, T_IDXS_MPC, self.mpc.a_solution) self.v_desired_trajectory = self.v_desired_trajectory_full[:CONTROL_N] self.a_desired_trajectory = self.a_desired_trajectory_full[:CONTROL_N] - self.j_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution) + self.j_desired_trajectory = np.interp(ModelConstants.T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution) # TODO counter is only needed because radar is glitchy, remove once radar is gone self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill @@ -180,7 +181,7 @@ class LongitudinalPlanner: # Interpolate 0.05 seconds and save as starting point for next iteration a_prev = self.a_desired - self.a_desired = float(interp(DT_MDL, T_IDXS[:CONTROL_N], self.a_desired_trajectory)) + self.a_desired = float(interp(DT_MDL, ModelConstants.T_IDXS[:CONTROL_N], self.a_desired_trajectory)) self.v_desired_filter.x = self.v_desired_filter.x + DT_MDL * (self.a_desired + a_prev) / 2.0 self.e2e_events(sm) diff --git a/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py b/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py index 1a3812e776..0b66ec2a2b 100644 --- a/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py +++ b/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py @@ -12,7 +12,7 @@ from openpilot.selfdrive.controls.lib.events import Events, ET from openpilot.selfdrive.controls.lib.sunnypilot.common import Source, Policy from openpilot.selfdrive.controls.lib.sunnypilot.helpers import description_for_state, debug from openpilot.selfdrive.controls.lib.sunnypilot.speed_limit_resolver import SpeedLimitResolver -from openpilot.selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.modeld.constants import ModelConstants class SpeedLimitController: @@ -23,7 +23,6 @@ class SpeedLimitController: self._last_op_enabled_time = 0.0 self._is_metric = self._params.get_bool("IsMetric") self._is_enabled = self._params.get_bool("SpeedLimitControl") - self._offset_enabled = self._params.get_bool("SpeedLimitPercOffset") self._disengage_on_accelerator = self._params.get_bool("DisengageOnAccelerator") self._op_enabled = False self._op_enabled_prev = False @@ -96,13 +95,12 @@ class SpeedLimitController: @property def speed_limit_offset(self): - if self._offset_enabled: - if self._offset_type == 0: - return interp(self._speed_limit, LIMIT_PERC_OFFSET_BP, LIMIT_PERC_OFFSET_V) * self._speed_limit - elif self._offset_type == 1: - return self._offset_value * 0.01 * self._speed_limit - elif self._offset_type == 2: - return self._offset_value * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS) + if self._offset_type == 0: + return interp(self._speed_limit, LIMIT_PERC_OFFSET_BP, LIMIT_PERC_OFFSET_V) * self._speed_limit + elif self._offset_type == 1: + return self._offset_value * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS) + elif self._offset_type == 2: + return self._offset_value * 0.01 * self._speed_limit return 0. @property @@ -121,10 +119,9 @@ class SpeedLimitController: t = time.monotonic() if t > self._last_params_update + PARAMS_UPDATE_PERIOD: self._is_enabled = self._params.get_bool("SpeedLimitControl") - self._offset_enabled = self._params.get_bool("SpeedLimitPercOffset") self._offset_type = int(self._params.get("SpeedLimitOffsetType", encoding='utf8')) self._offset_value = float(self._params.get("SpeedLimitValueOffset", encoding='utf8')) - debug(f'Updated Speed limit params. enabled: {self._is_enabled}, with offset: {self._offset_enabled}') + debug(f'Updated Speed limit params. enabled: {self._is_enabled}, with offset: {self._offset_type}') self._last_params_update = t def _update_calculations(self): @@ -196,11 +193,11 @@ class SpeedLimitController: if self.distance > 0: return (self.speed_limit_offseted**2 - self._v_ego**2) / (2. * self.distance) - return self._v_offset / T_IDXS[CONTROL_N] + return self._v_offset / ModelConstants.T_IDXS[CONTROL_N] def get_active_state_target_acceleration(self): """ In active state, aim to keep speed constant around control time horizon """ - return self._v_offset / T_IDXS[CONTROL_N] + return self._v_offset / ModelConstants.T_IDXS[CONTROL_N] def _update_solution(self): a_target = self.acceleration_solutions[self.state]() diff --git a/selfdrive/controls/lib/sunnypilot/speed_limit_resolver.py b/selfdrive/controls/lib/sunnypilot/speed_limit_resolver.py index c84166d9e6..5617239fcd 100644 --- a/selfdrive/controls/lib/sunnypilot/speed_limit_resolver.py +++ b/selfdrive/controls/lib/sunnypilot/speed_limit_resolver.py @@ -15,8 +15,8 @@ class SpeedLimitResolver: self._policy = policy self._policy_to_sources_map = { Policy.car_state_only: [Source.car_state], - Policy.car_state_priority: [Source.car_state, Source.map_data], - Policy.map_data_priority: [Source.map_data, Source.car_state], + Policy.car_state_priority: [Source.car_state, Source.nav, Source.map_data], + Policy.map_data_priority: [Source.map_data, Source.nav, Source.car_state], Policy.nav_priority: [Source.nav, Source.map_data, Source.car_state], Policy.map_data_only: [Source.map_data], Policy.combined: [Source.car_state, Source.nav, Source.map_data], @@ -86,7 +86,7 @@ class SpeedLimitResolver: self._limit_solutions[Source.map_data] = speed_limit self._distance_solutions[Source.map_data] = 0. - if next_speed_limit > 0. and next_speed_limit < self._v_ego: + if 0. < next_speed_limit < self._v_ego: adapt_time = (next_speed_limit - self._v_ego) / LIMIT_ADAPT_ACC adapt_distance = self._v_ego * adapt_time + 0.5 * LIMIT_ADAPT_ACC * adapt_time**2 @@ -95,57 +95,28 @@ class SpeedLimitResolver: self._distance_solutions[Source.map_data] = distance_to_speed_limit_ahead def _consolidate(self): - solutions = self._get_solutions_according_to_policy() - limits = solutions['limits'] - distances = solutions['distances'] - sources = solutions['sources'] - extra_sources = [] - - # Get all non-zero values and set the minimum if any, otherwise 0. - mask = limits > 0. - limits = limits[mask] - distances = distances[mask] - sources = sources[mask] - - if len(limits) > 0: - min_idx = np.argmin(limits) - self.speed_limit = limits[min_idx] - self.distance = distances[min_idx] - self.source = Source(sources[min_idx]) - elif self._policy in [Policy.car_state_priority, Policy.nav_priority, Policy.map_data_priority]: - # If policy is car_state_priority -> append map data - if self._policy == Policy.car_state_priority: - extra_sources = [Source.map_data] - # If policy is map_data_priority --> append car state - elif self._policy == Policy.map_data_priority: - extra_sources = [Source.car_state] - # If policy is nav_priority -> append map data then car state - elif self._policy == Policy.nav_priority: - extra_sources = [Source.map_data, Source.car_state] - - for src in extra_sources: - if self._limit_solutions[src] > 0: - # If the speed limit from the source is above 0, then choose this source - self.speed_limit = self._limit_solutions[src] - self.distance = self._distance_solutions[src] - self.source = src - break - else: - self.speed_limit = 0. - self.distance = 0. - self.source = Source.none - - else: - self.speed_limit = 0. - self.distance = 0. - self.source = Source.none + source = self._get_source_solution_according_to_policy() + self.speed_limit = self._limit_solutions[source] if source else 0. + self.distance = self._distance_solutions[source] if source else 0. + self.source = source or Source.none debug(f'SL: *** Speed Limit set: {self.speed_limit}, distance: {self.distance}, source: {self.source}') return self.speed_limit, self.distance, self.source - def _get_solutions_according_to_policy(self): - source_codes = self._policy_to_sources_map[self._policy] - limits = np.array([self._limit_solutions[source] for source in source_codes], dtype=float) - distances = np.array([self._distance_solutions[source] for source in source_codes], dtype=float) - sources = np.array([source.value for source in source_codes], dtype=int) - return {'limits': limits, 'distances': distances, 'sources': sources} + def _get_source_solution_according_to_policy(self) -> Source | None: + sources_for_policy = self._policy_to_sources_map[self._policy] + + if self._policy != Policy.combined: + # They are ordered in the order of preference, so we pick the first that's non zero + for source in sources_for_policy: + if self._limit_solutions[source] > 0.: + return source + + limits = np.array([self._limit_solutions[source] for source in sources_for_policy], dtype=float) + sources = np.array([source.value for source in sources_for_policy], dtype=int) + + if len(limits) > 0: + min_idx = np.argmin(limits) + return sources[min_idx] + + return None diff --git a/selfdrive/controls/lib/sunnypilot/tests/test_speed_limit_resolver.py b/selfdrive/controls/lib/sunnypilot/tests/test_speed_limit_resolver.py new file mode 100644 index 0000000000..2903f98c23 --- /dev/null +++ b/selfdrive/controls/lib/sunnypilot/tests/test_speed_limit_resolver.py @@ -0,0 +1,132 @@ +import random +import time + +import pytest +from unittest.mock import MagicMock + +from openpilot.selfdrive.controls.lib.drive_helpers import LIMIT_MAX_MAP_DATA_AGE + +# from selfdrive.controls.lib.speed_limit_controller_tbd import SpeedLimitResolver as OriginalSpeedLimitResolver +from selfdrive.controls.lib.sunnypilot.speed_limit_resolver import SpeedLimitResolver as RefactoredSpeedLimitResolver +from openpilot.selfdrive.controls.lib.sunnypilot.common import Source +from openpilot.selfdrive.controls.lib.sunnypilot.common import Policy + + +def create_mock(properties): + mock = MagicMock() + for property, value in properties.items(): + setattr(mock, property, value) + return mock + + +def setup_sm_mock(): + cruise_speed_limit = random.uniform(0, 120) + nav_instruction_limit = random.uniform(0, 120) + live_map_data_limit = random.uniform(0, 120) + + cruise_state = create_mock({'speedLimit': cruise_speed_limit}) + car_state = create_mock({ + 'cruiseState': cruise_state, + 'gasPressed': False, + 'brakePressed': False, + 'standstill': False, + }) + nav_instruction = create_mock({'speedLimit': nav_instruction_limit}) + live_map_data = create_mock({ + 'speedLimit': live_map_data_limit, + 'speedLimitValid': True, + 'speedLimitAhead': 0., + 'speedLimitAheadValid': 0., + 'speedLimitAheadDistance': 0., + 'lastGpsTimestamp': time.time() * 1e3, + }) + sm_mock = MagicMock() + sm_mock.__getitem__.side_effect = lambda key: { + 'carState': car_state, + 'navInstruction': nav_instruction, + 'liveMapDataSP': live_map_data, + }[key] + return sm_mock + + +parametrized_policies = pytest.mark.parametrize( + "policy, sm_key, function_key", [ + (Policy.car_state_only, 'carState', 'car_state'), + (Policy.car_state_priority, 'carState', 'car_state'), + (Policy.map_data_only, 'liveMapDataSP', 'map_data'), + (Policy.map_data_priority, 'liveMapDataSP', 'map_data'), + (Policy.nav_only, 'navInstruction', 'nav'), + (Policy.nav_priority, 'navInstruction', 'nav'), + ], + ids=lambda val: val.name if hasattr(val, 'name') else str(val) +) + + +@pytest.mark.parametrize("resolver_class", [RefactoredSpeedLimitResolver], ids=["Refactored"]) +class TestSpeedLimitResolverValidation: + + @pytest.mark.parametrize("policy", list(Policy), ids=lambda policy: policy.name) + def test_initial_state(self, resolver_class, policy): + resolver = resolver_class(policy) + for source in Source: + if source in resolver._limit_solutions: + assert resolver._limit_solutions[source] == 0. + assert resolver._distance_solutions[source] == 0. + + @parametrized_policies + def test_resolver(self, resolver_class, policy, sm_key, function_key): + resolver = resolver_class(policy) + sm_mock = setup_sm_mock() + source_speed_limit = sm_mock[sm_key].cruiseState.speedLimit if sm_key == 'carState' else sm_mock[sm_key].speedLimit + + # Assert the resolver + speed_limit, _, source = resolver.resolve(source_speed_limit, 0, sm_mock) + assert speed_limit == source_speed_limit + assert source == Source[function_key] + + def test_resolver_combined(self, resolver_class): + resolver = resolver_class(Policy.combined) + sm_mock = setup_sm_mock() + socket_to_source = {'carState': Source.car_state, 'liveMapDataSP': Source.map_data, 'navInstruction': Source.nav} + minimum_key, minimum_speed_limit = min( + ((key, sm_mock[key].cruiseState.speedLimit) if key == 'carState' else (key, sm_mock[key].speedLimit) for key in + socket_to_source.keys()), key=lambda x: x[1]) + + # Assert the resolver + speed_limit, _, source = resolver.resolve(minimum_speed_limit, 0, sm_mock) + assert speed_limit == minimum_speed_limit + assert source == socket_to_source[minimum_key] + + @parametrized_policies + def test_parser(self, resolver_class, policy, sm_key, function_key): + resolver = resolver_class(policy) + sm_mock = setup_sm_mock() + source_speed_limit = sm_mock[sm_key].cruiseState.speedLimit if sm_key == 'carState' else sm_mock[sm_key].speedLimit + + # Assert the parsing + speed_limit, _, source = resolver.resolve(source_speed_limit, 0, sm_mock) + assert resolver._limit_solutions[Source[function_key]] == source_speed_limit + assert resolver._distance_solutions[Source[function_key]] == 0. + + @pytest.mark.parametrize("policy", list(Policy), ids=lambda policy: policy.name) + def test_resolve_interaction_in_update(self, resolver_class, policy): + v_ego = 50 + resolver = resolver_class(policy) + + sm_mock = setup_sm_mock() + _speed_limit, _distance, _source = resolver.resolve(v_ego, 0, sm_mock) + + # After resolution + assert _speed_limit is not None + assert _distance is not None + assert _source is not None + + @pytest.mark.parametrize("policy", list(Policy), ids=lambda policy: policy.name) + def test_old_map_data_ignored(self, resolver_class, policy): + resolver = resolver_class(policy) + sm_mock = MagicMock() + sm_mock['liveMapDataSP'].lastGpsTimestamp = (time.time() - 2 * LIMIT_MAX_MAP_DATA_AGE) * 1e3 + resolver._sm = sm_mock + resolver._get_from_map_data() + assert resolver._limit_solutions[Source.map_data] == 0. + assert resolver._distance_solutions[Source.map_data] == 0. diff --git a/selfdrive/controls/lib/turn_speed_controller.py b/selfdrive/controls/lib/turn_speed_controller.py index 5612b8720b..9fc35913b1 100644 --- a/selfdrive/controls/lib/turn_speed_controller.py +++ b/selfdrive/controls/lib/turn_speed_controller.py @@ -4,7 +4,7 @@ from common.params import Params from cereal import custom from selfdrive.controls.lib.drive_helpers import LIMIT_ADAPT_ACC, LIMIT_MIN_SPEED, LIMIT_MAX_MAP_DATA_AGE, \ LIMIT_SPEED_OFFSET_TH, CONTROL_N, LIMIT_MIN_ACC, LIMIT_MAX_ACC -from selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.modeld.constants import ModelConstants _ACTIVE_LIMIT_MIN_ACC = -0.5 # m/s^2 Maximum deceleration allowed while active. @@ -223,7 +223,7 @@ class TurnSpeedController(): elif self.state == TurnSpeedControlState.active: # When active we are trying to keep the speed constant around the control time horizon. # but under constrained acceleration limits since we are in a turn. - a_target = self._v_offset / T_IDXS[CONTROL_N] + a_target = self._v_offset / ModelConstants.T_IDXS[CONTROL_N] a_target = np.clip(a_target, _ACTIVE_LIMIT_MIN_ACC, _ACTIVE_LIMIT_MAX_ACC) # update solution values. diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index eaafba2206..6ee9eab328 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -5,7 +5,7 @@ from cereal import car from openpilot.common.params import Params from openpilot.common.realtime import Priority, config_realtime_process from openpilot.system.swaglog import cloudlog -from openpilot.selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from openpilot.selfdrive.controls.lib.lateral_planner import LateralPlanner import cereal.messaging as messaging @@ -14,8 +14,8 @@ def cumtrapz(x, t): return np.concatenate([[0], np.cumsum(((x[0:-1] + x[1:])/2) * np.diff(t))]) def publish_ui_plan(sm, pm, lateral_planner, longitudinal_planner): - plan_odo = cumtrapz(longitudinal_planner.v_desired_trajectory_full, T_IDXS) - model_odo = cumtrapz(lateral_planner.v_plan, T_IDXS) + plan_odo = cumtrapz(longitudinal_planner.v_desired_trajectory_full, ModelConstants.T_IDXS) + model_odo = cumtrapz(lateral_planner.v_plan, ModelConstants.T_IDXS) ui_send = messaging.new_message('uiPlan') ui_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'modelV2']) diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py deleted file mode 100755 index 10c0e4a7b0..0000000000 --- a/selfdrive/locationd/laikad.py +++ /dev/null @@ -1,468 +0,0 @@ -#!/usr/bin/env python3 -import math -import os -import time -import shutil -from collections import defaultdict -from concurrent.futures import Future, ProcessPoolExecutor -from enum import IntEnum -from typing import List, Optional, Dict, Any - -import numpy as np - -from cereal import log, messaging -from openpilot.common.params import Params, put_nonblocking -from laika import AstroDog -from laika.constants import SECS_IN_HR, SECS_IN_MIN -from laika.downloader import DownloadFailed -from laika.ephemeris import EphemerisType, GPSEphemeris, GLONASSEphemeris, ephemeris_structs, parse_qcom_ephem -from laika.gps_time import GPSTime -from laika.helpers import ConstellationId, get_sv_id -from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox -from laika.raw_gnss import gps_time_from_qcom_report, get_measurements_from_qcom_reports -from laika.opt import calc_pos_fix, get_posfix_sympy_fun, calc_vel_fix, get_velfix_sympy_func -from openpilot.selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind -from openpilot.selfdrive.locationd.models.gnss_kf import GNSSKalman -from openpilot.selfdrive.locationd.models.gnss_kf import States as GStates -from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import cloudlog - -MAX_TIME_GAP = 10 -EPHEMERIS_CACHE = 'LaikadEphemerisV3' -CACHE_VERSION = 0.2 -POS_FIX_RESIDUAL_THRESHOLD = 100.0 - - -class LogEphemerisType(IntEnum): - nav = 0 - nasaUltraRapid = 1 - glonassIacUltraRapid = 2 - qcom = 3 - -class EphemerisSource(IntEnum): - gnssChip = 0 - internet = 1 - cache = 2 - unknown = 3 - -def get_log_eph_type(ephem): - if ephem.eph_type == EphemerisType.NAV: - source_type = LogEphemerisType.nav - elif ephem.eph_type == EphemerisType.QCOM_POLY: - source_type = LogEphemerisType.qcom - else: - assert ephem.file_epoch is not None - file_src = ephem.file_source - if file_src == 'igu': # example nasa: '2214/igu22144_00.sp3.Z' - source_type = LogEphemerisType.nasaUltraRapid - elif file_src == 'Sta': # example nasa: '22166/ultra/Stark_1D_22061518.sp3' - source_type = LogEphemerisType.glonassIacUltraRapid - else: - raise Exception(f"Didn't expect file source {file_src}") - return source_type - -def get_log_eph_source(ephem): - if ephem.file_name == 'qcom' or ephem.file_name == 'ublox': - source = EphemerisSource.gnssChip - elif ephem.file_name == EPHEMERIS_CACHE: - source = EphemerisSource.cache - else: - source = EphemerisSource.internet - return source - - -class Laikad: - def __init__(self, valid_const=(ConstellationId.GPS, ConstellationId.GLONASS), auto_fetch_navs=True, auto_update=False, - valid_ephem_types=(EphemerisType.NAV, EphemerisType.QCOM_POLY), - save_ephemeris=False, use_qcom=False): - """ - valid_const: GNSS constellation which can be used - auto_fetch_navs: If true fetch navs from internet when needed - auto_update: If true download AstroDog will download all files needed. This can be ephemeris or correction data like ionosphere. - valid_ephem_types: Valid ephemeris types to be used by AstroDog - save_ephemeris: If true saves and loads nav and orbit ephemeris to cache. - """ - self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, - clear_old_ephemeris=True, cache_dir=Paths.download_cache_root()) - self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True, erratic_clock=use_qcom) - - self.auto_fetch_navs = auto_fetch_navs - self.orbit_fetch_executor: Optional[ProcessPoolExecutor] = None - self.orbit_fetch_future: Optional[Future] = None - - self.last_report_time = GPSTime(0, 0) - self.last_fetch_navs_t = GPSTime(0, 0) - self.last_cached_t = GPSTime(0, 0) - self.save_ephemeris = save_ephemeris - self.load_cache() - - self.posfix_functions = {constellation: get_posfix_sympy_fun(constellation) for constellation in (ConstellationId.GPS, ConstellationId.GLONASS)} - self.velfix_function = get_velfix_sympy_func() - self.last_fix_pos = None - self.last_fix_t = None - self.use_qcom = use_qcom - self.first_log_time = None - self.ttff = -1 - self.measurement_lag = 0.630 if self.use_qcom else 0.095 - - # qcom specific stuff - self.qcom_reports_received = 4 - self.qcom_reports = [] - - def load_cache(self): - if not self.save_ephemeris: - return - - cache_bytes = Params().get(EPHEMERIS_CACHE) - if not cache_bytes: - return - - nav_dict = {} - try: - with ephemeris_structs.EphemerisCache.from_bytes(cache_bytes) as ephem_cache: - glonass_navs = [GLONASSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.glonassEphemerides] - gps_navs = [GPSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.gpsEphemerides] - for e in sum([glonass_navs, gps_navs], []): - if e.prn not in nav_dict: - nav_dict[e.prn] = [] - nav_dict[e.prn].append(e) - self.astro_dog.add_navs(nav_dict) - except Exception: - cloudlog.exception("Error parsing cache") - cloudlog.debug( - f"Loaded navs ({sum([len(nav_dict[prn]) for prn in nav_dict.keys()])}). Unique orbit and nav sats: {list(nav_dict.keys())} ") - - def cache_ephemeris(self): - - if self.save_ephemeris and (self.last_report_time - self.last_cached_t > SECS_IN_MIN): - nav_list: List = sum([v for k,v in self.astro_dog.navs.items()], []) - #TODO this only saves currently valid ephems, when we download future ephems we should save them too - valid_navs = [e for e in nav_list if e.valid(self.last_report_time)] - if len(valid_navs) > 0: - ephem_cache = ephemeris_structs.EphemerisCache(glonassEphemerides=[e.data for e in valid_navs if e.prn[0]=='R'], - gpsEphemerides=[e.data for e in valid_navs if e.prn[0]=='G']) - put_nonblocking(EPHEMERIS_CACHE, ephem_cache.to_bytes()) - cloudlog.debug("Cache saved") - self.last_cached_t = self.last_report_time - - def create_ephem_statuses(self): - ephemeris_statuses = [] - eph_list: List = sum([v for k,v in self.astro_dog.navs.items()], []) + sum([v for k,v in self.astro_dog.qcom_polys.items()], []) - for eph in eph_list: - status = log.GnssMeasurements.EphemerisStatus.new_message() - status.constellationId = ConstellationId.from_rinex_char(eph.prn[0]).value - status.svId = get_sv_id(eph.prn) - status.type = get_log_eph_type(eph).value - status.source = get_log_eph_source(eph).value - status.tow = eph.epoch.tow - status.gpsWeek = eph.epoch.week - ephemeris_statuses.append(status) - return ephemeris_statuses - - - def get_lsq_fix(self, t, measurements): - if self.last_fix_t is None or abs(self.last_fix_t - t) > 0: - min_measurements = 5 if any(p.constellation_id == ConstellationId.GLONASS for p in measurements) else 4 - - position_solution, pr_residuals, pos_std = calc_pos_fix(measurements, self.posfix_functions, min_measurements=min_measurements) - if len(position_solution) < 3: - return None - position_estimate = position_solution[:3] - position_std = pos_std[:3] - - velocity_solution, prr_residuals, vel_std = calc_vel_fix(measurements, position_estimate, self.velfix_function, min_measurements=min_measurements) - if len(velocity_solution) < 3: - return None - velocity_estimate = velocity_solution[:3] - velocity_std = vel_std[:3] - - return position_estimate, position_std, velocity_estimate, velocity_std - - def is_good_report(self, gnss_msg): - if gnss_msg.which() in ['drMeasurementReport', 'measurementReport'] and self.use_qcom: - # TODO: Understand and use remaining unknown constellations - try: - if gnss_msg.which() == 'drMeasurementReport': - constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source) - else: - constellation_id = ConstellationId.from_qcom_source(gnss_msg.measurementReport.source) - good_constellation = constellation_id in [ConstellationId.GPS, ConstellationId.SBAS, ConstellationId.GLONASS] - report_time = gps_time_from_qcom_report(gnss_msg) - except NotImplementedError: - return False - # Garbage timestamps with week > 32767 are sometimes sent by module. - # This is an issue with gpsTime and GLONASS time. - good_week = report_time.week < np.iinfo(np.int16).max - return good_constellation and good_week - elif gnss_msg.which() == 'measurementReport' and not self.use_qcom: - return True - else: - return False - - def read_report(self, gnss_msg): - if self.use_qcom: - # QCOM reports are per constellation, so we need to aggregate them - # Additionally, the pseudoranges are broken in the measurementReports - # and the doppler filteredSpeed is broken in the drMeasurementReports - report_time = gps_time_from_qcom_report(gnss_msg) - if report_time - self.last_report_time == 0: - self.qcom_reports.append(gnss_msg) - self.last_report_time = report_time - elif report_time - self.last_report_time > 0: - self.qcom_reports_received = max(1, len(self.qcom_reports)) - self.qcom_reports = [gnss_msg] - self.last_report_time = report_time - else: - # Sometimes DR reports get sent one iteration late (1second), they need to be ignored - cloudlog.warning(f"Received report with time {report_time} before last report time {self.last_report_time}") - - if len(self.qcom_reports) == self.qcom_reports_received: - new_meas = get_measurements_from_qcom_reports(self.qcom_reports) - else: - new_meas = [] - else: - report = gnss_msg.measurementReport - self.last_report_time = GPSTime(report.gpsWeek, report.rcvTow) - new_meas = read_raw_ublox(report) - return self.last_report_time, new_meas - - def is_ephemeris(self, gnss_msg): - if self.use_qcom: - return gnss_msg.which() == 'drSvPoly' - else: - return gnss_msg.which() in ('ephemeris', 'glonassEphemeris') - - def read_ephemeris(self, gnss_msg): - if self.use_qcom: - try: - ephem = parse_qcom_ephem(gnss_msg.drSvPoly) - self.astro_dog.add_qcom_polys({ephem.prn: [ephem]}) - except Exception: - cloudlog.exception("Error parsing qcom svPoly ephemeris from qcom module") - return - - else: - if gnss_msg.which() == 'ephemeris': - data_struct = ephemeris_structs.Ephemeris.new_message(**gnss_msg.ephemeris.to_dict()) - try: - ephem = GPSEphemeris(data_struct, file_name='ublox') - except Exception: - cloudlog.exception("Error parsing GPS ephemeris from ublox") - return - elif gnss_msg.which() == 'glonassEphemeris': - data_struct = ephemeris_structs.GlonassEphemeris.new_message(**gnss_msg.glonassEphemeris.to_dict()) - try: - ephem = GLONASSEphemeris(data_struct, file_name='ublox') - except Exception: - cloudlog.exception("Error parsing GLONASS ephemeris from ublox") - return - else: - cloudlog.error(f"Unsupported ephemeris type: {gnss_msg.which()}") - return - self.astro_dog.add_navs({ephem.prn: [ephem]}) - self.cache_ephemeris() - - def process_report(self, new_meas, t): - # Filter measurements with unexpected pseudoranges for GPS and GLONASS satellites - new_meas = [m for m in new_meas if 1e7 < m.observables['C1C'] < 3e7] - processed_measurements = process_measurements(new_meas, self.astro_dog) - if self.last_fix_pos is not None: - est_pos = self.last_fix_pos - correct_delay = True - else: - est_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist() - correct_delay = False - corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog, correct_delay=correct_delay) - # If many measurements weren't corrected, position may be garbage, so reset - if len(processed_measurements) >= 8 and len(corrected_measurements) < 5: - cloudlog.error("Didn't correct enough measurements, resetting estimate position") - self.last_fix_pos = None - self.last_fix_t = None - return corrected_measurements - - def calc_fix(self, t, measurements): - instant_fix = self.get_lsq_fix(t, measurements) - if instant_fix is None: - return None - else: - position_estimate, position_std, velocity_estimate, velocity_std = instant_fix - self.last_fix_t = t - self.last_fix_pos = position_estimate - self.lat_fix_pos_std = position_std - return position_estimate, position_std, velocity_estimate, velocity_std - - def process_gnss_msg(self, gnss_msg, gnss_mono_time: int, block=False): - out_msg = messaging.new_message("gnssMeasurements") - t = gnss_mono_time * 1e-9 - msg_dict: Dict[str, Any] = {"measTime": gnss_mono_time - int(1e9 * self.measurement_lag)} - if self.first_log_time is None: - self.first_log_time = 1e-9 * gnss_mono_time - if self.is_ephemeris(gnss_msg): - self.read_ephemeris(gnss_msg) - elif self.is_good_report(gnss_msg): - report_t, new_meas = self.read_report(gnss_msg) - if report_t.week > 0: - if self.auto_fetch_navs: - self.fetch_navs(report_t, block) - - corrected_measurements = self.process_report(new_meas, t) - msg_dict['correctedMeasurements'] = [create_measurement_msg(m) for m in corrected_measurements] - - fix = self.calc_fix(t, corrected_measurements) - measurement_msg = log.LiveLocationKalman.Measurement.new_message - if fix is not None: - position_estimate, position_std, velocity_estimate, velocity_std = fix - if self.ttff <= 0: - self.ttff = max(1e-3, t - self.first_log_time) - msg_dict["positionECEF"] = measurement_msg(value=position_estimate, std=position_std.tolist(), valid=bool(self.last_fix_t == t)) - msg_dict["velocityECEF"] = measurement_msg(value=velocity_estimate, std=velocity_std.tolist(), valid=bool(self.last_fix_t == t)) - - self.update_localizer(self.last_fix_pos, t, corrected_measurements) - P_diag = self.gnss_kf.P.diagonal() - kf_valid = all(self.kf_valid(t)) - msg_dict["kalmanPositionECEF"] = measurement_msg(value=self.gnss_kf.x[GStates.ECEF_POS].tolist(), - std=np.sqrt(P_diag[GStates.ECEF_POS]).tolist(), - valid=kf_valid) - msg_dict["kalmanVelocityECEF"] = measurement_msg(value=self.gnss_kf.x[GStates.ECEF_VELOCITY].tolist(), - std=np.sqrt(P_diag[GStates.ECEF_VELOCITY]).tolist(), - valid=kf_valid) - - msg_dict['gpsWeek'] = self.last_report_time.week - msg_dict['gpsTimeOfWeek'] = self.last_report_time.tow - msg_dict['timeToFirstFix'] = self.ttff - msg_dict['ephemerisStatuses'] = self.create_ephem_statuses() - out_msg.gnssMeasurements = msg_dict - return out_msg - - def update_localizer(self, est_pos, t: float, measurements: List[GNSSMeasurement]): - # Check time and outputs are valid - valid = self.kf_valid(t) - if not all(valid): - if not valid[0]: # Filter not initialized - pass - elif not valid[1]: - cloudlog.error("Time gap of over 10s detected, gnss kalman reset") - elif not valid[2]: - cloudlog.error("Gnss kalman filter state is nan") - if est_pos is not None and len(est_pos) > 0: - cloudlog.info(f"Reset kalman filter with {est_pos}") - self.init_gnss_localizer(est_pos) - else: - return - if len(measurements) > 0: - kf_add_observations(self.gnss_kf, t, measurements) - else: - # Ensure gnss filter is updated even with no new measurements - self.gnss_kf.predict(t) - - def kf_valid(self, t: float) -> List[bool]: - filter_time = self.gnss_kf.filter.get_filter_time() - return [not math.isnan(filter_time), - abs(t - filter_time) < MAX_TIME_GAP, - all(np.isfinite(self.gnss_kf.x[GStates.ECEF_POS]))] - - def init_gnss_localizer(self, est_pos): - x_initial, p_initial_diag = np.copy(GNSSKalman.x_initial), np.copy(np.diagonal(GNSSKalman.P_initial)) - x_initial[GStates.ECEF_POS] = est_pos - p_initial_diag[GStates.ECEF_POS] = 1000 ** 2 - self.gnss_kf.init_state(x_initial, covs_diag=p_initial_diag) - - def fetch_navs(self, t: GPSTime, block): - # Download new navs if 1 hour of navs data left - if t + SECS_IN_HR not in self.astro_dog.navs_fetched_times and (abs(t - self.last_fetch_navs_t) > SECS_IN_MIN): - astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types, self.astro_dog.cache_dir - ret = None - - if block: # Used for testing purposes - ret = get_orbit_data(t, *astro_dog_vars) - elif self.orbit_fetch_future is None: - self.orbit_fetch_executor = ProcessPoolExecutor(max_workers=1) - self.orbit_fetch_future = self.orbit_fetch_executor.submit(get_orbit_data, t, *astro_dog_vars) - elif self.orbit_fetch_future.done(): - ret = self.orbit_fetch_future.result() - self.orbit_fetch_executor = self.orbit_fetch_future = None - - if ret is not None: - if ret[0] is None: - self.last_fetch_navs_t = ret[2] - else: - self.astro_dog.navs, self.astro_dog.navs_fetched_times, self.last_fetch_navs_t = ret - self.cache_ephemeris() - - -def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types, cache_dir): - astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, cache_dir=cache_dir) - cloudlog.info(f"Start to download/parse navs for time {t.as_datetime()}") - start_time = time.monotonic() - try: - astro_dog.get_navs(t) - cloudlog.info(f"Done parsing navs. Took {time.monotonic() - start_time:.1f}s") - cloudlog.debug(f"Downloaded navs ({sum([len(v) for v in astro_dog.navs])}): {list(astro_dog.navs.keys())}" + - f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in astro_dog.orbit_fetched_times._ranges]}") - return astro_dog.navs, astro_dog.navs_fetched_times, t - except (DownloadFailed, RuntimeError, ValueError, IOError) as e: - cloudlog.warning(f"No orbit data found or parsing failure: {e}") - return None, None, t - - -def create_measurement_msg(meas: GNSSMeasurement): - c = log.GnssMeasurements.CorrectedMeasurement.new_message() - c.constellationId = meas.constellation_id.value - c.svId = meas.sv_id - c.glonassFrequency = meas.glonass_freq if meas.constellation_id == ConstellationId.GLONASS else 0 - c.pseudorange = float(meas.observables_final['C1C']) - c.pseudorangeStd = float(meas.observables_std['C1C']) - c.pseudorangeRate = float(meas.observables_final['D1C']) - c.pseudorangeRateStd = float(meas.observables_std['D1C']) - c.satPos = meas.sat_pos_final.tolist() - c.satVel = meas.sat_vel.tolist() - c.satVel = meas.sat_vel.tolist() - return c - -def kf_add_observations(gnss_kf: GNSSKalman, t: float, measurements: List[GNSSMeasurement]): - ekf_data = defaultdict(list) - for m in measurements: - m_arr = m.as_array() - if m.constellation_id == ConstellationId.GPS: - ekf_data[ObservationKind.PSEUDORANGE_GPS].append(m_arr) - elif m.constellation_id == ConstellationId.GLONASS: - ekf_data[ObservationKind.PSEUDORANGE_GLONASS].append(m_arr) - ekf_data[ObservationKind.PSEUDORANGE_RATE_GPS] = ekf_data[ObservationKind.PSEUDORANGE_GPS] - ekf_data[ObservationKind.PSEUDORANGE_RATE_GLONASS] = ekf_data[ObservationKind.PSEUDORANGE_GLONASS] - for kind, data in ekf_data.items(): - if len(data) > 0: - gnss_kf.predict_and_observe(t, kind, data) - - -def clear_tmp_cache(): - if os.path.exists(Paths.download_cache_root()): - shutil.rmtree(Paths.download_cache_root()) - os.mkdir(Paths.download_cache_root()) - - -def main(): - #clear_tmp_cache() - - use_qcom = not Params().get_bool("UbloxAvailable") - if use_qcom: - raw_name = "qcomGnss" - else: - raw_name = "ubloxGnss" - raw_gnss_sock = messaging.sub_sock(raw_name, conflate=False) - pm = messaging.PubMaster(['gnssMeasurements']) - - # disable until set as main gps source, to better analyze startup time - # TODO ensure low CPU usage before enabling - use_internet = False # "LAIKAD_NO_INTERNET" not in os.environ - - replay = "REPLAY" in os.environ - laikad = Laikad(save_ephemeris=not replay, auto_fetch_navs=use_internet, use_qcom=use_qcom) - - while True: - for in_msg in messaging.drain_sock(raw_gnss_sock, wait_for_one=True): - out_msg = laikad.process_gnss_msg(getattr(in_msg, raw_name), in_msg.logMonoTime, replay) - pm.send('gnssMeasurements', out_msg) - - -if __name__ == "__main__": - main() diff --git a/selfdrive/locationd/test/test_laikad.py b/selfdrive/locationd/test/test_laikad.py deleted file mode 100755 index db8802ed36..0000000000 --- a/selfdrive/locationd/test/test_laikad.py +++ /dev/null @@ -1,319 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import time -import unittest -from cereal import log -from openpilot.common.params import Params -from datetime import datetime -from unittest import mock - - -from laika.constants import SECS_IN_DAY -from laika.downloader import DownloadFailed -from laika.ephemeris import EphemerisType -from laika.gps_time import GPSTime -from laika.helpers import ConstellationId -from laika.raw_gnss import GNSSMeasurement, read_raw_ublox, read_raw_qcom -from openpilot.selfdrive.locationd.laikad import EPHEMERIS_CACHE, Laikad -from openpilot.selfdrive.test.openpilotci import get_url -from openpilot.tools.lib.logreader import LogReader - -from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process - -GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC = GPSTime.from_datetime(datetime(2022, month=1, day=29, hour=12)) -UBLOX_TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" -QCOM_TEST_ROUTE = "616dc83ca1f7f11e|2023-07-11--10-52-31" - - -def get_log_ublox(): - logs = LogReader(get_url(UBLOX_TEST_ROUTE, 0)) - - ublox_cfg = get_process_config("ubloxd") - all_logs = replay_process(ublox_cfg, logs) - low_gnss = [] - for m in all_logs: - if m.which() != "ubloxGnss" or m.ubloxGnss.which() != 'measurementReport': - continue - - MAX_MEAS = 7 - if m.ubloxGnss.measurementReport.numMeas > MAX_MEAS: - mb = log.Event.new_message(ubloxGnss=m.ubloxGnss.to_dict()) - mb.logMonoTime = m.logMonoTime - mb.ubloxGnss.measurementReport.numMeas = MAX_MEAS - mb.ubloxGnss.measurementReport.measurements = list(m.ubloxGnss.measurementReport.measurements)[:MAX_MEAS] - mb.ubloxGnss.measurementReport.measurements[0].pseudorange += 1000 - low_gnss.append(mb.as_reader()) - else: - low_gnss.append(m) - return all_logs, low_gnss - - -def get_log_qcom(): - logs = LogReader(get_url(QCOM_TEST_ROUTE, 0)) - all_logs = [m for m in logs if m.which() == 'qcomGnss'] - return all_logs - - -def verify_messages(lr, laikad, return_one_success=False): - good_msgs = [] - for m in lr: - if m.which() == 'ubloxGnss': - gnss_msg = m.ubloxGnss - elif m.which() == 'qcomGnss': - gnss_msg = m.qcomGnss - else: - continue - msg = laikad.process_gnss_msg(gnss_msg, m.logMonoTime, block=True) - if msg is not None and len(msg.gnssMeasurements.correctedMeasurements) > 0: - good_msgs.append(msg) - if return_one_success: - return msg - return good_msgs - - -def get_first_gps_time(logs): - for m in logs: - if m.which() == 'ubloxGnss': - if m.ubloxGnss.which == 'measurementReport': - new_meas = read_raw_ublox(m.ubloxGnss.measurementReport) - if len(new_meas) > 0: - return new_meas[0].recv_time - elif m.which() == "qcomGnss": - if m.qcomGnss.which == 'measurementReport': - new_meas = read_raw_qcom(m.qcomGnss.measurementReport) - if len(new_meas) > 0: - return new_meas[0].recv_time - - -def get_measurement_mock(gpstime, sat_ephemeris): - meas = GNSSMeasurement(ConstellationId.GPS, 1, gpstime.week, gpstime.tow, {'C1C': 0., 'D1C': 0.}, {'C1C': 0., 'D1C': 0.}) - # Fake measurement being processed - meas.observables_final = meas.observables - meas.sat_ephemeris = sat_ephemeris - return meas - - -@pytest.mark.slow -class TestLaikad(unittest.TestCase): - - @classmethod - def setUpClass(cls): - logs, low_gnss = get_log_ublox() - cls.logs = logs - cls.low_gnss = low_gnss - cls.logs_qcom = get_log_qcom() - first_gps_time = get_first_gps_time(logs) - cls.first_gps_time = first_gps_time - - def setUp(self): - Params().remove(EPHEMERIS_CACHE) - - def test_fetch_navs_non_blocking(self): - gpstime = GPSTime.from_datetime(datetime(2021, month=3, day=1)) - laikad = Laikad() - laikad.fetch_navs(gpstime, block=False) - laikad.orbit_fetch_future.result(30) - - # Get results and save orbits to laikad: - laikad.fetch_navs(gpstime, block=False) - ephem = laikad.astro_dog.navs['G01'][0] - self.assertIsNotNone(ephem) - - laikad.fetch_navs(gpstime+2*SECS_IN_DAY, block=False) - laikad.orbit_fetch_future.result(30) - # Get results and save orbits to laikad: - laikad.fetch_navs(gpstime + 2 * SECS_IN_DAY, block=False) - - ephem2 = laikad.astro_dog.navs['G01'][0] - self.assertIsNotNone(ephem) - self.assertNotEqual(ephem, ephem2) - - - def test_fetch_navs_with_wrong_clocks(self): - laikad = Laikad() - - def check_has_navs(): - self.assertGreater(len(laikad.astro_dog.navs), 0) - ephem = laikad.astro_dog.navs['G01'][0] - self.assertIsNotNone(ephem) - real_current_time = GPSTime.from_datetime(datetime(2021, month=3, day=1)) - wrong_future_clock_time = real_current_time + SECS_IN_DAY - - laikad.fetch_navs(wrong_future_clock_time, block=True) - check_has_navs() - self.assertEqual(laikad.last_fetch_navs_t, wrong_future_clock_time) - - # Test fetching orbits with earlier time - assert real_current_time < laikad.last_fetch_navs_t - - laikad.astro_dog.orbits = {} - laikad.fetch_navs(real_current_time, block=True) - check_has_navs() - self.assertEqual(laikad.last_fetch_navs_t, real_current_time) - - def test_laika_online(self): - laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT) - correct_msgs = verify_messages(self.logs, laikad) - - correct_msgs_expected = 560 - self.assertEqual(correct_msgs_expected, len(correct_msgs)) - self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) - - def test_kf_becomes_valid(self): - laikad = Laikad(auto_update=False) - m = self.logs[0] - self.assertFalse(all(laikad.kf_valid(m.logMonoTime * 1e-9))) - kf_valid = False - for m in self.logs: - if m.which() != 'ubloxGnss': - continue - - laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=True) - kf_valid = all(laikad.kf_valid(m.logMonoTime * 1e-9)) - if kf_valid: - break - self.assertTrue(kf_valid) - - def test_laika_online_nav_only(self): - for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs], strict=True): - laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.NAV, use_qcom=use_qcom) - # Disable fetch_orbits to test NAV only - correct_msgs = verify_messages(logs, laikad) - correct_msgs_expected = 55 if use_qcom else 560 - valid_fix_expected = 55 if use_qcom else 560 - - self.assertEqual(correct_msgs_expected, len(correct_msgs)) - self.assertEqual(valid_fix_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) - - @mock.patch('laika.downloader.download_and_cache_file') - def test_laika_offline(self, downloader_mock): - downloader_mock.side_effect = DownloadFailed("Mock download failed") - laikad = Laikad(auto_update=False) - laikad.fetch_navs(GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC, block=True) - - @mock.patch('laika.downloader.download_and_cache_file') - def test_download_failed_russian_source(self, downloader_mock): - downloader_mock.side_effect = DownloadFailed - laikad = Laikad(auto_update=False) - correct_msgs = verify_messages(self.logs, laikad) - expected_msgs = 376 - self.assertEqual(expected_msgs, len(correct_msgs)) - self.assertEqual(expected_msgs, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) - - def test_laika_get_orbits(self): - laikad = Laikad(auto_update=False) - # Pretend process has loaded the orbits on startup by using the time of the first gps message. - laikad.fetch_navs(self.first_gps_time, block=True) - self.dict_has_values(laikad.astro_dog.navs) - - @unittest.skip("Use to debug live data") - def test_laika_get_navs_now(self): - laikad = Laikad(auto_update=False) - laikad.fetch_navs(GPSTime.from_datetime(datetime.utcnow()), block=True) - prn = "G01" - self.assertGreater(len(laikad.astro_dog.navs[prn]), 0) - prn = "R01" - self.assertGreater(len(laikad.astro_dog.navs[prn]), 0) - - def test_get_navs_in_process(self): - for auto_fetch_navs in [True, False]: - for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs], strict=True): - laikad = Laikad(auto_update=False, use_qcom=use_qcom, auto_fetch_navs=auto_fetch_navs) - has_navs = False - has_fix = False - seen_chip_eph = False - seen_internet_eph = False - - for m in logs: - if m.which() != 'ubloxGnss' and m.which() != 'qcomGnss': - continue - - gnss_msg = m.qcomGnss if use_qcom else m.ubloxGnss - out_msg = laikad.process_gnss_msg(gnss_msg, m.logMonoTime, block=False) - if laikad.orbit_fetch_future is not None: - laikad.orbit_fetch_future.result() - vals = laikad.astro_dog.navs.values() - has_navs = len(vals) > 0 and max([len(v) for v in vals]) > 0 - vals = laikad.astro_dog.qcom_polys.values() - has_polys = len(vals) > 0 and max([len(v) for v in vals]) > 0 - has_fix = has_fix or out_msg.gnssMeasurements.positionECEF.valid - if len(out_msg.gnssMeasurements.ephemerisStatuses): - seen_chip_eph = seen_chip_eph or any(x.source == 'gnssChip' for x in out_msg.gnssMeasurements.ephemerisStatuses) - seen_internet_eph = seen_internet_eph or any(x.source == 'internet' for x in out_msg.gnssMeasurements.ephemerisStatuses) - - self.assertTrue(has_navs or has_polys) - self.assertTrue(has_fix) - self.assertTrue(seen_chip_eph or auto_fetch_navs) - self.assertTrue(seen_internet_eph or not auto_fetch_navs) - self.assertEqual(len(laikad.astro_dog.navs_fetched_times._ranges), 0) - self.assertEqual(None, laikad.orbit_fetch_future) - - def test_cache(self): - use_qcom = True - for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs], strict=True): - Params().remove(EPHEMERIS_CACHE) - laikad = Laikad(auto_update=True, save_ephemeris=True, use_qcom=use_qcom) - def wait_for_cache(): - max_time = 2 - while Params().get(EPHEMERIS_CACHE) is None: - time.sleep(0.1) - max_time -= 0.1 - if max_time < 0: - self.fail("Cache has not been written after 2 seconds") - - # Test cache with no ephemeris - laikad.last_report_time = GPSTime(1,0) - laikad.cache_ephemeris() - if Params().get(EPHEMERIS_CACHE) is not None: - self.fail("Cache should not have been written without valid ephem") - - #laikad.astro_dog.get_navs(self.first_gps_time) - msg = verify_messages(logs, laikad, return_one_success=True) - laikad.cache_ephemeris() - wait_for_cache() - - # Check both nav and orbits separate - laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.NAV, - save_ephemeris=True, use_qcom=use_qcom, auto_fetch_navs=False) - # Verify navs are loaded from cache - self.dict_has_values(laikad.astro_dog.navs) - # Verify cache is working for only nav by running a segment - msg = verify_messages(logs, laikad, return_one_success=True) - self.assertTrue(len(msg.gnssMeasurements.ephemerisStatuses)) - self.assertTrue(any(x.source=='cache' for x in msg.gnssMeasurements.ephemerisStatuses)) - self.assertIsNotNone(msg) - - #TODO test cache with only orbits - #with patch('selfdrive.locationd.laikad.get_orbit_data', return_value=None) as mock_method: - # # Verify no orbit downloads even if orbit fetch times is reset since the cache has recently been saved and we don't want to download high frequently - # laikad.astro_dog.orbit_fetched_times = TimeRangeHolder() - # laikad.fetch_navs(self.first_gps_time, block=False) - # mock_method.assert_not_called() - - # # Verify cache is working for only orbits by running a segment - # laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT, save_ephemeris=True) - # msg = verify_messages(self.logs, laikad, return_one_success=True) - # self.assertIsNotNone(msg) - # # Verify orbit data is not downloaded - # mock_method.assert_not_called() - #break - - def test_low_gnss_meas(self): - cnt = 0 - laikad = Laikad() - for m in self.low_gnss: - msg = laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=True) - if msg is None: - continue - gm = msg.gnssMeasurements - if len(gm.correctedMeasurements) != 0 and gm.positionECEF.valid: - cnt += 1 - self.assertEqual(cnt, 560) - - def dict_has_values(self, dct): - self.assertGreater(len(dct), 0) - self.assertGreater(min([len(v) for v in dct.values()]), 0) - - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/manager/gitlab_runner.sh b/selfdrive/manager/gitlab_runner.sh new file mode 100755 index 0000000000..4430f1e98b --- /dev/null +++ b/selfdrive/manager/gitlab_runner.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Define the service name +SERVICE_NAME="gitlab-runner" + +# Function to control the service +control_service() { + local action=$1 # Store the function argument in a local variable + sudo systemctl $action ${SERVICE_NAME} +} + +service_exists_and_is_loaded() { + sudo systemctl status ${SERVICE_NAME} &>/dev/null + if [[ $? -ne 4 ]]; then + return 0 # Service is known to systemd (i.e., loaded) + else + return 1 # Service is unknown to systemd (i.e., not loaded) + fi +} + +# Check for required argument +if [[ -z $1 ]] || { [[ $1 != "start" ]] && [[ $1 != "stop" ]]; }; then + echo "Usage: $0 {start|stop}" + exit 1 +fi + +# Store the script argument in a descriptive variable +ACTION=$1 + +# Trap EXIT signal (Ctrl+C) and stop the service +trap 'control_service stop ; exit' SIGINT SIGKILL EXIT + +# Enter the main loop +while true; do + # Check if the service is actually present on the system + if service_exists_and_is_loaded; then + control_service $ACTION # Call the function with the specified action + fi + sleep 1 # Pause before the next iteration +done diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index bac1853b82..362cb3c39d 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -65,8 +65,7 @@ def manager_init() -> None: ("MadsCruiseMain", "1"), ("CustomBootScreen", "0"), ("CustomOffsets", "0"), - ("DevUI", "1"), - ("DevUIInfo", "1"), + ("DevUIInfo", "0"), ("DisableOnroadUploads", "0"), ("DisengageLateralOnBrake", "0"), ("DynamicLaneProfile", "1"), @@ -88,7 +87,6 @@ def manager_init() -> None: ("ScreenRecorder", "1"), ("ShowDebugUI", "1"), ("SpeedLimitControl", "1"), - ("SpeedLimitPercOffset", "1"), ("SpeedLimitValueOffset", "0"), ("SpeedLimitOffsetType", "0"), ("StandStillTimer", "0"), diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index 0dda81c2d1..d4a6b73de7 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -89,7 +89,7 @@ class ManagerProcess(ABC): self.stop(sig=signal.SIGKILL) self.start() - def check_watchdog(self, started: bool, params: Params) -> None: + def check_watchdog(self, started: bool) -> None: if self.watchdog_max_dt is None or self.proc is None: return @@ -103,7 +103,7 @@ class ManagerProcess(ABC): dt = time.monotonic() - self.last_watchdog_time / 1e9 - always_watchdog = self.always_watchdog and params.get_bool("IsOffroad") and self.proc.exitcode is not None + always_watchdog = self.always_watchdog and not started and self.proc.exitcode is not None if dt > self.watchdog_max_dt: if (self.watchdog_seen or always_watchdog) and ENABLE_WATCHDOG: @@ -291,6 +291,6 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None else: p.stop(block=False) - p.check_watchdog(started, params) + p.check_watchdog(started) return running diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index fd1ba29868..23cff24a97 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -45,7 +45,6 @@ procs = [ DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), NativeProcess("camerad", "system/camerad", ["./camerad"], driverview), - NativeProcess("clocksd", "system/clocksd", ["./clocksd"], only_onroad), NativeProcess("logcatd", "system/logcatd", ["./logcatd"], only_onroad), NativeProcess("proclogd", "system/proclogd", ["./proclogd"], only_onroad), PythonProcess("logmessaged", "system.logmessaged", always_run), @@ -68,7 +67,6 @@ procs = [ PythonProcess("controlsd", "selfdrive.controls.controlsd", only_onroad), PythonProcess("deleter", "system.loggerd.deleter", always_run), PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(not PC or WEBCAM)), - PythonProcess("laikad", "selfdrive.locationd.laikad", only_onroad), PythonProcess("rawgpsd", "system.sensord.rawgps.rawgpsd", qcomgps, enabled=TICI), PythonProcess("navd", "selfdrive.navd.navd", only_onroad), PythonProcess("pandad", "selfdrive.boardd.pandad", always_run), @@ -84,15 +82,20 @@ procs = [ PythonProcess("statsd", "selfdrive.statsd", always_run), NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None), always_watchdog=True), - PythonProcess("gpxd", "selfdrive.gpxd.gpxd", only_onroad), - PythonProcess("gpxd_uploader", "selfdrive.gpxd.gpx_uploader", only_offroad), PythonProcess("mapd", "selfdrive.mapd.mapd", only_onroad), PythonProcess("otisserv", "selfdrive.navd.otisserv", always_run), - PythonProcess("fleet_manager", "system.fleetmanager.fleet_manager", only_offroad), + PythonProcess("fleet_manager", "system.fleetmanager.fleet_manager", always_run), # debug procs NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar), PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), ] +if os.path.exists("./gitlab_runner.sh") and True: # Of course and True is always true. Placeholder for a param :D + # Only devs! + procs += [ + NativeProcess("gitlab_runner_start", "selfdrive/manager", ["./gitlab_runner.sh", "start"], only_offroad, sigkill=False), + NativeProcess("gitlab_runner_stop", "selfdrive/manager", ["./gitlab_runner.sh", "stop"], only_onroad, sigkill=False) + ] + managed_processes = {p.name: p for p in procs} diff --git a/selfdrive/mapd/mapd.py b/selfdrive/mapd/mapd.py index d4efcbad64..a2f9a5f5af 100644 --- a/selfdrive/mapd/mapd.py +++ b/selfdrive/mapd/mapd.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import json -import math import threading from traceback import print_exception import numpy as np @@ -37,16 +36,6 @@ def excepthook(args): threading.excepthook = excepthook -class GPSData: - def __init__(self, unixTimestampMillis, latitude, longitude, speed, bearingDeg, accuracy, bearingAccuracyDeg): - self.unixTimestampMillis = unixTimestampMillis - self.latitude = latitude - self.longitude = longitude - self.speed = speed - self.bearingDeg = bearingDeg - self.accuracy = accuracy - self.bearingAccuracyDeg = bearingAccuracyDeg - class MapD(): def __init__(self): @@ -68,6 +57,7 @@ class MapD(): self._disengaging = False self._query_thread = None self._lock = threading.RLock() + self.gps_sock = 'gpsLocationExternal' # dp - use LastGPSPosition as init position (if we are in a undercover car park?) # this way we can prefetch osm data before we get a fix. @@ -91,37 +81,27 @@ class MapD(): self._op_enabled = hud_control.speedVisible def update_gps(self, sm): - sock = 'liveLocationKalman' - if not sm.updated[sock] or not sm.valid[sock]: + self.gps_sock = 'gpsLocationExternal' if sm.rcv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' + if not sm.updated[self.gps_sock] or not sm.valid[self.gps_sock]: return - log = sm[sock] - + log = sm[self.gps_sock] + self.last_gps = log # ignore the message if the fix is invalid - if not log.positionGeodetic.valid: + if log.flags % 2 == 0: return self.last_gps_fix_timestamp = log.unixTimestampMillis # Unix TS. Milliseconds since January 1, 1970. - self.location_rad = np.radians(np.array([log.positionGeodetic.value[0], log.positionGeodetic.value[1]], dtype=float)) - self.location_deg = (log.positionGeodetic.value[0], log.positionGeodetic.value[1]) - self.bearing_rad = math.degrees(log.calibratedOrientationNED.value[2]) - self.gps_speed = log.velocityCalibrated.value[2] - self.location_stdev = 1 # hardcoding, this is not available in liveLocationKalman - - self.last_gps = GPSData( - unixTimestampMillis=self.last_gps_fix_timestamp, - latitude=self.location_deg[0], - longitude=self.location_deg[1], - speed=self.gps_speed, - bearingDeg=self.bearing_rad, - accuracy=self.location_stdev, # assuming this is equivalent information - bearingAccuracyDeg=1. # you'll need to assign this if available - ) + self.location_rad = np.radians(np.array([log.latitude, log.longitude], dtype=float)) + self.location_deg = (log.latitude, log.longitude) + self.bearing_rad = np.radians(log.bearingDeg, dtype=float) + self.gps_speed = log.speed + self.location_stdev = log.accuracy if self.gps_sock == 'gpsLocationExternal' else 1 # gpsLocation doesn't report accuracy _debug('Mapd: ********* Got GPS fix' + f'Pos: {self.location_deg} +/- {self.location_stdev * 2.} mts.\n' - + f'Bearing: {log.calibratedOrientationNED.value[2]} +/- {1. * 2.} deg.\n' + + f'Bearing: {log.bearingDeg} +/- {log.bearingAccuracyDeg * 2.} deg.\n' + f'timestamp: {strftime("%d-%m-%y %H:%M:%S", gmtime(self.last_gps_fix_timestamp * 1e-3))}' + '*******', log_to_cloud=False) @@ -242,8 +222,8 @@ class MapD(): current_road_name = self.route.current_road_name map_data_msg = messaging.new_message('liveMapDataSP') - map_data_msg.valid = sm.all_alive(service_list=['liveLocationKalman']) and \ - sm.all_valid(service_list=['liveLocationKalman']) + map_data_msg.valid = sm.all_alive(service_list=[self.gps_sock]) and \ + sm.all_valid(service_list=[self.gps_sock]) liveMapDataSP = map_data_msg.liveMapDataSP liveMapDataSP.lastGpsTimestamp = self.last_gps.unixTimestampMillis @@ -251,7 +231,7 @@ class MapD(): liveMapDataSP.lastGpsLongitude = float(self.last_gps.longitude) liveMapDataSP.lastGpsSpeed = float(self.last_gps.speed) liveMapDataSP.lastGpsBearingDeg = float(self.last_gps.bearingDeg) - liveMapDataSP.lastGpsAccuracy = float(self.last_gps.accuracy) + liveMapDataSP.lastGpsAccuracy = float(self.last_gps.accuracy if self.gps_sock == 'gpsLocationExternal' else 1) # gpsLocation doesnt report accuracy liveMapDataSP.lastGpsBearingAccuracyDeg = float(self.last_gps.bearingAccuracyDeg) liveMapDataSP.speedLimitValid = bool(speed_limit is not None) @@ -292,7 +272,7 @@ def mapd_thread(sm=None, pm=None): # *** setup messaging if sm is None: - sm = messaging.SubMaster(['liveLocationKalman', 'carControl']) + sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal', 'carControl']) if pm is None: pm = messaging.PubMaster(['liveMapDataSP']) diff --git a/selfdrive/mapd/sp_osm_db.json b/selfdrive/mapd/sp_osm_db.json index 6e625918c0..75562fe505 100644 --- a/selfdrive/mapd/sp_osm_db.json +++ b/selfdrive/mapd/sp_osm_db.json @@ -44,6 +44,11 @@ "waypoint": "2729449", "url": "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EQzbZeus-yBNsgP1KQyLn9wBYZVMMknVoHdB7_ewulLbdA?e=DrnKkb&download=1" }, + "Spain": { + "name": "spain", + "waypoint": "4263034", + "url": "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/Ee21EfLqZxxLjksDCB_2dscBXKN9Kch8ZOYz3cIWnVpBTg?e=LWBJ1n&download=1" + }, "Taiwan": { "name": "taiwan", "waypoint": "198637969", diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 509c4e6aa7..286f3a0a4a 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -45,17 +45,19 @@ snpe_rpath = lenvCython['RPATH'] + [snpe_rpath_qcom if arch == "larch64" else sn cython_libs = envCython["LIBS"] + libs snpemodel_lib = lenv.Library('snpemodel', ['runners/snpemodel.cc']) commonmodel_lib = lenv.Library('commonmodel', common_src) -driving_lib = lenv.Library('driving', ['models/driving.cc']) lenvCython.Program('runners/runmodel_pyx.so', 'runners/runmodel_pyx.pyx', LIBS=cython_libs, FRAMEWORKS=frameworks) lenvCython.Program('runners/snpemodel_pyx.so', 'runners/snpemodel_pyx.pyx', LIBS=[snpemodel_lib, snpe_lib, *cython_libs], FRAMEWORKS=frameworks, RPATH=snpe_rpath) lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) -lenvCython.Program('models/driving_pyx.so', 'models/driving_pyx.pyx', LIBS=[driving_lib, commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) + +# Get model metadata +fn = File("models/supercombo").abspath +cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' +files = sum([lenv.Glob("#"+x) for x in open(File("#release/files_common").abspath).read().split("\n") if x.endswith("get_model_metadata.py")], []) +lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"]+files, cmd) # Build thneed model if arch == "larch64" or GetOption('pc_thneed'): - fn = File("models/supercombo").abspath - tinygrad_opts = ["NOLOCALS=1", "IMAGE=2", "GPU=1"] if not GetOption('pc_thneed'): # use FLOAT16 on device for speed + don't cache the CL kernels for space diff --git a/selfdrive/modeld/constants.py b/selfdrive/modeld/constants.py index 125864b98b..e03d93d7e8 100644 --- a/selfdrive/modeld/constants.py +++ b/selfdrive/modeld/constants.py @@ -1,7 +1,80 @@ -IDX_N = 33 +import numpy as np def index_function(idx, max_val=192, max_idx=32): return (max_val) * ((idx/max_idx)**2) +class ModelConstants: + # time and distance indices + IDX_N = 33 + T_IDXS = [index_function(idx, max_val=10.0) for idx in range(IDX_N)] + X_IDXS = [index_function(idx, max_val=192.0) for idx in range(IDX_N)] + LEAD_T_IDXS = [0., 2., 4., 6., 8., 10.] + LEAD_T_OFFSETS = [0., 2., 4.] + META_T_IDXS = [2., 4., 6., 8., 10.] -T_IDXS = [index_function(idx, max_val=10.0) for idx in range(IDX_N)] + # model inputs constants + MODEL_FREQ = 20 + FEATURE_LEN = 512 + HISTORY_BUFFER_LEN = 99 + DESIRE_LEN = 8 + TRAFFIC_CONVENTION_LEN = 2 + NAV_FEATURE_LEN = 256 + NAV_INSTRUCTION_LEN = 150 + DRIVING_STYLE_LEN = 12 + + # model outputs constants + FCW_THRESHOLDS_5MS2 = np.array([.05, .05, .15, .15, .15], dtype=np.float32) + FCW_THRESHOLDS_3MS2 = np.array([.7, .7], dtype=np.float32) + FCW_5MS2_PROBS_WIDTH = 5 + FCW_3MS2_PROBS_WIDTH = 2 + + DISENGAGE_WIDTH = 5 + POSE_WIDTH = 6 + WIDE_FROM_DEVICE_WIDTH = 3 + SIM_POSE_WIDTH = 6 + LEAD_WIDTH = 4 + LANE_LINES_WIDTH = 2 + ROAD_EDGES_WIDTH = 2 + PLAN_WIDTH = 15 + DESIRE_PRED_WIDTH = 8 + + NUM_LANE_LINES = 4 + NUM_ROAD_EDGES = 2 + + LEAD_TRAJ_LEN = 6 + DESIRE_PRED_LEN = 4 + + PLAN_MHP_N = 5 + LEAD_MHP_N = 2 + PLAN_MHP_SELECTION = 1 + LEAD_MHP_SELECTION = 3 + + FCW_THRESHOLD_5MS2_HIGH = 0.15 + FCW_THRESHOLD_5MS2_LOW = 0.05 + FCW_THRESHOLD_3MS2 = 0.7 + + CONFIDENCE_BUFFER_LEN = 5 + RYG_GREEN = 0.01165 + RYG_YELLOW = 0.06157 + +# model outputs slices +class Plan: + POSITION = slice(0, 3) + VELOCITY = slice(3, 6) + ACCELERATION = slice(6, 9) + T_FROM_CURRENT_EULER = slice(9, 12) + ORIENTATION_RATE = slice(12, 15) + +class Meta: + ENGAGED = slice(0, 1) + # next 2, 4, 6, 8, 10 seconds + GAS_DISENGAGE = slice(1, 36, 7) + BRAKE_DISENGAGE = slice(2, 36, 7) + STEER_OVERRIDE = slice(3, 36, 7) + HARD_BRAKE_3 = slice(4, 36, 7) + HARD_BRAKE_4 = slice(5, 36, 7) + HARD_BRAKE_5 = slice(6, 36, 7) + GAS_PRESS = slice(7, 36, 7) + # next 0, 2, 4, 6, 8, 10 seconds + LEFT_BLINKER = slice(36, 48, 2) + RIGHT_BLINKER = slice(37, 48, 2) diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py new file mode 100644 index 0000000000..55d7abc1aa --- /dev/null +++ b/selfdrive/modeld/fill_model_msg.py @@ -0,0 +1,188 @@ +import os +import capnp +import numpy as np +from typing import Dict +from cereal import log +from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta + +SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') + +ConfidenceClass = log.ModelDataV2.ConfidenceClass + +class PublishState: + def __init__(self): + self.disengage_buffer = np.zeros(ModelConstants.CONFIDENCE_BUFFER_LEN*ModelConstants.DISENGAGE_WIDTH, dtype=np.float32) + self.prev_brake_5ms2_probs = np.zeros(ModelConstants.FCW_5MS2_PROBS_WIDTH, dtype=np.float32) + self.prev_brake_3ms2_probs = np.zeros(ModelConstants.FCW_3MS2_PROBS_WIDTH, dtype=np.float32) + +def fill_xyzt(builder, t, x, y, z, x_std=None, y_std=None, z_std=None): + builder.t = t + builder.x = x.tolist() + builder.y = y.tolist() + builder.z = z.tolist() + if x_std is not None: + builder.xStd = x_std.tolist() + if y_std is not None: + builder.yStd = y_std.tolist() + if z_std is not None: + builder.zStd = z_std.tolist() + +def fill_xyvat(builder, t, x, y, v, a, x_std=None, y_std=None, v_std=None, a_std=None): + builder.t = t + builder.x = x.tolist() + builder.y = y.tolist() + builder.v = v.tolist() + builder.a = a.tolist() + if x_std is not None: + builder.xStd = x_std.tolist() + if y_std is not None: + builder.yStd = y_std.tolist() + if v_std is not None: + builder.vStd = v_std.tolist() + if a_std is not None: + builder.aStd = a_std.tolist() + +def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: Dict[str, np.ndarray], publish_state: PublishState, + vipc_frame_id: int, vipc_frame_id_extra: int, frame_id: int, frame_drop: float, + timestamp_eof: int, timestamp_llk: int, model_execution_time: float, + nav_enabled: bool, valid: bool) -> None: + frame_age = frame_id - vipc_frame_id if frame_id > vipc_frame_id else 0 + msg.valid = valid + + modelV2 = msg.modelV2 + modelV2.frameId = vipc_frame_id + modelV2.frameIdExtra = vipc_frame_id_extra + modelV2.frameAge = frame_age + modelV2.frameDropPerc = frame_drop * 100 + modelV2.timestampEof = timestamp_eof + modelV2.locationMonoTime = timestamp_llk + modelV2.modelExecutionTime = model_execution_time + modelV2.navEnabled = nav_enabled + + # plan + position = modelV2.position + fill_xyzt(position, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.POSITION].T, *net_output_data['plan_stds'][0,:,Plan.POSITION].T) + velocity = modelV2.velocity + fill_xyzt(velocity, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.VELOCITY].T) + acceleration = modelV2.acceleration + fill_xyzt(acceleration, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ACCELERATION].T) + orientation = modelV2.orientation + fill_xyzt(orientation, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.T_FROM_CURRENT_EULER].T) + orientation_rate = modelV2.orientationRate + fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) + + # times at X_IDXS according to model plan + PLAN_T_IDXS = [np.nan] * ModelConstants.IDX_N + PLAN_T_IDXS[0] = 0.0 + plan_x = net_output_data['plan'][0,:,Plan.POSITION][:,0].tolist() + for xidx in range(1, ModelConstants.IDX_N): + tidx = 0 + # increment tidx until we find an element that's further away than the current xidx + while tidx < ModelConstants.IDX_N - 1 and plan_x[tidx+1] < ModelConstants.X_IDXS[xidx]: + tidx += 1 + if tidx == ModelConstants.IDX_N - 1: + # if the Plan doesn't extend far enough, set plan_t to the max value (10s), then break + PLAN_T_IDXS[xidx] = ModelConstants.T_IDXS[ModelConstants.IDX_N - 1] + break + # interpolate to find `t` for the current xidx + current_x_val = plan_x[tidx] + next_x_val = plan_x[tidx+1] + p = (ModelConstants.X_IDXS[xidx] - current_x_val) / (next_x_val - current_x_val) + PLAN_T_IDXS[xidx] = p * ModelConstants.T_IDXS[tidx+1] + (1 - p) * ModelConstants.T_IDXS[tidx] + + # lane lines + modelV2.init('laneLines', 4) + for i in range(4): + lane_line = modelV2.laneLines[i] + fill_xyzt(lane_line, PLAN_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['lane_lines'][0,i,:,0], net_output_data['lane_lines'][0,i,:,1]) + modelV2.laneLineStds = net_output_data['lane_lines_stds'][0,:,0,0].tolist() + modelV2.laneLineProbs = net_output_data['lane_lines_prob'][0,1::2].tolist() + + # road edges + modelV2.init('roadEdges', 2) + for i in range(2): + road_edge = modelV2.roadEdges[i] + fill_xyzt(road_edge, PLAN_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['road_edges'][0,i,:,0], net_output_data['road_edges'][0,i,:,1]) + modelV2.roadEdgeStds = net_output_data['road_edges_stds'][0,:,0,0].tolist() + + # leads + modelV2.init('leadsV3', 3) + for i in range(3): + lead = modelV2.leadsV3[i] + fill_xyvat(lead, ModelConstants.LEAD_T_IDXS, *net_output_data['lead'][0,i].T, *net_output_data['lead_stds'][0,i].T) + lead.prob = net_output_data['lead_prob'][0,i].tolist() + lead.probTime = ModelConstants.LEAD_T_OFFSETS[i] + + # meta + meta = modelV2.meta + meta.desireState = net_output_data['desire_state'][0].reshape(-1).tolist() + meta.desirePrediction = net_output_data['desire_pred'][0].reshape(-1).tolist() + meta.engagedProb = net_output_data['meta'][0,Meta.ENGAGED].item() + meta.init('disengagePredictions') + disengage_predictions = meta.disengagePredictions + disengage_predictions.t = ModelConstants.META_T_IDXS + disengage_predictions.brakeDisengageProbs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE].tolist() + disengage_predictions.gasDisengageProbs = net_output_data['meta'][0,Meta.GAS_DISENGAGE].tolist() + disengage_predictions.steerOverrideProbs = net_output_data['meta'][0,Meta.STEER_OVERRIDE].tolist() + disengage_predictions.brake3MetersPerSecondSquaredProbs = net_output_data['meta'][0,Meta.HARD_BRAKE_3].tolist() + disengage_predictions.brake4MetersPerSecondSquaredProbs = net_output_data['meta'][0,Meta.HARD_BRAKE_4].tolist() + disengage_predictions.brake5MetersPerSecondSquaredProbs = net_output_data['meta'][0,Meta.HARD_BRAKE_5].tolist() + + publish_state.prev_brake_5ms2_probs[:-1] = publish_state.prev_brake_5ms2_probs[1:] + publish_state.prev_brake_5ms2_probs[-1] = net_output_data['meta'][0,Meta.HARD_BRAKE_5][0] + publish_state.prev_brake_3ms2_probs[:-1] = publish_state.prev_brake_3ms2_probs[1:] + publish_state.prev_brake_3ms2_probs[-1] = net_output_data['meta'][0,Meta.HARD_BRAKE_3][0] + hard_brake_predicted = (publish_state.prev_brake_5ms2_probs > ModelConstants.FCW_THRESHOLDS_5MS2).all() and \ + (publish_state.prev_brake_3ms2_probs > ModelConstants.FCW_THRESHOLDS_3MS2).all() + meta.hardBrakePredicted = hard_brake_predicted.item() + + # temporal pose + temporal_pose = modelV2.temporalPose + temporal_pose.trans = net_output_data['sim_pose'][0,:3].tolist() + temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:3].tolist() + temporal_pose.rot = net_output_data['sim_pose'][0,3:].tolist() + temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,3:].tolist() + + # confidence + if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0: + # any disengage prob + brake_disengage_probs = net_output_data['meta'][0,Meta.BRAKE_DISENGAGE] + gas_disengage_probs = net_output_data['meta'][0,Meta.GAS_DISENGAGE] + steer_override_probs = net_output_data['meta'][0,Meta.STEER_OVERRIDE] + any_disengage_probs = 1-((1-brake_disengage_probs)*(1-gas_disengage_probs)*(1-steer_override_probs)) + # independent disengage prob for each 2s slice + ind_disengage_probs = np.r_[any_disengage_probs[0], np.diff(any_disengage_probs) / (1 - any_disengage_probs[:-1])] + # rolling buf for 2, 4, 6, 8, 10s + publish_state.disengage_buffer[:-ModelConstants.DISENGAGE_WIDTH] = publish_state.disengage_buffer[ModelConstants.DISENGAGE_WIDTH:] + publish_state.disengage_buffer[-ModelConstants.DISENGAGE_WIDTH:] = ind_disengage_probs + + score = 0. + for i in range(ModelConstants.DISENGAGE_WIDTH): + score += publish_state.disengage_buffer[i*ModelConstants.DISENGAGE_WIDTH+ModelConstants.DISENGAGE_WIDTH-1-i].item() / ModelConstants.DISENGAGE_WIDTH + if score < ModelConstants.RYG_GREEN: + modelV2.confidence = ConfidenceClass.green + elif score < ModelConstants.RYG_YELLOW: + modelV2.confidence = ConfidenceClass.yellow + else: + modelV2.confidence = ConfidenceClass.red + + # raw prediction if enabled + if SEND_RAW_PRED: + modelV2.rawPredictions = net_output_data['raw_pred'].tobytes() + +def fill_pose_msg(msg: capnp._DynamicStructBuilder, net_output_data: Dict[str, np.ndarray], + vipc_frame_id: int, vipc_dropped_frames: int, timestamp_eof: int, live_calib_seen: bool) -> None: + msg.valid = live_calib_seen & (vipc_dropped_frames < 1) + cameraOdometry = msg.cameraOdometry + + cameraOdometry.frameId = vipc_frame_id + cameraOdometry.timestampEof = timestamp_eof + + cameraOdometry.trans = net_output_data['pose'][0,:3].tolist() + cameraOdometry.rot = net_output_data['pose'][0,3:].tolist() + cameraOdometry.wideFromDeviceEuler = net_output_data['wide_from_device_euler'][0,:].tolist() + cameraOdometry.roadTransformTrans = net_output_data['road_transform'][0,:3].tolist() + cameraOdometry.transStd = net_output_data['pose_stds'][0,:3].tolist() + cameraOdometry.rotStd = net_output_data['pose_stds'][0,3:].tolist() + cameraOdometry.wideFromDeviceEulerStd = net_output_data['wide_from_device_euler_stds'][0,:].tolist() + cameraOdometry.roadTransformTransStd = net_output_data['road_transform_stds'][0,:3].tolist() diff --git a/selfdrive/modeld/get_model_metadata.py b/selfdrive/modeld/get_model_metadata.py new file mode 100755 index 0000000000..187f83399b --- /dev/null +++ b/selfdrive/modeld/get_model_metadata.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import sys +import pathlib +import onnx +import codecs +import pickle +from typing import Tuple + +def get_name_and_shape(value_info:onnx.ValueInfoProto) -> Tuple[str, Tuple[int,...]]: + shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim]) + name = value_info.name + return name, shape + +if __name__ == "__main__": + model_path = pathlib.Path(sys.argv[1]) + model = onnx.load(str(model_path)) + i = [x.key for x in model.metadata_props].index('output_slices') + output_slices = model.metadata_props[i].value + + metadata = {} + metadata['output_slices'] = pickle.loads(codecs.decode(output_slices.encode(), "base64")) + metadata['input_shapes'] = dict([get_name_and_shape(x) for x in model.graph.input]) + metadata['output_shapes'] = dict([get_name_and_shape(x) for x in model.graph.output]) + + metadata_path = model_path.parent / (model_path.stem + '_metadata.pkl') + with open(metadata_path, 'wb') as f: + pickle.dump(metadata, f) + + print(f'saved metadata to {metadata_path}') diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 4f49398797..5df4e5e172 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 import sys +import os import time +import pickle import numpy as np +import cereal.messaging as messaging from pathlib import Path from typing import Dict, Optional from setproctitle import setproctitle @@ -13,16 +16,19 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import get_warp_matrix from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime +from openpilot.selfdrive.modeld.parse_model_outputs import Parser +from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState +from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.modeld.models.commonmodel_pyx import ModelFrame, CLContext -from openpilot.selfdrive.modeld.models.driving_pyx import ( - PublishState, create_model_msg, create_pose_msg, - FEATURE_LEN, HISTORY_BUFFER_LEN, DESIRE_LEN, TRAFFIC_CONVENTION_LEN, NAV_FEATURE_LEN, NAV_INSTRUCTION_LEN, - OUTPUT_SIZE, NET_OUTPUT_SIZE, MODEL_FREQ) + +SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PATHS = { ModelRunner.THNEED: Path(__file__).parent / 'models/supercombo.thneed', ModelRunner.ONNX: Path(__file__).parent / 'models/supercombo.onnx'} +METADATA_PATH = Path(__file__).parent / 'models/supercombo_metadata.pkl' + class FrameMeta: frame_id: int = 0 timestamp_sof: int = 0 @@ -43,28 +49,41 @@ class ModelState: def __init__(self, context: CLContext): self.frame = ModelFrame(context) self.wide_frame = ModelFrame(context) - self.prev_desire = np.zeros(DESIRE_LEN, dtype=np.float32) - self.output = np.zeros(NET_OUTPUT_SIZE, dtype=np.float32) + self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) self.inputs = { - 'desire': np.zeros(DESIRE_LEN * (HISTORY_BUFFER_LEN+1), dtype=np.float32), - 'traffic_convention': np.zeros(TRAFFIC_CONVENTION_LEN, dtype=np.float32), - 'nav_features': np.zeros(NAV_FEATURE_LEN, dtype=np.float32), - 'nav_instructions': np.zeros(NAV_INSTRUCTION_LEN, dtype=np.float32), - 'features_buffer': np.zeros(HISTORY_BUFFER_LEN * FEATURE_LEN, dtype=np.float32), + 'desire': np.zeros(ModelConstants.DESIRE_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), + 'traffic_convention': np.zeros(ModelConstants.TRAFFIC_CONVENTION_LEN, dtype=np.float32), + 'nav_features': np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32), + 'nav_instructions': np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32), + 'features_buffer': np.zeros(ModelConstants.HISTORY_BUFFER_LEN * ModelConstants.FEATURE_LEN, dtype=np.float32), } + with open(METADATA_PATH, 'rb') as f: + model_metadata = pickle.load(f) + + 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) + self.parser = Parser() + self.model = ModelRunner(MODEL_PATHS, self.output, Runtime.GPU, False, context) self.model.addInput("input_imgs", None) self.model.addInput("big_input_imgs", None) for k,v in self.inputs.items(): self.model.addInput(k, v) + def slice_outputs(self, model_outputs: np.ndarray) -> Dict[str, np.ndarray]: + parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in self.output_slices.items()} + if SEND_RAW_PRED: + parsed_model_outputs['raw_pred'] = model_outputs.copy() + return parsed_model_outputs + def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray, - inputs: Dict[str, np.ndarray], prepare_only: bool) -> Optional[np.ndarray]: + inputs: Dict[str, np.ndarray], prepare_only: bool) -> Optional[Dict[str, np.ndarray]]: # Model decides when action is completed, so desire input is just a pulse triggered on rising edge inputs['desire'][0] = 0 - self.inputs['desire'][:-DESIRE_LEN] = self.inputs['desire'][DESIRE_LEN:] - self.inputs['desire'][-DESIRE_LEN:] = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0) + self.inputs['desire'][:-ModelConstants.DESIRE_LEN] = self.inputs['desire'][ModelConstants.DESIRE_LEN:] + self.inputs['desire'][-ModelConstants.DESIRE_LEN:] = np.where(inputs['desire'] - self.prev_desire > .99, inputs['desire'], 0) self.prev_desire[:] = inputs['desire'] self.inputs['traffic_convention'][:] = inputs['traffic_convention'] @@ -81,9 +100,11 @@ class ModelState: return None self.model.execute() - self.inputs['features_buffer'][:-FEATURE_LEN] = self.inputs['features_buffer'][FEATURE_LEN:] - self.inputs['features_buffer'][-FEATURE_LEN:] = self.output[OUTPUT_SIZE:OUTPUT_SIZE+FEATURE_LEN] - return self.output + outputs = self.parser.parse_outputs(self.slice_outputs(self.output)) + + self.inputs['features_buffer'][:-ModelConstants.FEATURE_LEN] = self.inputs['features_buffer'][ModelConstants.FEATURE_LEN:] + self.inputs['features_buffer'][-ModelConstants.FEATURE_LEN:] = outputs['hidden_state'][0, :] + return outputs def main(): @@ -122,22 +143,21 @@ def main(): pm = PubMaster(["modelV2", "cameraOdometry"]) sm = SubMaster(["lateralPlan", "roadCameraState", "liveCalibration", "driverMonitoringState", "navModel", "navInstruction"]) - state = PublishState() + publish_state = PublishState() params = Params() # setup filter to track dropped frames - frame_dropped_filter = FirstOrderFilter(0., 10., 1. / MODEL_FREQ) + frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_FREQ) frame_id = 0 last_vipc_frame_id = 0 run_count = 0 - # last = 0.0 model_transform_main = np.zeros((3, 3), dtype=np.float32) model_transform_extra = np.zeros((3, 3), dtype=np.float32) live_calib_seen = False driving_style = np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], dtype=np.float32) - nav_features = np.zeros(NAV_FEATURE_LEN, dtype=np.float32) - nav_instructions = np.zeros(NAV_INSTRUCTION_LEN, dtype=np.float32) + nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) + nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) buf_main, buf_extra = None, None meta_main = FrameMeta() meta_extra = FrameMeta() @@ -190,8 +210,8 @@ def main(): traffic_convention = np.zeros(2) traffic_convention[int(is_rhd)] = 1 - vec_desire = np.zeros(DESIRE_LEN, dtype=np.float32) - if desire >= 0 and desire < DESIRE_LEN: + vec_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) + if desire >= 0 and desire < ModelConstants.DESIRE_LEN: vec_desire[desire] = 1 # Enable/disable nav features @@ -244,13 +264,15 @@ def main(): model_execution_time = mt2 - mt1 if model_output is not None: - pm.send("modelV2", create_model_msg(model_output, state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio, - meta_main.timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, live_calib_seen)) - pm.send("cameraOdometry", create_pose_msg(model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen)) + modelv2_send = messaging.new_message('modelV2') + posenet_send = messaging.new_message('cameraOdometry') + fill_model_msg(modelv2_send, model_output, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio, + meta_main.timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, live_calib_seen) + + fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen) + pm.send('modelV2', modelv2_send) + pm.send('cameraOdometry', posenet_send) - # print("model process: %.2fms, from last %.2fms, vipc_frame_id %u, frame_id, %u, frame_drop %.3f" % - # ((mt2 - mt1)*1000, (mt1 - last)*1000, meta_extra.frame_id, frame_id, frame_drop_ratio)) - # last = mt1 last_vipc_frame_id = meta_main.frame_id diff --git a/selfdrive/modeld/models/README.md b/selfdrive/modeld/models/README.md index 11808ef552..ff5c91fe0c 100644 --- a/selfdrive/modeld/models/README.md +++ b/selfdrive/modeld/models/README.md @@ -20,7 +20,11 @@ To view the architecture of the ONNX networks, you can use [netron](https://netr * **traffic convention** * one-hot encoded vector to tell model whether traffic is right-hand or left-hand traffic : 2 * **feature buffer** - * A buffer of intermediate features that gets appended to the current feature to form a 5 seconds temporal context (at 20FPS) : 99 * 128 + * A buffer of intermediate features that gets appended to the current feature to form a 5 seconds temporal context (at 20FPS) : 99 * 512 +* **nav features** + * 1 * 150 +* **nav instructions** + * 1 * 256 ### Supercombo output format (Full size: XXX x float32) diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc deleted file mode 100644 index 0a7f0c949d..0000000000 --- a/selfdrive/modeld/models/driving.cc +++ /dev/null @@ -1,330 +0,0 @@ -#include "selfdrive/modeld/models/driving.h" - -#include - - -void fill_lead(cereal::ModelDataV2::LeadDataV3::Builder lead, const ModelOutputLeads &leads, int t_idx, float prob_t) { - std::array lead_t = {0.0, 2.0, 4.0, 6.0, 8.0, 10.0}; - const auto &best_prediction = leads.get_best_prediction(t_idx); - lead.setProb(sigmoid(leads.prob[t_idx])); - lead.setProbTime(prob_t); - std::array lead_x, lead_y, lead_v, lead_a; - std::array lead_x_std, lead_y_std, lead_v_std, lead_a_std; - for (int i=0; i desire_state_softmax; - softmax(meta_data.desire_state_prob.array.data(), desire_state_softmax.data(), DESIRE_LEN); - - std::array desire_pred_softmax; - for (int i=0; i lat_long_t = {2, 4, 6, 8, 10}; - std::array gas_disengage_sigmoid, brake_disengage_sigmoid, steer_override_sigmoid, - brake_3ms2_sigmoid, brake_4ms2_sigmoid, brake_5ms2_sigmoid; - for (int i=0; i threshold; - } - for (int i=0; i FCW_THRESHOLD_3MS2; - } - - auto disengage = meta.initDisengagePredictions(); - disengage.setT(to_kj_array_ptr(lat_long_t)); - disengage.setGasDisengageProbs(to_kj_array_ptr(gas_disengage_sigmoid)); - disengage.setBrakeDisengageProbs(to_kj_array_ptr(brake_disengage_sigmoid)); - disengage.setSteerOverrideProbs(to_kj_array_ptr(steer_override_sigmoid)); - disengage.setBrake3MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_3ms2_sigmoid)); - disengage.setBrake4MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_4ms2_sigmoid)); - disengage.setBrake5MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_5ms2_sigmoid)); - - meta.setEngagedProb(sigmoid(meta_data.engaged_prob)); - meta.setDesirePrediction(to_kj_array_ptr(desire_pred_softmax)); - meta.setDesireState(to_kj_array_ptr(desire_state_softmax)); - meta.setHardBrakePredicted(above_fcw_threshold); -} - -void fill_confidence(cereal::ModelDataV2::Builder &framed, PublishState &ps) { - if (framed.getFrameId() % (2*MODEL_FREQ) == 0) { - // update every 2s to match predictions interval - auto dbps = framed.getMeta().getDisengagePredictions().getBrakeDisengageProbs(); - auto dgps = framed.getMeta().getDisengagePredictions().getGasDisengageProbs(); - auto dsps = framed.getMeta().getDisengagePredictions().getSteerOverrideProbs(); - - float any_dp[DISENGAGE_LEN]; - float dp_ind[DISENGAGE_LEN]; - - for (int i = 0; i < DISENGAGE_LEN; i++) { - any_dp[i] = 1 - ((1-dbps[i])*(1-dgps[i])*(1-dsps[i])); // any disengage prob - } - - dp_ind[0] = any_dp[0]; - for (int i = 0; i < DISENGAGE_LEN-1; i++) { - dp_ind[i+1] = (any_dp[i+1] - any_dp[i]) / (1 - any_dp[i]); // independent disengage prob for each 2s slice - } - - // rolling buf for 2, 4, 6, 8, 10s - std::memmove(&ps.disengage_buffer[0], &ps.disengage_buffer[DISENGAGE_LEN], sizeof(float) * DISENGAGE_LEN * (DISENGAGE_LEN-1)); - std::memcpy(&ps.disengage_buffer[DISENGAGE_LEN * (DISENGAGE_LEN-1)], &dp_ind[0], sizeof(float) * DISENGAGE_LEN); - } - - float score = 0; - for (int i = 0; i < DISENGAGE_LEN; i++) { - score += ps.disengage_buffer[i*DISENGAGE_LEN+DISENGAGE_LEN-1-i] / DISENGAGE_LEN; - } - - if (score < RYG_GREEN) { - framed.setConfidence(cereal::ModelDataV2::ConfidenceClass::GREEN); - } else if (score < RYG_YELLOW) { - framed.setConfidence(cereal::ModelDataV2::ConfidenceClass::YELLOW); - } else { - framed.setConfidence(cereal::ModelDataV2::ConfidenceClass::RED); - } -} - -template -void fill_xyzt(cereal::XYZTData::Builder xyzt, const std::array &t, - const std::array &x, const std::array &y, const std::array &z) { - xyzt.setT(to_kj_array_ptr(t)); - xyzt.setX(to_kj_array_ptr(x)); - xyzt.setY(to_kj_array_ptr(y)); - xyzt.setZ(to_kj_array_ptr(z)); -} - -template -void fill_xyzt(cereal::XYZTData::Builder xyzt, const std::array &t, - const std::array &x, const std::array &y, const std::array &z, - const std::array &x_std, const std::array &y_std, const std::array &z_std) { - fill_xyzt(xyzt, t, x, y, z); - xyzt.setXStd(to_kj_array_ptr(x_std)); - xyzt.setYStd(to_kj_array_ptr(y_std)); - xyzt.setZStd(to_kj_array_ptr(z_std)); -} - -void fill_plan(cereal::ModelDataV2::Builder &framed, const ModelOutputPlanPrediction &plan) { - std::array pos_x, pos_y, pos_z; - std::array pos_x_std, pos_y_std, pos_z_std; - std::array vel_x, vel_y, vel_z; - std::array rot_x, rot_y, rot_z; - std::array acc_x, acc_y, acc_z; - std::array rot_rate_x, rot_rate_y, rot_rate_z; - - for (int i=0; i &plan_t, - const ModelOutputLaneLines &lanes) { - std::array left_far_y, left_far_z; - std::array left_near_y, left_near_z; - std::array right_near_y, right_near_z; - std::array right_far_y, right_far_z; - for (int j=0; j &plan_t, - const ModelOutputRoadEdges &edges) { - std::array left_y, left_z; - std::array right_y, right_z; - for (int j=0; j plan_t; - std::fill_n(plan_t.data(), plan_t.size(), NAN); - plan_t[0] = 0.0; - for (int xidx=1, tidx=0; xidx t_offsets = {0.0, 2.0, 4.0}; - for (int i=0; i vipc_frame_id) ? (frame_id - vipc_frame_id) : 0; - auto framed = msg.initEvent(valid).initModelV2(); - framed.setFrameId(vipc_frame_id); - framed.setFrameIdExtra(vipc_frame_id_extra); - framed.setFrameAge(frame_age); - framed.setFrameDropPerc(frame_drop * 100); - framed.setTimestampEof(timestamp_eof); - framed.setLocationMonoTime(timestamp_llk); - framed.setModelExecutionTime(model_execution_time); - framed.setNavEnabled(nav_enabled); - if (send_raw_pred) { - framed.setRawPredictions(kj::ArrayPtr(net_output_data, NET_OUTPUT_SIZE).asBytes()); - } - fill_model(framed, *((ModelOutput*) net_output_data), ps); -} - -void fill_pose_msg(MessageBuilder &msg, float *net_output_data, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, uint64_t timestamp_eof, const bool valid) { - const ModelOutput &net_outputs = *((ModelOutput*) net_output_data); - const auto &v_mean = net_outputs.pose.velocity_mean; - const auto &r_mean = net_outputs.pose.rotation_mean; - const auto &t_mean = net_outputs.wide_from_device_euler.mean; - const auto &v_std = net_outputs.pose.velocity_std; - const auto &r_std = net_outputs.pose.rotation_std; - const auto &t_std = net_outputs.wide_from_device_euler.std; - const auto &road_transform_trans_mean = net_outputs.road_transform.position_mean; - const auto &road_transform_trans_std = net_outputs.road_transform.position_std; - - auto posenetd = msg.initEvent(valid && (vipc_dropped_frames < 1)).initCameraOdometry(); - posenetd.setTrans({v_mean.x, v_mean.y, v_mean.z}); - posenetd.setRot({r_mean.x, r_mean.y, r_mean.z}); - posenetd.setWideFromDeviceEuler({t_mean.x, t_mean.y, t_mean.z}); - posenetd.setRoadTransformTrans({road_transform_trans_mean.x, road_transform_trans_mean.y, road_transform_trans_mean.z}); - posenetd.setTransStd({exp(v_std.x), exp(v_std.y), exp(v_std.z)}); - posenetd.setRotStd({exp(r_std.x), exp(r_std.y), exp(r_std.z)}); - posenetd.setWideFromDeviceEulerStd({exp(t_std.x), exp(t_std.y), exp(t_std.z)}); - posenetd.setRoadTransformTransStd({exp(road_transform_trans_std.x), exp(road_transform_trans_std.y), exp(road_transform_trans_std.z)}); - - posenetd.setTimestampEof(timestamp_eof); - posenetd.setFrameId(vipc_frame_id); -} diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h deleted file mode 100644 index 5df172dad6..0000000000 --- a/selfdrive/modeld/models/driving.h +++ /dev/null @@ -1,257 +0,0 @@ -#pragma once - -#include -#include - -#include "cereal/messaging/messaging.h" -#include "common/modeldata.h" -#include "common/util.h" -#include "selfdrive/modeld/models/commonmodel.h" -#include "selfdrive/modeld/runners/run.h" - -constexpr int FEATURE_LEN = 512; -constexpr int HISTORY_BUFFER_LEN = 99; -constexpr int DESIRE_LEN = 8; -constexpr int DESIRE_PRED_LEN = 4; -constexpr int TRAFFIC_CONVENTION_LEN = 2; -constexpr int NAV_FEATURE_LEN = 256; -constexpr int NAV_INSTRUCTION_LEN = 150; -constexpr int DRIVING_STYLE_LEN = 12; -constexpr int MODEL_FREQ = 20; - -constexpr int DISENGAGE_LEN = 5; -constexpr int BLINKER_LEN = 6; -constexpr int META_STRIDE = 7; - -constexpr int PLAN_MHP_N = 5; -constexpr int LEAD_MHP_N = 2; -constexpr int LEAD_TRAJ_LEN = 6; -constexpr int LEAD_MHP_SELECTION = 3; -// Padding to get output shape as multiple of 4 -constexpr int PAD_SIZE = 2; - -constexpr float FCW_THRESHOLD_5MS2_HIGH = 0.15; -constexpr float FCW_THRESHOLD_5MS2_LOW = 0.05; -constexpr float FCW_THRESHOLD_3MS2 = 0.7; - -struct ModelOutputXYZ { - float x; - float y; - float z; -}; -static_assert(sizeof(ModelOutputXYZ) == sizeof(float)*3); - -struct ModelOutputYZ { - float y; - float z; -}; -static_assert(sizeof(ModelOutputYZ) == sizeof(float)*2); - -struct ModelOutputPlanElement { - ModelOutputXYZ position; - ModelOutputXYZ velocity; - ModelOutputXYZ acceleration; - ModelOutputXYZ rotation; - ModelOutputXYZ rotation_rate; -}; -static_assert(sizeof(ModelOutputPlanElement) == sizeof(ModelOutputXYZ)*5); - -struct ModelOutputPlanPrediction { - std::array mean; - std::array std; - float prob; -}; -static_assert(sizeof(ModelOutputPlanPrediction) == (sizeof(ModelOutputPlanElement)*TRAJECTORY_SIZE*2) + sizeof(float)); - -struct ModelOutputPlans { - std::array prediction; - - constexpr const ModelOutputPlanPrediction &get_best_prediction() const { - int max_idx = 0; - for (int i = 1; i < prediction.size(); i++) { - if (prediction[i].prob > prediction[max_idx].prob) { - max_idx = i; - } - } - return prediction[max_idx]; - } -}; -static_assert(sizeof(ModelOutputPlans) == sizeof(ModelOutputPlanPrediction)*PLAN_MHP_N); - -struct ModelOutputLinesXY { - std::array left_far; - std::array left_near; - std::array right_near; - std::array right_far; -}; -static_assert(sizeof(ModelOutputLinesXY) == sizeof(ModelOutputYZ)*TRAJECTORY_SIZE*4); - -struct ModelOutputLineProbVal { - float val_deprecated; - float val; -}; -static_assert(sizeof(ModelOutputLineProbVal) == sizeof(float)*2); - -struct ModelOutputLinesProb { - ModelOutputLineProbVal left_far; - ModelOutputLineProbVal left_near; - ModelOutputLineProbVal right_near; - ModelOutputLineProbVal right_far; -}; -static_assert(sizeof(ModelOutputLinesProb) == sizeof(ModelOutputLineProbVal)*4); - -struct ModelOutputLaneLines { - ModelOutputLinesXY mean; - ModelOutputLinesXY std; - ModelOutputLinesProb prob; -}; -static_assert(sizeof(ModelOutputLaneLines) == (sizeof(ModelOutputLinesXY)*2) + sizeof(ModelOutputLinesProb)); - -struct ModelOutputEdgessXY { - std::array left; - std::array right; -}; -static_assert(sizeof(ModelOutputEdgessXY) == sizeof(ModelOutputYZ)*TRAJECTORY_SIZE*2); - -struct ModelOutputRoadEdges { - ModelOutputEdgessXY mean; - ModelOutputEdgessXY std; -}; -static_assert(sizeof(ModelOutputRoadEdges) == (sizeof(ModelOutputEdgessXY)*2)); - -struct ModelOutputLeadElement { - float x; - float y; - float velocity; - float acceleration; -}; -static_assert(sizeof(ModelOutputLeadElement) == sizeof(float)*4); - -struct ModelOutputLeadPrediction { - std::array mean; - std::array std; - std::array prob; -}; -static_assert(sizeof(ModelOutputLeadPrediction) == (sizeof(ModelOutputLeadElement)*LEAD_TRAJ_LEN*2) + (sizeof(float)*LEAD_MHP_SELECTION)); - -struct ModelOutputLeads { - std::array prediction; - std::array prob; - - constexpr const ModelOutputLeadPrediction &get_best_prediction(int t_idx) const { - int max_idx = 0; - for (int i = 1; i < prediction.size(); i++) { - if (prediction[i].prob[t_idx] > prediction[max_idx].prob[t_idx]) { - max_idx = i; - } - } - return prediction[max_idx]; - } -}; -static_assert(sizeof(ModelOutputLeads) == (sizeof(ModelOutputLeadPrediction)*LEAD_MHP_N) + (sizeof(float)*LEAD_MHP_SELECTION)); - - -struct ModelOutputPose { - ModelOutputXYZ velocity_mean; - ModelOutputXYZ rotation_mean; - ModelOutputXYZ velocity_std; - ModelOutputXYZ rotation_std; -}; -static_assert(sizeof(ModelOutputPose) == sizeof(ModelOutputXYZ)*4); - -struct ModelOutputWideFromDeviceEuler { - ModelOutputXYZ mean; - ModelOutputXYZ std; -}; -static_assert(sizeof(ModelOutputWideFromDeviceEuler) == sizeof(ModelOutputXYZ)*2); - -struct ModelOutputTemporalPose { - ModelOutputXYZ velocity_mean; - ModelOutputXYZ rotation_mean; - ModelOutputXYZ velocity_std; - ModelOutputXYZ rotation_std; -}; -static_assert(sizeof(ModelOutputTemporalPose) == sizeof(ModelOutputXYZ)*4); - -struct ModelOutputRoadTransform { - ModelOutputXYZ position_mean; - ModelOutputXYZ rotation_mean; - ModelOutputXYZ position_std; - ModelOutputXYZ rotation_std; -}; -static_assert(sizeof(ModelOutputRoadTransform) == sizeof(ModelOutputXYZ)*4); - -struct ModelOutputDisengageProb { - float gas_disengage; - float brake_disengage; - float steer_override; - float brake_3ms2; - float brake_4ms2; - float brake_5ms2; - float gas_pressed; -}; -static_assert(sizeof(ModelOutputDisengageProb) == sizeof(float)*7); - -struct ModelOutputBlinkerProb { - float left; - float right; -}; -static_assert(sizeof(ModelOutputBlinkerProb) == sizeof(float)*2); - -struct ModelOutputDesireProb { - union { - struct { - float none; - float turn_left; - float turn_right; - float lane_change_left; - float lane_change_right; - float keep_left; - float keep_right; - float null; - }; - struct { - std::array array; - }; - }; -}; -static_assert(sizeof(ModelOutputDesireProb) == sizeof(float)*DESIRE_LEN); - -struct ModelOutputMeta { - ModelOutputDesireProb desire_state_prob; - float engaged_prob; - std::array disengage_prob; - std::array blinker_prob; - std::array desire_pred_prob; -}; -static_assert(sizeof(ModelOutputMeta) == sizeof(ModelOutputDesireProb) + sizeof(float) + (sizeof(ModelOutputDisengageProb)*DISENGAGE_LEN) + (sizeof(ModelOutputBlinkerProb)*BLINKER_LEN) + (sizeof(ModelOutputDesireProb)*DESIRE_PRED_LEN)); - -struct ModelOutputFeatures { - std::array feature; -}; -static_assert(sizeof(ModelOutputFeatures) == (sizeof(float)*FEATURE_LEN)); - -struct ModelOutput { - const ModelOutputPlans plans; - const ModelOutputLaneLines lane_lines; - const ModelOutputRoadEdges road_edges; - const ModelOutputLeads leads; - const ModelOutputMeta meta; - const ModelOutputPose pose; - const ModelOutputWideFromDeviceEuler wide_from_device_euler; - const ModelOutputTemporalPose temporal_pose; - const ModelOutputRoadTransform road_transform; -}; - -constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float); -constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + FEATURE_LEN + PAD_SIZE; - -struct PublishState { - std::array disengage_buffer = {}; - std::array prev_brake_5ms2_probs = {}; - std::array prev_brake_3ms2_probs = {}; -}; - -void fill_model_msg(MessageBuilder &msg, float *net_output_data, PublishState &ps, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, - uint64_t timestamp_eof, uint64_t timestamp_llk, float model_execution_time, const bool nav_enabled, const bool valid); -void fill_pose_msg(MessageBuilder &msg, float *net_outputs, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, uint64_t timestamp_eof, const bool valid); diff --git a/selfdrive/modeld/models/driving.pxd b/selfdrive/modeld/models/driving.pxd deleted file mode 100644 index 8d6b8d755e..0000000000 --- a/selfdrive/modeld/models/driving.pxd +++ /dev/null @@ -1,25 +0,0 @@ -# distutils: language = c++ - -from libcpp cimport bool -from libc.stdint cimport uint32_t, uint64_t - -cdef extern from "cereal/messaging/messaging.h": - cdef cppclass MessageBuilder: - size_t getSerializedSize() - int serializeToBuffer(unsigned char *, size_t) - -cdef extern from "selfdrive/modeld/models/driving.h": - cdef int FEATURE_LEN - cdef int HISTORY_BUFFER_LEN - cdef int DESIRE_LEN - cdef int TRAFFIC_CONVENTION_LEN - cdef int DRIVING_STYLE_LEN - cdef int NAV_FEATURE_LEN - cdef int NAV_INSTRUCTION_LEN - cdef int OUTPUT_SIZE - cdef int NET_OUTPUT_SIZE - cdef int MODEL_FREQ - cdef struct PublishState: pass - - void fill_model_msg(MessageBuilder, float *, PublishState, uint32_t, uint32_t, uint32_t, float, uint64_t, uint64_t, float, bool, bool) - void fill_pose_msg(MessageBuilder, float *, uint32_t, uint32_t, uint64_t, bool) diff --git a/selfdrive/modeld/models/driving_pyx.pyx b/selfdrive/modeld/models/driving_pyx.pyx deleted file mode 100644 index b98a8f3ff1..0000000000 --- a/selfdrive/modeld/models/driving_pyx.pyx +++ /dev/null @@ -1,52 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii - -import numpy as np -cimport numpy as cnp -from libcpp cimport bool -from libc.string cimport memcpy -from libc.stdint cimport uint32_t, uint64_t - -from .commonmodel cimport mat3 -from .driving cimport FEATURE_LEN as CPP_FEATURE_LEN, HISTORY_BUFFER_LEN as CPP_HISTORY_BUFFER_LEN, DESIRE_LEN as CPP_DESIRE_LEN, \ - TRAFFIC_CONVENTION_LEN as CPP_TRAFFIC_CONVENTION_LEN, DRIVING_STYLE_LEN as CPP_DRIVING_STYLE_LEN, \ - NAV_FEATURE_LEN as CPP_NAV_FEATURE_LEN, NAV_INSTRUCTION_LEN as CPP_NAV_INSTRUCTION_LEN, \ - OUTPUT_SIZE as CPP_OUTPUT_SIZE, NET_OUTPUT_SIZE as CPP_NET_OUTPUT_SIZE, MODEL_FREQ as CPP_MODEL_FREQ -from .driving cimport MessageBuilder, PublishState as cppPublishState -from .driving cimport fill_model_msg, fill_pose_msg - -FEATURE_LEN = CPP_FEATURE_LEN -HISTORY_BUFFER_LEN = CPP_HISTORY_BUFFER_LEN -DESIRE_LEN = CPP_DESIRE_LEN -TRAFFIC_CONVENTION_LEN = CPP_TRAFFIC_CONVENTION_LEN -DRIVING_STYLE_LEN = CPP_DRIVING_STYLE_LEN -NAV_FEATURE_LEN = CPP_NAV_FEATURE_LEN -NAV_INSTRUCTION_LEN = CPP_NAV_INSTRUCTION_LEN -OUTPUT_SIZE = CPP_OUTPUT_SIZE -NET_OUTPUT_SIZE = CPP_NET_OUTPUT_SIZE -MODEL_FREQ = CPP_MODEL_FREQ - -cdef class PublishState: - cdef cppPublishState state - -def create_model_msg(float[:] model_outputs, PublishState ps, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, - uint64_t timestamp_eof, uint64_t timestamp_llk, float model_execution_time, bool nav_enabled, bool valid): - cdef MessageBuilder msg - fill_model_msg(msg, &model_outputs[0], ps.state, vipc_frame_id, vipc_frame_id_extra, frame_id, frame_drop, - timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, valid) - - output_size = msg.getSerializedSize() - output_data = bytearray(output_size) - cdef unsigned char * output_ptr = output_data - assert msg.serializeToBuffer(output_ptr, output_size) > 0, "output buffer is too small to serialize" - return bytes(output_data) - -def create_pose_msg(float[:] model_outputs, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, uint64_t timestamp_eof, bool valid): - cdef MessageBuilder msg - fill_pose_msg(msg, &model_outputs[0], vipc_frame_id, vipc_dropped_frames, timestamp_eof, valid) - - output_size = msg.getSerializedSize() - output_data = bytearray(output_size) - cdef unsigned char * output_ptr = output_data - assert msg.serializeToBuffer(output_ptr, output_size) > 0, "output buffer is too small to serialize" - return bytes(output_data) diff --git a/selfdrive/modeld/navmodeld.py b/selfdrive/modeld/navmodeld.py index f5dc51ed20..90b9762800 100755 --- a/selfdrive/modeld/navmodeld.py +++ b/selfdrive/modeld/navmodeld.py @@ -13,13 +13,13 @@ from cereal.visionipc import VisionIpcClient, VisionStreamType from openpilot.system.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.realtime import set_realtime_priority -from openpilot.selfdrive.modeld.constants import IDX_N +from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime NAV_INPUT_SIZE = 256*256 NAV_FEATURE_LEN = 256 NAV_DESIRE_LEN = 32 -NAV_OUTPUT_SIZE = 2*2*IDX_N + NAV_DESIRE_LEN + NAV_FEATURE_LEN +NAV_OUTPUT_SIZE = 2*2*ModelConstants.IDX_N + NAV_DESIRE_LEN + NAV_FEATURE_LEN MODEL_PATHS = { ModelRunner.SNPE: Path(__file__).parent / 'models/navmodel_q.dlc', ModelRunner.ONNX: Path(__file__).parent / 'models/navmodel.onnx'} @@ -31,8 +31,8 @@ class NavModelOutputXY(ctypes.Structure): class NavModelOutputPlan(ctypes.Structure): _fields_ = [ - ("mean", NavModelOutputXY*IDX_N), - ("std", NavModelOutputXY*IDX_N)] + ("mean", NavModelOutputXY*ModelConstants.IDX_N), + ("std", NavModelOutputXY*ModelConstants.IDX_N)] class NavModelResult(ctypes.Structure): _fields_ = [ diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py new file mode 100644 index 0000000000..a7b160fcea --- /dev/null +++ b/selfdrive/modeld/parse_model_outputs.py @@ -0,0 +1,100 @@ +import numpy as np +from typing import Dict +from openpilot.selfdrive.modeld.constants import ModelConstants + +def sigmoid(x): + return 1. / (1. + np.exp(-x)) + +def softmax(x, axis=-1): + x -= np.max(x, axis=axis, keepdims=True) + if x.dtype == np.float32 or x.dtype == np.float64: + np.exp(x, out=x) + else: + x = np.exp(x) + x /= np.sum(x, axis=axis, keepdims=True) + return x + +class Parser: + def __init__(self, ignore_missing=False): + self.ignore_missing = ignore_missing + + def check_missing(self, outs, name): + if name not in outs and not self.ignore_missing: + raise ValueError(f"Missing output {name}") + return name not in outs + + def parse_categorical_crossentropy(self, name, outs, out_shape=None): + if self.check_missing(outs, name): + return + raw = outs[name] + if out_shape is not None: + raw = raw.reshape((raw.shape[0],) + out_shape) + outs[name] = softmax(raw, axis=-1) + + def parse_binary_crossentropy(self, name, outs): + if self.check_missing(outs, name): + return + raw = outs[name] + outs[name] = sigmoid(raw) + + def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=None): + if self.check_missing(outs, name): + return + raw = outs[name] + raw = raw.reshape((raw.shape[0], max(in_N, 1), -1)) + + pred_mu = raw[:,:,:(raw.shape[2] - out_N)//2] + n_values = (raw.shape[2] - out_N)//2 + pred_mu = raw[:,:,:n_values] + pred_std = np.exp(raw[:,:,n_values: 2*n_values]) + + if in_N > 1: + weights = np.zeros((raw.shape[0], in_N, out_N), dtype=raw.dtype) + for i in range(out_N): + weights[:,:,i - out_N] = softmax(raw[:,:,i - out_N], axis=-1) + + if out_N == 1: + for fidx in range(weights.shape[0]): + idxs = np.argsort(weights[fidx][:,0])[::-1] + weights[fidx] = weights[fidx][idxs] + pred_mu[fidx] = pred_mu[fidx][idxs] + pred_std[fidx] = pred_std[fidx][idxs] + full_shape = tuple([raw.shape[0], in_N] + list(out_shape)) + outs[name + '_weights'] = weights + outs[name + '_hypotheses'] = pred_mu.reshape(full_shape) + outs[name + '_stds_hypotheses'] = pred_std.reshape(full_shape) + + pred_mu_final = np.zeros((raw.shape[0], out_N, n_values), dtype=raw.dtype) + pred_std_final = np.zeros((raw.shape[0], out_N, n_values), dtype=raw.dtype) + for fidx in range(weights.shape[0]): + for hidx in range(out_N): + idxs = np.argsort(weights[fidx,:,hidx])[::-1] + pred_mu_final[fidx, hidx] = pred_mu[fidx, idxs[0]] + pred_std_final[fidx, hidx] = pred_std[fidx, idxs[0]] + else: + pred_mu_final = pred_mu + pred_std_final = pred_std + + if out_N > 1: + final_shape = tuple([raw.shape[0], out_N] + list(out_shape)) + else: + final_shape = tuple([raw.shape[0],] + list(out_shape)) + outs[name] = pred_mu_final.reshape(final_shape) + outs[name + '_stds'] = pred_std_final.reshape(final_shape) + + def parse_outputs(self, outs: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]: + self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION, + out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH)) + self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) + self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) + self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) + self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) + 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)) + for k in ['lead_prob', 'lane_lines_prob', 'meta']: + self.parse_binary_crossentropy(k, outs) + self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) + self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) + return outs diff --git a/selfdrive/modeld/thneed/lib.py b/selfdrive/modeld/thneed/lib.py deleted file mode 100644 index c058638fad..0000000000 --- a/selfdrive/modeld/thneed/lib.py +++ /dev/null @@ -1,32 +0,0 @@ -import struct -import json - -def load_thneed(fn): - with open(fn, "rb") as f: - json_len = struct.unpack("I", f.read(4))[0] - jdat = json.loads(f.read(json_len).decode('latin_1')) - weights = f.read() - ptr = 0 - for o in jdat['objects']: - if o['needs_load']: - nptr = ptr + o['size'] - o['data'] = weights[ptr:nptr] - ptr = nptr - for o in jdat['binaries']: - nptr = ptr + o['length'] - o['data'] = weights[ptr:nptr] - ptr = nptr - return jdat - -def save_thneed(jdat, fn): - new_weights = [] - for o in jdat['objects'] + jdat['binaries']: - if 'data' in o: - new_weights.append(o['data']) - del o['data'] - new_weights_bytes = b''.join(new_weights) - with open(fn, "wb") as f: - j = json.dumps(jdat, ensure_ascii=False).encode('latin_1') - f.write(struct.pack("I", len(j))) - f.write(j) - f.write(new_weights_bytes) diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index 0dce1a0f9b..bb935fdc8e 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -6,7 +6,7 @@ from cereal import log import cereal.messaging as messaging from openpilot.common.realtime import Ratekeeper, DT_MDL from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState -from openpilot.selfdrive.modeld.constants import T_IDXS +from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU @@ -100,13 +100,13 @@ class Plant: # this is to ensure lead policy is effective when model # does not predict slowdown in e2e mode position = log.XYZTData.new_message() - position.x = [float(x) for x in (self.speed + 0.5) * np.array(T_IDXS)] + position.x = [float(x) for x in (self.speed + 0.5) * np.array(ModelConstants.T_IDXS)] model.modelV2.position = position velocity = log.XYZTData.new_message() - velocity.x = [float(x) for x in (self.speed + 0.5) * np.ones_like(T_IDXS)] + velocity.x = [float(x) for x in (self.speed + 0.5) * np.ones_like(ModelConstants.T_IDXS)] model.modelV2.velocity = velocity acceleration = log.XYZTData.new_message() - acceleration.x = [float(x) for x in np.zeros_like(T_IDXS)] + acceleration.x = [float(x) for x in np.zeros_like(ModelConstants.T_IDXS)] model.modelV2.acceleration = acceleration control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index 6992d46f1d..bec3eb9016 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -17,7 +17,6 @@ Currently the following processes are tested: * locationd * paramsd * ubloxd -* laikad * torqued ### Usage @@ -71,7 +70,7 @@ lr = LogReader(...) output_logs = replay_process_with_name('locationd', lr) # or list of names -output_logs = replay_process_with_name(['ubloxd', 'locationd', 'laikad'], lr) +output_logs = replay_process_with_name(['ubloxd', 'locationd'], lr) ``` Supported processes: @@ -83,7 +82,6 @@ Supported processes: * locationd * paramsd * ubloxd -* laikad * torqued * modeld * dmonitoringmodeld diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index 4a395e4d62..dbb7c223f5 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -90,6 +90,26 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non return diff +def format_process_diff(diff): + diff_short, diff_long = "", "" + + if isinstance(diff, str): + diff_short += f" {diff}\n" + diff_long += f"\t{diff}\n" + else: + cnt: Dict[str, int] = {} + for d in diff: + diff_long += f"\t{str(d)}\n" + + k = str(d[1]) + cnt[k] = 1 if k not in cnt else cnt[k] + 1 + + for k, v in sorted(cnt.items()): + diff_short += f" {k}: {v}\n" + + return diff_short, diff_long + + def format_diff(results, log_paths, ref_commit): diff_short, diff_long = "", "" diff_long += f"***** tested against commit {ref_commit} *****\n" @@ -105,25 +125,17 @@ def format_diff(results, log_paths, ref_commit): diff_long += f"\tnew: {log_paths[segment][proc]['new']}\n\n" diff_short += f" {proc}\n" - if isinstance(diff, str): + + if isinstance(diff, str) or len(diff): diff_short += f" ref: {log_paths[segment][proc]['ref']}\n" diff_short += f" new: {log_paths[segment][proc]['new']}\n\n" - diff_short += f" {diff}\n" failed = True - elif len(diff): - diff_short += f" ref: {log_paths[segment][proc]['ref']}\n" - diff_short += f" new: {log_paths[segment][proc]['new']}\n\n" - cnt: Dict[str, int] = {} - for d in diff: - diff_long += f"\t{str(d)}\n" + proc_diff_short, proc_diff_long = format_process_diff(diff) - k = str(d[1]) - cnt[k] = 1 if k not in cnt else cnt[k] + 1 + diff_long += proc_diff_long + diff_short += proc_diff_short - for k, v in sorted(cnt.items()): - diff_short += f" {k}: {v}\n" - failed = True return diff_short, diff_long, failed diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 626af12661..2a4113a77f 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -f851c7e7f90eff828a59444d20fac5df8cd7ae0c +0e0f55cf3bb2cf79b44adf190e6387a83deb6646 diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 9aab541991..0cb8ac9cac 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -448,16 +448,6 @@ def controlsd_config_callback(params, cfg, lr): params.put("ReplayControlsState", controlsState.as_builder().to_bytes()) -def laikad_config_pubsub_callback(params, cfg, lr): - ublox = params.get_bool("UbloxAvailable") - main_key = "ubloxGnss" if ublox else "qcomGnss" - sub_keys = ({"qcomGnss", } if ublox else {"ubloxGnss", }) - - cfg.pubs = set(cfg.pubs) - sub_keys - cfg.main_pub = main_key - cfg.main_pub_drained = True - - def locationd_config_pubsub_callback(params, cfg, lr): ublox = params.get_bool("UbloxAvailable") sub_keys = ({"gpsLocation", } if ublox else {"gpsLocationExternal", }) @@ -543,17 +533,6 @@ CONFIGS = [ subs=["ubloxGnss", "gpsLocationExternal"], ignore=["logMonoTime"], ), - ProcessConfig( - proc_name="laikad", - pubs=["ubloxGnss", "qcomGnss"], - subs=["gnssMeasurements"], - ignore=["logMonoTime"], - config_callback=laikad_config_pubsub_callback, - tolerance=NUMPY_TOLERANCE, - processing_time=0.002, - timeout=60*10, # first messages are blocked on internet assistance - main_pub="ubloxGnss", # config_callback will switch this to qcom if needed - ), ProcessConfig( proc_name="torqued", pubs=["liveLocationKalman", "carState", "carControl"], diff --git a/selfdrive/test/process_replay/test_fuzzy.py b/selfdrive/test/process_replay/test_fuzzy.py index f3f48d0159..4b8629fc7d 100755 --- a/selfdrive/test/process_replay/test_fuzzy.py +++ b/selfdrive/test/process_replay/test_fuzzy.py @@ -13,7 +13,7 @@ import openpilot.selfdrive.test.process_replay.process_replay as pr # These processes currently fail because of unrealistic data breaking assumptions # that openpilot makes causing error with NaN, inf, int size, array indexing ... # TODO: Make each one testable -NOT_TESTED = ['controlsd', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'laikad', 'dmonitoringmodeld', 'modeld'] +NOT_TESTED = ['controlsd', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'dmonitoringmodeld', 'modeld'] TEST_CASES = [(cfg.proc_name, copy.deepcopy(cfg)) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED] class TestFuzzProcesses(unittest.TestCase): diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py index 1d380ba5eb..6d0cc204c5 100755 --- a/selfdrive/test/profiling/profiler.py +++ b/selfdrive/test/profiling/profiler.py @@ -83,14 +83,12 @@ if __name__ == '__main__': from openpilot.selfdrive.controls.radard import radard_thread from openpilot.selfdrive.locationd.paramsd import main as paramsd_thread from openpilot.selfdrive.controls.plannerd import main as plannerd_thread - from openpilot.selfdrive.locationd.laikad import main as laikad_thread procs = { 'radard': radard_thread, 'controlsd': controlsd_thread, 'paramsd': paramsd_thread, 'plannerd': plannerd_thread, - 'laikad': laikad_thread, } proc = sys.argv[1] diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 230a96ddad..e4f6bccf6c 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import bz2 import math import json import os @@ -36,7 +37,7 @@ PROCS = { "selfdrive.locationd.paramsd": 9.0, "./sensord": 7.0, "selfdrive.controls.radard": 4.5, - "selfdrive.modeld.modeld": 8.0, + "selfdrive.modeld.modeld": 13.0, "selfdrive.modeld.dmonitoringmodeld": 8.0, "selfdrive.modeld.navmodeld": 1.0, "selfdrive.thermald.thermald": 3.87, @@ -46,7 +47,6 @@ PROCS = { "selfdrive.monitoring.dmonitoringd": 4.0, "./proclogd": 1.54, "system.logmessaged": 0.2, - "./clocksd": 0.02, "selfdrive.tombstoned": 0, "./logcatd": 0, "system.micd": 10.0, @@ -56,7 +56,6 @@ PROCS = { "selfdrive.navd.navd": 0.4, "system.loggerd.uploader": 3.0, "system.loggerd.deleter": 0.1, - "selfdrive.locationd.laikad": (1.0, 80.0), # TODO: better GPS setup in testing closet } PROCS.update({ @@ -161,6 +160,7 @@ class TestOnroad(unittest.TestCase): # use the second segment by default as it's the first full segment cls.lr = list(LogReader(os.path.join(str(cls.segments[1]), "rlog"))) + cls.log_path = cls.segments[1] @cached_property def service_msgs(self): @@ -191,6 +191,26 @@ class TestOnroad(unittest.TestCase): big_logs = [f for f, n in cnt.most_common(3) if n / sum(cnt.values()) > 30.] self.assertEqual(len(big_logs), 0, f"Log spam: {big_logs}") + def test_log_sizes(self): + for f in self.log_path.iterdir(): + assert f.is_file() + + sz = f.stat().st_size / 1e6 + if f.name in ("qlog", "rlog"): + with open(f, 'rb') as ff: + sz = len(bz2.compress(ff.read())) / 1e6 + + if f.name == "qcamera.ts": + assert 2.15 < sz < 2.35 + elif f.name == "qlog": + assert 0.7 < sz < 1.0 + elif f.name == "rlog": + assert 5 < sz < 50 + elif f.name.endswith('.hevc'): + assert 70 < sz < 77 + else: + raise NotImplementedError + def test_ui_timings(self): result = "\n" result += "------------------------------------------------\n" diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 8157066334..326cb4fa56 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -8,7 +8,6 @@ from azure.storage.blob import ContainerClient from tqdm import tqdm from openpilot.selfdrive.car.tests.routes import routes as test_car_models_routes -from openpilot.selfdrive.locationd.test.test_laikad import UBLOX_TEST_ROUTE, QCOM_TEST_ROUTE from openpilot.selfdrive.test.process_replay.test_processes import source_segments as replay_segments from openpilot.selfdrive.test.openpilotci import (DATA_CI_ACCOUNT, DATA_CI_ACCOUNT_URL, DATA_CI_CONTAINER, get_azure_credential, get_container_sas) @@ -90,7 +89,6 @@ if __name__ == "__main__": if not len(to_sync): # sync routes from the car tests routes and process replay - to_sync.extend([UBLOX_TEST_ROUTE, QCOM_TEST_ROUTE]) to_sync.extend([rt.route for rt in test_car_models_routes]) to_sync.extend([s[1].rsplit('--', 1)[0] for s in replay_segments]) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index a274b342fc..b320c3ca69 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -25,8 +25,14 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", "qt/widgets/offroad_alerts.cc", "qt/widgets/prime.cc", "qt/widgets/keyboard.cc", "qt/widgets/scrollview.cc", "qt/widgets/cameraview.cc", "#third_party/qrcode/QrCode.cc", - "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc", - "qt/offroad/sunnypilot_settings.cc"] + "qt/request_repeater.cc", "qt/qt_window.cc", "qt/network/networking.cc", "qt/network/wifi_manager.cc"] + +widgets_src += ["qt/offroad/sunnypilot/display_settings.cc", "qt/offroad/sunnypilot/sunnypilot_settings.cc", + "qt/offroad/sunnypilot/vehicle_settings.cc", "qt/offroad/sunnypilot/visuals_settings.cc", + "qt/offroad/sunnypilot/trips_settings.cc", "qt/offroad/sunnypilot/mads_settings.cc", + "qt/offroad/sunnypilot/lane_change_settings.cc", "qt/offroad/sunnypilot/monitoring_settings.cc", + "qt/offroad/sunnypilot/osm_settings.cc", "qt/offroad/sunnypilot/custom_offsets_settings.cc", + "qt/widgets/sunnypilot/drive_stats.cc"] qt_env['CPPDEFINES'] = [] if maps: diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index 57c9973add..1b481b64b1 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -130,6 +130,18 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid list->addItem(tetheringToggle); QObject::connect(tetheringToggle, &ToggleControl::toggleFlipped, this, &AdvancedNetworking::toggleTethering); + hotspotOnBootToggle = new ToggleControl( + tr("Retain hotspot/tethering state"), + tr("Enabling this toggle will retain the hotspot/tethering toggle state across reboots."), + "", + params.getBool("HotspotOnBoot") + ); + hotspotOnBootToggle->setEnabled(wifi->isTetheringEnabled() || set_hotspot_on_boot); + QObject::connect(hotspotOnBootToggle, &ToggleControl::toggleFlipped, [=](bool state) { + params.putBool("HotspotOnBoot", state); + }); + list->addItem(hotspotOnBootToggle); + // Change tethering password ButtonControl *editPasswordButton = new ButtonControl(tr("Tethering Password"), tr("EDIT")); connect(editPasswordButton, &ButtonControl::clicked, [=]() { @@ -205,6 +217,11 @@ void AdvancedNetworking::toggleTethering(bool enabled) { params.putBool("HotspotOnBootConfirmed", enabled); wifi->setTetheringEnabled(enabled); tetheringToggle->setEnabled(false); + + hotspotOnBootToggle->setEnabled(enabled); + if (!enabled) { + params.remove("HotspotOnBoot"); + } } // WifiUI functions diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h index 4ff7380f42..2fc7807bdc 100644 --- a/selfdrive/ui/qt/network/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -66,6 +66,8 @@ private: WifiManager* wifi = nullptr; Params params; + ToggleControl* hotspotOnBootToggle; + signals: void backPress(); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 4eb6118b1b..c992c8d3ec 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -17,7 +17,7 @@ #include "common/watchdog.h" #include "common/util.h" #include "system/hardware/hw.h" -#include "selfdrive/ui/qt/offroad/sunnypilot_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot_main.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/scrollview.h" @@ -34,7 +34,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "OpenpilotEnabledToggle", tr("Enable sunnypilot"), tr("Use the sunnypilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), - "../assets/offroad/icon_openpilot.png", + "../assets/offroad/icon_blank.png", }, { "ExperimentalLongitudinalEnabled", @@ -43,13 +43,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { .arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")), - "../assets/offroad/icon_speed_limit.png", + "../assets/offroad/icon_blank.png", }, { "CustomStockLong", tr("Custom Stock Longitudinal Control"), tr("When enabled, sunnypilot will attempt to control stock longitudinal control with ACC button presses.\nThis feature must be used along with SLC, and/or V-TSC, and/or M-TSC."), - "../assets/offroad/icon_speed_limit.png", + "../assets/offroad/icon_blank.png", }, { "CustomStockLongPlanner", @@ -61,55 +61,74 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "ExperimentalMode", tr("Experimental Mode"), "", - "../assets/img_experimental_white.svg", + "../assets/offroad/icon_blank.png", + }, + { + "DynamicExperimentalControl", + tr("Enable Dynamic Experimental Control"), + tr("Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal."), + "../assets/offroad/icon_blank.png", }, { "DisengageOnAccelerator", tr("Disengage on Accelerator Pedal"), tr("When enabled, pressing the accelerator pedal will disengage openpilot."), - "../assets/offroad/icon_disengage_on_accelerator.svg", + "../assets/offroad/icon_blank.png", }, { "IsLdwEnabled", tr("Enable Lane Departure Warnings"), tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."), - "../assets/offroad/icon_warning.png", + "../assets/offroad/icon_blank.png", }, { "RecordFront", tr("Record and Upload Driver Camera"), tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), - "../assets/offroad/icon_monitoring.png", + "../assets/offroad/icon_blank.png", + }, + { + "DisableOnroadUploads", + tr("Disable Onroad Uploads"), + tr("Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC)."), + "../assets/offroad/icon_blank.png", + }, + { + "EnableDebugSnapshot", + tr("Debug snapshot on screen center tap"), + tr("Stores snapshot file with current state of some modules."), + "../assets/offroad/icon_blank.png" }, { "IsMetric", tr("Use Metric System"), tr("Display speed in km/h instead of mph."), - "../assets/offroad/icon_metric.png", + "../assets/offroad/icon_blank.png", }, #ifdef ENABLE_MAPS { "NavSettingTime24h", tr("Show ETA in 24h Format"), tr("Use 24h format instead of am/pm"), - "../assets/offroad/icon_metric.png", + "../assets/offroad/icon_blank.png", }, { "NavSettingLeftSide", tr("Show Map on Left Side of UI"), tr("Show map on left side when in split screen view."), - "../assets/offroad/icon_road.png", + "../assets/offroad/icon_blank.png", }, #endif }; - std::vector longi_button_texts{tr("Maniac"), tr("Aggro"), tr("Stock"), tr("Relaxed")}; + std::vector longi_button_texts{tr("Maniac"), tr("Aggressive"), tr("Stock"), tr("Relaxed")}; long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), tr("Stock is recommended. In aggressive/maniac mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " "In relaxed mode openpilot will stay further away from lead cars."), - "../assets/offroad/icon_speed_limit.png", - longi_button_texts); + "../assets/offroad/icon_blank.png", + longi_button_texts, + 380); long_personality_setting->showDescription(); for (auto &[param, title, desc, icon] : toggle_defs) { auto toggle = new ParamControl(param, title, desc, icon, this); @@ -127,7 +146,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { } // Toggles with confirmation dialogs - toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg"); + //toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg"); toggles["ExperimentalMode"]->setConfirmation(true, true); toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false); toggles["CustomStockLong"]->setConfirmation(true, false); @@ -142,7 +161,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { param_watcher = new ParamWatcher(this); QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { - updateButtons(); + updateToggles(); }); } @@ -152,13 +171,19 @@ void TogglesPanel::expandToggleDescription(const QString ¶m) { void TogglesPanel::showEvent(QShowEvent *event) { updateToggles(); - updateButtons(); } void TogglesPanel::updateToggles() { + param_watcher->addParam("LongitudinalPersonality"); + + if (!isVisible()) return; + + long_personality_setting->setButton("LongitudinalPersonality"); + auto experimental_mode_toggle = toggles["ExperimentalMode"]; auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; auto custom_stock_long_toggle = toggles["CustomStockLong"]; + auto dec_toggle = toggles["DynamicExperimentalControl"]; auto custom_stock_long_planner = toggles["CustomStockLongPlanner"]; const QString e2e_description = QString("%1
" "

%2


" @@ -193,7 +218,7 @@ void TogglesPanel::updateToggles() { op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release); if (!CP.getCustomStockLongAvailable()) { - params.remove("CustomStockLongControl"); + params.remove("CustomStockLong"); } custom_stock_long_toggle->setVisible(CP.getCustomStockLongAvailable()); @@ -202,14 +227,18 @@ void TogglesPanel::updateToggles() { experimental_mode_toggle->setEnabled(true); experimental_mode_toggle->setDescription(e2e_description); long_personality_setting->setEnabled(true); + op_long_toggle->setEnabled(true); custom_stock_long_toggle->setEnabled(false); params.remove("CustomStockLong"); + dec_toggle->setEnabled(true); } else if (custom_stock_long_toggle->isToggled()) { + op_long_toggle->setEnabled(false); experimental_mode_toggle->setEnabled(true); experimental_mode_toggle->setDescription(e2e_description); long_personality_setting->setEnabled(false); - op_long_toggle->setEnabled(false); custom_stock_long_planner->setEnabled(true); + params.remove("ExperimentalLongitudinalEnabled"); + params.remove("ExperimentalMode"); } else { // no long for now experimental_mode_toggle->setEnabled(false); @@ -229,31 +258,26 @@ void TogglesPanel::updateToggles() { } experimental_mode_toggle->setDescription("" + long_desc + "

" + e2e_description); - custom_stock_long_toggle->setEnabled(CP.getCustomStockLongAvailable()); - custom_stock_long_planner->setEnabled(false); - op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release); op_long_toggle->setEnabled(CP.getExperimentalLongitudinalAvailable() && !is_release); + custom_stock_long_toggle->setEnabled(CP.getCustomStockLongAvailable()); + dec_toggle->setEnabled(false); + custom_stock_long_planner->setEnabled(false); + params.remove("DynamicExperimentalControl"); } experimental_mode_toggle->refresh(); - custom_stock_long_toggle->refresh(); op_long_toggle->refresh(); + custom_stock_long_toggle->refresh(); + dec_toggle->refresh(); } else { experimental_mode_toggle->setDescription(e2e_description); op_long_toggle->setVisible(false); custom_stock_long_toggle->setVisible(false); + dec_toggle->setVisible(false); custom_stock_long_planner->setVisible(false); } } -void TogglesPanel::updateButtons() { - param_watcher->addParam("LongitudinalPersonality"); - - if (!isVisible()) return; - - long_personality_setting->setButton("LongitudinalPersonality"); -} - DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { setSpacing(50); addItem(new LabelControl(tr("Dongle ID"), getDongleId().value_or(tr("N/A")))); @@ -528,34 +552,42 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { TogglesPanel *toggles = new TogglesPanel(this); QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); - QList> panels = { - {tr(" Device"), device}, - {tr(" Network"), new Networking(this)}, - {tr(" Toggles"), toggles}, - {tr(" Software"), new SoftwarePanel(this)}, + QList panels = { + PanelInfo(" " + tr("Device"), device, "../assets/navigation/icon_home.svg"), + PanelInfo(" " + tr("Network"), new Networking(this), "../assets/offroad/icon_network.png"), + PanelInfo(" " + tr("Toggles"), toggles, "../assets/offroad/icon_toggle.png"), + PanelInfo(" " + tr("Software"), new SoftwarePanel(this), "../assets/offroad/icon_software.png"), + PanelInfo(" " + tr("sunnypilot"), new SunnypilotPanel(this), "../assets/offroad/icon_openpilot.png"), + PanelInfo(" " + tr("OSM"), new OsmPanel(this), "../assets/offroad/icon_map.png"), + PanelInfo(" " + tr("Monitoring"), new MonitoringPanel(this), "../assets/offroad/icon_monitoring.png"), + PanelInfo(" " + tr("Visuals"), new VisualsPanel(this), "../assets/offroad/icon_visuals.png"), + PanelInfo(" " + tr("Display"), new DisplayPanel(this), "../assets/offroad/icon_display.png"), + PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../assets/offroad/icon_trips.png"), + PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../assets/offroad/icon_vehicle.png"), }; - panels.push_back({tr(" SP - General"), new SPGeneralPanel(this)}); - panels.push_back({tr(" SP - Controls"), new SPControlsPanel(this)}); - panels.push_back({tr(" SP - Vehicles"), new SPVehiclesPanel(this)}); - panels.push_back({tr(" SP - Visuals"), new SPVisualsPanel(this)}); - nav_btns = new QButtonGroup(this); - for (auto &[name, panel] : panels) { + for (auto &[name, panel, icon] : panels) { QPushButton *btn = new QPushButton(name); btn->setCheckable(true); btn->setChecked(nav_btns->buttons().size() == 0); - btn->setIcon(QIcon(QPixmap("../assets/offroad/icon_blank.png"))); - btn->setIconSize(QSize(45, 45)); + btn->setIcon(QIcon(QPixmap(icon))); + btn->setIconSize(QSize(70, 70)); btn->setStyleSheet(R"( QPushButton { - color: grey; + border-radius: 20px; + width: 400px; + height: 98px; + color: #bdbdbd; border: none; background: none; font-size: 50px; font-weight: 500; + text-align: left; + padding-left: 22px; } QPushButton:checked { + background-color: #696868; color: white; } QPushButton:pressed { @@ -565,9 +597,8 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); nav_btns->addButton(btn); buttons_layout->addWidget(btn, 0, Qt::AlignLeft | Qt::AlignBottom); - buttons_layout->addSpacing(25); - const int lr_margin = name != tr("Network") ? 50 : 0; // Network panel handles its own margins + const int lr_margin = (name != (" " + tr("Network")) || (name != (" " + tr("sunnypilot")))) ? 50 : 0; // Network and sunnypilot panel handles its own margins panel->setContentsMargins(lr_margin, 25, lr_margin, 25); ScrollView *panel_frame = new ScrollView(panel, this); @@ -578,7 +609,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { panel_widget->setCurrentWidget(w); }); } - sidebar_layout->setContentsMargins(50, 50, 50, 50); + sidebar_layout->setContentsMargins(50, 50, 25, 50); // main settings layout, sidebar + main panel QHBoxLayout *main_layout = new QHBoxLayout(this); @@ -603,7 +634,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { background-color: black; } QStackedWidget, ScrollView { - background-color: #292929; + background-color: black; border-radius: 30px; } )"); diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index bce7f19295..ae86093129 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -37,6 +37,14 @@ private: QWidget *sidebar_widget; QButtonGroup *nav_btns; QStackedWidget *panel_widget; + + struct PanelInfo { + QString name; + QWidget *widget; + QString icon; + + PanelInfo(const QString &name, QWidget *widget, const QString &icon) : name(name), widget(widget), icon(icon) {} + }; }; class DevicePanel : public ListWidget { @@ -79,7 +87,6 @@ private: ButtonParamControl *long_personality_setting; ParamWatcher *param_watcher; - void updateButtons(); }; class SoftwarePanel : public ListWidget { @@ -95,124 +102,12 @@ private: bool is_onroad = false; QLabel *onroadLbl; - QLabel *osmUpdateLbl; LabelControl *currentModelLbl; LabelControl *versionLbl; ButtonControl *installBtn; ButtonControl *downloadBtn; ButtonControl *targetBranchBtn; - ButtonControl *osmDownloadBtn; - ButtonControl *osmUpdateBtn; Params params; ParamWatcher *fs_watch; - - inline std::vector> getOsmLocations() { - // location_title, location_name, waypoint, url - std::vector> locations{ - { - "== None ==", - "", - "", - "", - }, - { - "Australia", - "australia", - "514911884", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EathcAhPP8dHiTdBiFPIGYoB7zwlUR79OkSTH5dnS-9Shg?e=9cS6er&download=1", - }, - { - "Brazil", - "brazil", - "124199196", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EXWcrd8ukahDtoFDXVOxvkYBjGGa9b29fVvRdJlADv3YdA?e=jwabo2&download=1", - }, - { - "Canada", - "canada", - "68588664", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/ETpwTMZftJpCkep2goPmzUQBl5SW_YfHBKLTd7w5CUagkA?e=dshC10&download=1", - }, - { - "GCC States", - "gcc-states", - "69021390", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EWtFOVR9mj5CoRpATYR0_tUBWNEcy5jeasp8zO0ZYdduRg?e=Cg0l2Z&download=1", - }, - { - "Germany", - "germany", - "461526153", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EbNzQuA8jHBCuGch1WY4cuoBJfK1S6m5odj5qMnOu3HA3Q?e=cNDn30&download=1", - }, - { - "Malaysia, Singapore, Brunei", - "malaysia-singapore-brunei", - "1112741782", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EQb9bq_YkE1NuFx0jp-52zoBnAbdo4nyLXMnEY1pUbCB8g?e=CoFiUJ&download=1", - }, - { - "New Zealand", - "new-zealand", - "154430132", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EVI6eMuNa4dJpXg-r92Vw1EBq5RxR83_7Z5C56AYftg8xQ?e=xouRBl&download=1", - }, - { - "South Africa", - "south-africa", - "2729449", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EQzbZeus-yBNsgP1KQyLn9wBYZVMMknVoHdB7_ewulLbdA?e=DrnKkb&download=1", - }, - { - "Taiwan", - "taiwan", - "198637969", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EXgP3Z5_lsBPiQe-aXOOSY4Bu-r54Z_3kKKmT8-IqLO42A?e=tOhUYw&download=1", - }, - { - "Turkey", - "turkey", - "698359658", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EWJRrYpV7I1IvOW0LUWrDsUBjEjuKRTla0hA4yRbsVX2xw?e=RGcu8n&download=1", - }, - { - "US - Florida", - "florida", - "147221754", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/ET9rAvCaretAnO4TjocAddkBFxAziblxblAn9YWzGk5hTw?e=tts5Oz&download=1", - }, - { - "US - Midwest", - "us-midwest", - "1059596607", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EaDYnugxokRHhOukMndJuZMBKz-kzPfM6C_-iQcki-r94Q?e=oUX6hz&download=1", - }, - { - "US - Northeast", - "us-northeast", - "575213527", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/ERrlIKNuT4ZIuUOfr4a3BwABLfd7X1wSq9nUrk9buKrIgA?e=HHIT8R&download=1", - }, - { - "US - Pacific", - "us-pacific", - "112909709", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EQOzoMksk9tAluahQ2qm3rQBHOZD03qFU8Aw4o-TFUTuJA?e=CcBOxL&download=1", - }, - { - "US - South", - "us-south", - "243729876", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/ETizacxh8lFNk8NMVRyKCEsBMMYZl1iq0r-D4IWoOAd_DQ?e=EVIElm&download=1", - }, - { - "US - West", - "us-west", - "30023440", - "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/ETTRiSKwlEVKoRHtylruy7wBKgITKXdaCUAkJMuGAN2_mA?e=7jlglL&download=1", - }, - }; - return locations; - } }; diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index fb07ebd90e..4980d70ea5 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -76,54 +76,6 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { }); addItem(targetBranchBtn); - osmUpdateLbl = new QLabel(tr("Database updates can be downloaded while the car is off.")); - osmUpdateLbl->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; padding-top: 30px; padding-bottom: 30px;"); - addItem(osmUpdateLbl); - - osmUpdateBtn = new ButtonControl(tr("OpenStreetMap Database Update"), tr("CHECK")); - connect(osmUpdateBtn, &ButtonControl::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot and perform a database update?\nEstimated time: 30-90 minutes"), tr("Reboot"), parent)) { - params.putBool("OsmDbUpdatesCheck", true); - Hardware::reboot(); - } - }); - addItem(osmUpdateBtn); - - osmDownloadBtn = new ButtonControl(tr("OpenStreetMap Database"), tr("SELECT")); - connect(osmDownloadBtn, &ButtonControl::clicked, [=]() { - std::vector> locations = getOsmLocations(); - QString initTitle = QString::fromStdString(params.get("OsmLocationTitle")); - QString currentTitle = ((initTitle == "== None ==") || (initTitle.length() == 0)) ? "== None ==" : initTitle; - - QStringList locationTitles; - for (auto& loc : locations) { - locationTitles.push_back(std::get<0>(loc)); - } - - QString selection = MultiOptionDialog::getSelection(tr("Select your location"), locationTitles, currentTitle, this); - if (!selection.isEmpty()) { - params.put("OsmLocal", "1"); - params.put("OsmLocationTitle", selection.toStdString()); - for (auto& loc : locations) { - if (std::get<0>(loc) == selection) { - params.put("OsmLocationName", std::get<1>(loc).toStdString()); - params.put("OsmWayTest", std::get<2>(loc).toStdString()); - params.put("OsmLocationUrl", std::get<3>(loc).toStdString()); - break; - } - } - osmDownloadBtn->setValue(selection); - if (selection != "== None ==") { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot to start downloading the selected database? Estimated time: 30-90 minutes"), tr("Reboot"), parent)) { - params.putBool("OsmDbUpdatesCheck", true); - Hardware::reboot(); - } - } - } - updateLabels(); - }); - addItem(osmDownloadBtn); - // uninstall button auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL")); connect(uninstallBtn, &ButtonControl::clicked, [&]() { @@ -168,16 +120,6 @@ void SoftwarePanel::updateLabels() { onroadLbl->setVisible(is_onroad); downloadBtn->setVisible(!is_onroad); - QString name = QString::fromStdString(params.get("OsmLocationName")); - if (!name.isEmpty()) { - osmUpdateBtn->setVisible(!is_onroad); - } else { - params.remove("OsmLocal"); - osmUpdateBtn->setVisible(false); - } - osmUpdateLbl->setVisible(is_onroad); - osmDownloadBtn->setEnabled(!is_onroad); - // download update QString updater_state = QString::fromStdString(params.get("UpdaterState")); bool failed = std::atoi(params.get("UpdateFailedCount").c_str()) > 0; @@ -203,7 +145,6 @@ void SoftwarePanel::updateLabels() { downloadBtn->setEnabled(true); } targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch"))); - osmDownloadBtn->setValue(QString::fromStdString(params.get("OsmLocationTitle"))); // current + new versions versionLbl->setText(QString::fromStdString(params.get("UpdaterCurrentDescription"))); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.cc new file mode 100644 index 0000000000..405eca50ce --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.cc @@ -0,0 +1,68 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h" + +CustomOffsetsSettings::CustomOffsetsSettings(QWidget* parent) : QWidget(parent) { + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(50, 20, 50, 20); + main_layout->setSpacing(20); + + // Back button + PanelBackButton* back = new PanelBackButton(tr("Back")); + connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); + main_layout->addWidget(back, 0, Qt::AlignLeft); + + ListWidget *list = new ListWidget(this, false); + + // Controls: Camera Offset (cm) + camera_offset = new CameraOffset(); + connect(camera_offset, &SPOptionControl::updateLabels, camera_offset, &CameraOffset::refresh); + camera_offset->showDescription(); + list->addItem(camera_offset); + + // Controls: Path Offset (cm) + path_offset = new PathOffset(); + connect(path_offset, &SPOptionControl::updateLabels, path_offset, &PathOffset::refresh); + path_offset->showDescription(); + list->addItem(path_offset); + + main_layout->addWidget(new ScrollView(list, this)); +} + +// Camera Offset Value +CameraOffset::CameraOffset() : SPOptionControl ( + "CameraOffset", + tr("Camera Offset - Laneful Only"), + tr("Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm"), + "../assets/offroad/icon_blank.png", + {-20, 20}) { + + refresh(); +} + +void CameraOffset::refresh() { + QString option = QString::fromStdString(params.get("CameraOffset")); + QString unit = tr(" cm"); + if (option.toInt() >= 0) { + option = "+" + option; + } + setLabel(option + unit); +} + +// Path Offset Value +PathOffset::PathOffset() : SPOptionControl ( + "PathOffset", + tr("Path Offset"), + tr("Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately."), + "../assets/offroad/icon_blank.png", + {-10, 10}) { + + refresh(); +} + +void PathOffset::refresh() { + QString option = QString::fromStdString(params.get("PathOffset")); + QString unit = tr(" cm"); + if (option.toInt() >= 0) { + option = "+" + option; + } + setLabel(option + unit); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h new file mode 100644 index 0000000000..63a5a817be --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h @@ -0,0 +1,45 @@ +#pragma once + +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +class CameraOffset : public SPOptionControl { + Q_OBJECT + +public: + CameraOffset(); + + void refresh(); + +private: + Params params; +}; + +class PathOffset : public SPOptionControl { + Q_OBJECT + +public: + PathOffset(); + + void refresh(); + +private: + Params params; +}; + +class CustomOffsetsSettings : public QWidget { + Q_OBJECT + +public: + explicit CustomOffsetsSettings(QWidget* parent = nullptr); + +signals: + void backPress(); + +private: + Params params; + std::map toggles; + + CameraOffset *camera_offset; + PathOffset *path_offset; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc new file mode 100644 index 0000000000..3d83fce252 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/display_settings.cc @@ -0,0 +1,185 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/display_settings.h" + +DisplayPanel::DisplayPanel(QWidget *parent) : ListWidget(parent, false) { + // param, title, desc, icon + std::vector> toggle_defs{ + { + "OnroadScreenOffEvent", + tr("Driving Screen Off: Non-Critical Events"), + QString("%1
" + "

%2


" + "

%3


") + .arg(tr("When Driving Screen Off Timer is not set to \"Always On\":")) + .arg(tr("Enabled: Wake the brightness of the screen to display all events.")) + .arg(tr("Disabled: Wake the brightness of the screen to display critical events.")), + "../assets/offroad/icon_blank.png", + }, + { + "ScreenRecorder", + tr("Enable Screen Recorder"), + tr("Enable this will display a button on the onroad screen to toggle on or off real-time screen recording with UI elements."), + "../assets/offroad/icon_blank.png" + } + }; + + // General: Max Time Offroad (Shutdown timer) + auto max_time_offroad = new MaxTimeOffroad(); + connect(max_time_offroad, &SPOptionControl::updateLabels, max_time_offroad, &MaxTimeOffroad::refresh); + addItem(max_time_offroad); + + // General: Onroad Screen Off (Auto Onroad Screen Timer) + onroad_screen_off = new OnroadScreenOff(); + onroad_screen_off->setUpdateOtherToggles(true); + connect(onroad_screen_off, &SPOptionControl::updateLabels, onroad_screen_off, &OnroadScreenOff::refresh); + connect(onroad_screen_off, &SPOptionControl::updateOtherToggles, this, &DisplayPanel::updateToggles); + addItem(onroad_screen_off); + + // General: Onroad Screen Off Brightness + onroad_screen_off_brightness = new OnroadScreenOffBrightness(); + connect(onroad_screen_off_brightness, &SPOptionControl::updateLabels, onroad_screen_off_brightness, &OnroadScreenOffBrightness::refresh); + addItem(onroad_screen_off_brightness); + + // General: Brightness Control (Global) + auto brightness_control = new BrightnessControl(); + connect(brightness_control, &SPOptionControl::updateLabels, brightness_control, &BrightnessControl::refresh); + + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, this); + + addItem(toggle); + toggles[param.toStdString()] = toggle; + + if (param == "OnroadScreenOffEvent") { + // General: Brightness Control (Global) + addItem(brightness_control); + } + } +} + +void DisplayPanel::showEvent(QShowEvent *event) { + updateToggles(); +} + +void DisplayPanel::updateToggles() { + if (!isVisible()) { + return; + } + + // toggle names to update when OnroadScreenOff is toggled + onroad_screen_off_brightness->setVisible(QString::fromStdString(params.get("OnroadScreenOff")) != "-2"); + toggles["OnroadScreenOffEvent"]->setVisible(QString::fromStdString(params.get("OnroadScreenOff")) != "-2"); +} + +// Max Time Offroad (Shutdown timer) +MaxTimeOffroad::MaxTimeOffroad() : SPOptionControl ( + "MaxTimeOffroad", + tr("Max Time Offroad"), + tr("Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road)."), + "../assets/offroad/icon_blank.png", + {0, 12}) { + + refresh(); +} + +void MaxTimeOffroad::refresh() { + QString option = QString::fromStdString(params.get("MaxTimeOffroad")); + QString second = tr("s"); + QString minute = tr("m"); + QString hour = tr("hr"); + if (option == "0") { + setLabel(tr("Always On")); + } else if (option == "1") { + setLabel(tr("Immediate")); + } else if (option == "2") { + setLabel("30 " + second); + } else if (option == "3") { + setLabel("1 " + minute); + } else if (option == "4") { + setLabel("3 " + minute); + } else if (option == "5") { + setLabel("5 " + minute); + } else if (option == "6") { + setLabel("10 " + minute); + } else if (option == "7") { + setLabel("30 " + minute); + } else if (option == "8") { + setLabel("1 " + hour); + } else if (option == "9") { + setLabel("3 " + hour); + } else if (option == "10") { + setLabel("5 " + hour); + } else if (option == "11") { + setLabel("10 " + hour); + } else if (option == "12") { + setLabel("30 " + hour); + } +} + +// Onroad Screen Off (Auto Onroad Screen Timer) +OnroadScreenOff::OnroadScreenOff() : SPOptionControl ( + "OnroadScreenOff", + tr("Driving Screen Off Timer"), + tr("Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs."), + "../assets/offroad/icon_blank.png", + {-2, 10}) { + + refresh(); +} + +void OnroadScreenOff::refresh() { + QString option = QString::fromStdString(params.get("OnroadScreenOff")); + QString second = tr("s"); + QString minute = tr("min"); + if (option == "-2") { + setLabel(tr("Always On")); + } else if (option == "-1") { + setLabel("15 " + second); + } else if (option == "0") { + setLabel("30 " + second); + } else { + setLabel(option + " " + minute); + } +} + +// Onroad Screen Off Brightness +OnroadScreenOffBrightness::OnroadScreenOffBrightness() : SPOptionControl ( + "OnroadScreenOffBrightness", + tr("Driving Screen Off Brightness (%)"), + tr("When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio."), + "../assets/offroad/icon_blank.png", + {0, 100}, + 10) { + + refresh(); +} + +void OnroadScreenOffBrightness::refresh() { + QString option = QString::fromStdString(params.get("OnroadScreenOffBrightness")); + if (option == "0") { + setLabel(tr("Dark")); + } else { + setLabel(option); + } +} + +// Brightness Control (Global) +BrightnessControl::BrightnessControl() : SPOptionControl ( + "BrightnessControl", + tr("Brightness"), + tr("Manually adjusts the global brightness of the screen."), + "../assets/offroad/icon_blank.png", + {0, 100}, + 5) { + + refresh(); +} + +void BrightnessControl::refresh() { + QString option = QString::fromStdString(params.get("BrightnessControl")); + QString percentage = " %"; + if (option == "0") { + setLabel(tr("Auto")); + } else { + setLabel(option + percentage); + } +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/display_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/display_settings.h new file mode 100644 index 0000000000..b8083b53fc --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/display_settings.h @@ -0,0 +1,69 @@ +#pragma once + +#include "selfdrive/ui/qt/widgets/controls.h" + +class OnroadScreenOff : public SPOptionControl { + Q_OBJECT + +public: + OnroadScreenOff(); + + void refresh(); + +private: + Params params; +}; + +class OnroadScreenOffBrightness : public SPOptionControl { + Q_OBJECT + +public: + OnroadScreenOffBrightness(); + + void refresh(); + +private: + Params params; +}; + +class MaxTimeOffroad : public SPOptionControl { + Q_OBJECT + +public: + MaxTimeOffroad(); + + void refresh(); + +private: + Params params; +}; + +class BrightnessControl : public SPOptionControl { + Q_OBJECT + +public: + BrightnessControl(); + + void refresh(); + +private: + Params params; +}; + +class DisplayPanel : public ListWidget { + Q_OBJECT + +public: + explicit DisplayPanel(QWidget *parent = nullptr); + void showEvent(QShowEvent *event) override; + +public slots: + void updateToggles(); + +private: + Params params; + std::map toggles; + + OnroadScreenOff *onroad_screen_off; + OnroadScreenOffBrightness *onroad_screen_off_brightness; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc new file mode 100644 index 0000000000..f4050e161b --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.cc @@ -0,0 +1,107 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h" + +LaneChangeSettings::LaneChangeSettings(QWidget* parent) : QWidget(parent) { + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(50, 20, 50, 20); + main_layout->setSpacing(20); + + // Back button + PanelBackButton* back = new PanelBackButton(tr("Back")); + connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); + main_layout->addWidget(back, 0, Qt::AlignLeft); + + ListWidget *list = new ListWidget(this, false); + // param, title, desc, icon + std::vector> toggle_defs{ + { + "AutoLaneChangeBsmDelay", + tr("Auto Lane Change: Delay with Blind Spot"), + tr("Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering."), + "../assets/offroad/icon_blank.png", + }, + { + "RoadEdge", + tr("Block Lane Change: Road Edge Detection"), + tr("Enable this toggle to block lane change when road edge is detected on the stalk actuated side."), + "../assets/offroad/icon_blank.png", + } + }; + + // Controls: Auto Lane Change Timer + auto_lane_change_timer = new AutoLaneChangeTimer(); + auto_lane_change_timer->setUpdateOtherToggles(true); + auto_lane_change_timer->showDescription(); + connect(auto_lane_change_timer, &SPOptionControl::updateLabels, auto_lane_change_timer, &AutoLaneChangeTimer::refresh); + connect(auto_lane_change_timer, &AutoLaneChangeTimer::updateOtherToggles, this, &LaneChangeSettings::updateToggles); + list->addItem(auto_lane_change_timer); + + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, this); + + list->addItem(toggle); + toggles[param.toStdString()] = toggle; + + // trigger offroadTransition when going onroad/offroad + connect(uiState(), &UIState::offroadTransition, toggle, &ParamControl::setEnabled); + } + + main_layout->addWidget(new ScrollView(list, this)); +} + +void LaneChangeSettings::showEvent(QShowEvent *event) { + updateToggles(); +} + +void LaneChangeSettings::updateToggles() { + if (!isVisible()) { + return; + } + + auto auto_lane_change_bsm_delay_toggle = toggles["AutoLaneChangeBsmDelay"]; + auto auto_lane_change_timer_param = QString::fromStdString(params.get("AutoLaneChangeTimer")); + + auto cp_bytes = params.get("CarParamsPersistent"); + if (!cp_bytes.empty()) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); + cereal::CarParams::Reader CP = cmsg.getRoot(); + + if (!CP.getEnableBsm()) { + params.remove("AutoLaneChangeBsmDelay"); + } + auto_lane_change_bsm_delay_toggle->setEnabled(CP.getEnableBsm() && auto_lane_change_timer_param != "0"); + + auto_lane_change_bsm_delay_toggle->refresh(); + } else { + auto_lane_change_bsm_delay_toggle->setEnabled(false); + } +} + +// Auto Lane Change Timer (ALCT) +AutoLaneChangeTimer::AutoLaneChangeTimer() : SPOptionControl ( + "AutoLaneChangeTimer", + tr("Auto Lane Change Timer"), + tr("Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set.\nPlease use caution when using this feature. Only use the blinker when traffic and road conditions permit."), + "../assets/offroad/icon_blank.png", + {0, 5}) { + + refresh(); +} + +void AutoLaneChangeTimer::refresh() { + QString option = QString::fromStdString(params.get("AutoLaneChangeTimer")); + QString second = tr("s"); + if (option == "0") { + setLabel(tr("Nudge")); + } else if (option == "1") { + setLabel(tr("Nudgeless")); + } else if (option == "2") { + setLabel("0.5 " + second); + } else if (option == "3") { + setLabel("1 " + second); + } else if (option == "4") { + setLabel("1.5 " + second); + } else { + setLabel("2 " + second); + } +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h new file mode 100644 index 0000000000..86bdd3ede3 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h @@ -0,0 +1,40 @@ +#pragma once + +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +class AutoLaneChangeTimer : public SPOptionControl { + Q_OBJECT + +public: + AutoLaneChangeTimer(); + + void refresh(); + +signals: + void toggleUpdated(); + +private: + Params params; +}; + +class LaneChangeSettings : public QWidget { + Q_OBJECT + +public: + explicit LaneChangeSettings(QWidget* parent = nullptr); + void showEvent(QShowEvent *event) override; + +signals: + void backPress(); + +public slots: + void updateToggles(); + +private: + Params params; + std::map toggles; + + AutoLaneChangeTimer *auto_lane_change_timer; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc new file mode 100644 index 0000000000..f28479aa3c --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.cc @@ -0,0 +1,76 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h" + +MadsSettings::MadsSettings(QWidget* parent) : QWidget(parent) { + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(50, 20, 50, 20); + main_layout->setSpacing(20); + + // Back button + PanelBackButton* back = new PanelBackButton(); + connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); + main_layout->addWidget(back, 0, Qt::AlignLeft); + + ListWidget *list = new ListWidget(this, false); + // param, title, desc, icon + std::vector> toggle_defs{ + { + "AccMadsCombo", + tr("Enable ACC+MADS with RES+/SET-"), + QString("%1
" + "

%2

") + .arg(tr("Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button.")) + .arg(tr("Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off.")), + "../assets/offroad/icon_blank.png", + }, + { + "MadsCruiseMain", + tr("Toggle M.A.D.S. with Cruise Main"), + tr("Allows M.A.D.S. engagement/disengagement with \"Cruise Main\" cruise control button from the steering wheel."), + "../assets/offroad/icon_blank.png", + } + }; + + // Disengage Lateral on Brake (DLOB) + std::vector dlob_settings_texts{tr("Remain Active"), tr("Pause Steering")}; + dlob_settings = new ButtonParamControl( + "DisengageLateralOnBrake", "Steering Mode After Braking", "Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\nRemain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed.", + "../assets/offroad/icon_blank.png", + dlob_settings_texts, + 500 + ); + dlob_settings->showDescription(); + list->addItem(dlob_settings); + + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, this); + + list->addItem(toggle); + toggles[param.toStdString()] = toggle; + + // trigger offroadTransition when going onroad/offroad + connect(uiState(), &UIState::offroadTransition, toggle, &ParamControl::setEnabled); + } + + // trigger offroadTransition when going onroad/offroad + connect(uiState(), &UIState::offroadTransition, dlob_settings, &ButtonParamControl::setEnabled); + + main_layout->addWidget(new ScrollView(list, this)); +} + +void MadsSettings::showEvent(QShowEvent *event) { + updateToggles(); +} + +void MadsSettings::updateToggles() { + if (!isVisible()) { + return; + } + + const bool is_offroad = !uiState()->scene.started; + const bool enable_mads = params.getBool("EnableMads"); + const bool enabled = is_offroad && enable_mads; + + toggles["AccMadsCombo"]->setEnabled(enabled); + toggles["MadsCruiseMain"]->setEnabled(enabled); + dlob_settings->setEnabled(enabled); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h new file mode 100644 index 0000000000..2a949c97a4 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h @@ -0,0 +1,25 @@ +#pragma once + +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +class MadsSettings : public QWidget { + Q_OBJECT + +public: + explicit MadsSettings(QWidget* parent = nullptr); + void showEvent(QShowEvent *event) override; + +signals: + void backPress(); + +public slots: + void updateToggles(); + +private: + Params params; + std::map toggles; + + ButtonParamControl *dlob_settings; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc new file mode 100644 index 0000000000..e2a410f9ff --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.cc @@ -0,0 +1,30 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h" + +MonitoringPanel::MonitoringPanel(QWidget *parent) : QFrame(parent) { + main_layout = new QStackedLayout(this); + + ListWidget *list = new ListWidget(this, false); + // param, title, desc, icon + std::vector> toggle_defs{ + { + "HandsOnWheelMonitoring", + tr("Enable Hands on Wheel Monitoring"), + tr("Monitor and alert when driver is not keeping the hands on the steering wheel."), + "../assets/offroad/icon_blank.png", + } + }; + + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, this); + + list->addItem(toggle); + toggles[param.toStdString()] = toggle; + } + + monitoringScreen = new QWidget(this); + QVBoxLayout* vlayout = new QVBoxLayout(monitoringScreen); + vlayout->setContentsMargins(50, 20, 50, 20); + + vlayout->addWidget(new ScrollView(list, this), 1); + main_layout->addWidget(monitoringScreen); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h new file mode 100644 index 0000000000..214d2f8ac0 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h @@ -0,0 +1,17 @@ +#pragma once + +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +class MonitoringPanel : public QFrame { + Q_OBJECT + +public: + explicit MonitoringPanel(QWidget *parent = nullptr); + +private: + QStackedLayout* main_layout = nullptr; + QWidget* monitoringScreen = nullptr; + Params params; + std::map toggles; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.cc new file mode 100644 index 0000000000..e7d6112ab9 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.cc @@ -0,0 +1,109 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h" + +OsmPanel::OsmPanel(QWidget *parent) : QFrame(parent) { + main_layout = new QStackedLayout(this); + + ListWidget *list = new ListWidget(this, false); + // param, title, desc, icon + std::vector> toggle_defs{ + { + "OsmLocalDb", + tr("Use Offline Database"), + "", + "../assets/offroad/icon_blank.png", + } + }; + + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, this); + + list->addItem(toggle); + toggles[param.toStdString()] = toggle; + } + + osmUpdateLbl = new QLabel(tr("Database updates can be downloaded while the car is off.")); + osmUpdateLbl->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; padding-top: 30px; padding-bottom: 30px;"); + list->addItem(osmUpdateLbl); + + osmUpdateBtn = new ButtonControl(tr("OpenStreetMap Database Update"), tr("CHECK")); + connect(osmUpdateBtn, &ButtonControl::clicked, [=]() { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot and perform a database update?\nEstimated time: 30-90 minutes"), tr("Reboot"), parent)) { + params.putBool("OsmDbUpdatesCheck", true); + Hardware::reboot(); + } + }); + list->addItem(osmUpdateBtn); + + osmDownloadBtn = new ButtonControl(tr("OpenStreetMap Database"), tr("SELECT")); + connect(osmDownloadBtn, &ButtonControl::clicked, [=]() { + std::vector> locations = getOsmLocations(); + QString initTitle = QString::fromStdString(params.get("OsmLocationTitle")); + QString currentTitle = ((initTitle == "== None ==") || (initTitle.length() == 0)) ? "== None ==" : initTitle; + + QStringList locationTitles; + for (auto& loc : locations) { + locationTitles.push_back(std::get<0>(loc)); + } + + QString selection = MultiOptionDialog::getSelection(tr("Select your location"), locationTitles, currentTitle, this); + if (!selection.isEmpty()) { + params.put("OsmLocal", "1"); + params.put("OsmLocationTitle", selection.toStdString()); + for (auto& loc : locations) { + if (std::get<0>(loc) == selection) { + params.put("OsmLocationName", std::get<1>(loc).toStdString()); + params.put("OsmWayTest", std::get<2>(loc).toStdString()); + params.put("OsmLocationUrl", std::get<3>(loc).toStdString()); + break; + } + } + osmDownloadBtn->setValue(selection); + if (selection != "== None ==") { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot to start downloading the selected database? Estimated time: 30-90 minutes"), tr("Reboot"), parent)) { + params.putBool("OsmDbUpdatesCheck", true); + Hardware::reboot(); + } + } + } + updateLabels(); + }); + list->addItem(osmDownloadBtn); + + connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + is_onroad = !offroad; + updateLabels(); + }); + + updateLabels(); + + osmScreen = new QWidget(this); + QVBoxLayout* vlayout = new QVBoxLayout(osmScreen); + vlayout->setContentsMargins(50, 20, 50, 20); + + vlayout->addWidget(new ScrollView(list, this), 1); + main_layout->addWidget(osmScreen); +} + +void OsmPanel::showEvent(QShowEvent *event) { + updateLabels(); +} + +void OsmPanel::updateLabels() { + if (!isVisible()) { + return; + } + + QString name = QString::fromStdString(params.get("OsmLocationName")); + if (!name.isEmpty()) { + osmUpdateBtn->setVisible(!is_onroad); + } else { + params.remove("OsmLocal"); + osmUpdateBtn->setVisible(false); + } + osmUpdateLbl->setVisible(is_onroad); + osmDownloadBtn->setEnabled(!is_onroad); + + osmDownloadBtn->setValue(QString::fromStdString(params.get("OsmLocationTitle"))); + + update(); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h new file mode 100644 index 0000000000..29af9f492e --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h @@ -0,0 +1,142 @@ +#pragma once + +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +class OsmPanel : public QFrame { + Q_OBJECT + +public: + explicit OsmPanel(QWidget *parent = nullptr); + +private: + QStackedLayout* main_layout = nullptr; + QWidget* osmScreen = nullptr; + Params params; + std::map toggles; + + void showEvent(QShowEvent *event) override; + void updateLabels(); + + bool is_onroad = false; + + QLabel *osmUpdateLbl; + ButtonControl *osmDownloadBtn; + ButtonControl *osmUpdateBtn; + + inline std::vector> getOsmLocations() { + // location_title, location_name, waypoint, url + std::vector> locations{ + { + "== None ==", + "", + "", + "", + }, + { + "Australia", + "australia", + "514911884", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EathcAhPP8dHiTdBiFPIGYoB7zwlUR79OkSTH5dnS-9Shg?e=9cS6er&download=1", + }, + { + "Brazil", + "brazil", + "124199196", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EXWcrd8ukahDtoFDXVOxvkYBjGGa9b29fVvRdJlADv3YdA?e=jwabo2&download=1", + }, + { + "Canada", + "canada", + "68588664", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/ETpwTMZftJpCkep2goPmzUQBl5SW_YfHBKLTd7w5CUagkA?e=dshC10&download=1", + }, + { + "GCC States", + "gcc-states", + "69021390", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EWtFOVR9mj5CoRpATYR0_tUBWNEcy5jeasp8zO0ZYdduRg?e=Cg0l2Z&download=1", + }, + { + "Germany", + "germany", + "461526153", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EbNzQuA8jHBCuGch1WY4cuoBJfK1S6m5odj5qMnOu3HA3Q?e=cNDn30&download=1", + }, + { + "Malaysia, Singapore, Brunei", + "malaysia-singapore-brunei", + "1112741782", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EQb9bq_YkE1NuFx0jp-52zoBnAbdo4nyLXMnEY1pUbCB8g?e=CoFiUJ&download=1", + }, + { + "New Zealand", + "new-zealand", + "154430132", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EVI6eMuNa4dJpXg-r92Vw1EBq5RxR83_7Z5C56AYftg8xQ?e=xouRBl&download=1", + }, + { + "South Africa", + "south-africa", + "2729449", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EQzbZeus-yBNsgP1KQyLn9wBYZVMMknVoHdB7_ewulLbdA?e=DrnKkb&download=1", + }, + { + "Spain", + "spain", + "4263034", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/Ee21EfLqZxxLjksDCB_2dscBXKN9Kch8ZOYz3cIWnVpBTg?e=LWBJ1n&download=1", + }, + { + "Taiwan", + "taiwan", + "198637969", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EXgP3Z5_lsBPiQe-aXOOSY4Bu-r54Z_3kKKmT8-IqLO42A?e=tOhUYw&download=1", + }, + { + "Turkey", + "turkey", + "698359658", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EWJRrYpV7I1IvOW0LUWrDsUBjEjuKRTla0hA4yRbsVX2xw?e=RGcu8n&download=1", + }, + { + "US - Florida", + "florida", + "147221754", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/ET9rAvCaretAnO4TjocAddkBFxAziblxblAn9YWzGk5hTw?e=tts5Oz&download=1", + }, + { + "US - Midwest", + "us-midwest", + "1059596607", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EaDYnugxokRHhOukMndJuZMBKz-kzPfM6C_-iQcki-r94Q?e=oUX6hz&download=1", + }, + { + "US - Northeast", + "us-northeast", + "575213527", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/ERrlIKNuT4ZIuUOfr4a3BwABLfd7X1wSq9nUrk9buKrIgA?e=HHIT8R&download=1", + }, + { + "US - Pacific", + "us-pacific", + "112909709", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/EQOzoMksk9tAluahQ2qm3rQBHOZD03qFU8Aw4o-TFUTuJA?e=CcBOxL&download=1", + }, + { + "US - South", + "us-south", + "243729876", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/ETizacxh8lFNk8NMVRyKCEsBMMYZl1iq0r-D4IWoOAd_DQ?e=EVIElm&download=1", + }, + { + "US - West", + "us-west", + "30023440", + "https://thecorgirosie-my.sharepoint.com/:u:/g/personal/databases_sunnypilot_com/ETTRiSKwlEVKoRHtylruy7wBKgITKXdaCUAkJMuGAN2_mA?e=7jlglL&download=1", + }, + }; + return locations; + } +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc new file mode 100644 index 0000000000..da69255846 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc @@ -0,0 +1,502 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h" + +SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { + main_layout = new QStackedLayout(this); + + ListWidget *list = new ListWidget(this, false); + // param, title, desc, icon + std::vector> toggle_defs{ + { + "EnableMads", + tr("Enable M.A.D.S."), + tr("Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement."), + "../assets/offroad/icon_blank.png", + }, + { + "VisionCurveLaneless", + tr("Laneless for Curves in \"Auto\" Mode"), + tr("While in Auto Lane, switch to Laneless for current/future curves."), + "../assets/offroad/icon_blank.png", + }, + { + "CustomOffsets", + tr("Custom Offsets"), + tr("Add custom offsets to Camera and Path in sunnypilot."), + "../assets/offroad/icon_blank.png", + }, + { + "EnforceTorqueLateral", + tr("Enforce Torque Lateral Control"), + tr("Enable this to enforce sunnypilot to steer with Torque lateral control."), + "../assets/offroad/icon_blank.png", + }, + { + "LiveTorque", + tr("Enable Self-Tune"), + tr("Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default."), + "../assets/offroad/icon_blank.png", + }, + { + "LiveTorqueRelaxed", + tr("Less Restrict Settings for Self-Tune (Beta)"), + tr("Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values."), + "../assets/offroad/icon_blank.png", + }, + { + "CustomTorqueLateral", + tr("Enable Custom Tuning"), + tr("Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within \"selfdrive/torque_data\". The values will also be used live when \"Override Self-Tune\" toggle is enabled."), + "../assets/offroad/icon_blank.png", + }, + { + "TorquedOverride", + tr("Manual Real-Time Tuning"), + tr("Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values."), + "../assets/offroad/icon_blank.png", + }, + { + "TurnVisionControl", + tr("Enable Vision Based Turn Speed Control (V-TSC)"), + tr("Use vision path predictions to estimate the appropriate speed to drive through turns ahead."), + "../assets/offroad/icon_blank.png", + }, + { + "TurnSpeedControl", + tr("Enable Map Data Turn Speed Control (M-TSC)"), + tr("Use curvature information from map data to define speed limits to take turns ahead."), + "../assets/offroad/icon_blank.png", + }, + { + "ReverseAccChange", + tr("ACC +/-: Long Press Reverse"), + QString("%1
" + "

%2


" + "

%3


") + .arg(tr("Change the ACC +/- buttons behavior with cruise speed change in sunnypilot.")) + .arg(tr("Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric)")) + .arg(tr("Enabled: Short = 5 (imperial) / 10 (metric), Long=1")), + "../assets/offroad/icon_blank.png", + }, + { + "QuietDrive", + tr("Quiet Drive 🤫"), + tr("sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on."), + "../assets/offroad/icon_blank.png", + }, + { + "EndToEndLongAlertLight", + tr("Green Traffic Light Chime (Beta)"), + QString("%1
" + "

%2


") + .arg(tr("A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged.")) + .arg(tr("Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly.")), + "../assets/offroad/icon_blank.png", + }, + { + "EndToEndLongAlertLead", + tr("Lead Vehicle Departure Alert"), + QString("%1
" + "

%2


") + .arg(tr("Enable this will notify when the leading vehicle drives away.")) + .arg(tr("Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly.")), + "../assets/offroad/icon_blank.png", + } + }; + + // M.A.D.S. Settings + SubPanelButton *madsSettings = new SubPanelButton(tr("Customize M.A.D.S.")); + madsSettings->setObjectName("mads_btn"); + // Set margin on the outside of the button + QVBoxLayout* madsSettingsLayout = new QVBoxLayout; + madsSettingsLayout->setContentsMargins(0, 0, 0, 30); + madsSettingsLayout->addWidget(madsSettings); + connect(madsSettings, &QPushButton::clicked, [=]() { + scrollView->setLastScrollPosition(); + main_layout->setCurrentWidget(mads_settings); + }); + + mads_settings = new MadsSettings(this); + connect(mads_settings, &MadsSettings::backPress, [=]() { + scrollView->restoreScrollPosition(); + main_layout->setCurrentWidget(sunnypilotScreen); + }); + + // Lane Change Settings + SubPanelButton *laneChangeSettings = new SubPanelButton(tr("Customize Lane Change")); + laneChangeSettings->setObjectName("lane_change_btn"); + // Set margin on the outside of the button + QVBoxLayout* laneChangeSettingsLayout = new QVBoxLayout; + laneChangeSettingsLayout->setContentsMargins(0, 0, 0, 30); + laneChangeSettingsLayout->addWidget(laneChangeSettings); + connect(laneChangeSettings, &QPushButton::clicked, [=]() { + scrollView->setLastScrollPosition(); + main_layout->setCurrentWidget(lane_change_settings); + }); + + lane_change_settings = new LaneChangeSettings(this); + connect(lane_change_settings, &LaneChangeSettings::backPress, [=]() { + scrollView->restoreScrollPosition(); + main_layout->setCurrentWidget(sunnypilotScreen); + }); + + // Custom Offsets Settings + SubPanelButton *customOffsetsSettings = new SubPanelButton(tr("Customize Offsets")); + customOffsetsSettings->setObjectName("custom_offsets_btn"); + // Set margin on the outside of the button + QVBoxLayout* customOffsetsSettingsLayout = new QVBoxLayout; + customOffsetsSettingsLayout->setContentsMargins(0, 0, 0, 30); + customOffsetsSettingsLayout->addWidget(customOffsetsSettings); + connect(customOffsetsSettings, &QPushButton::clicked, [=]() { + scrollView->setLastScrollPosition(); + main_layout->setCurrentWidget(custom_offsets_settings); + }); + + custom_offsets_settings = new CustomOffsetsSettings(this); + connect(custom_offsets_settings, &CustomOffsetsSettings::backPress, [=]() { + scrollView->restoreScrollPosition(); + main_layout->setCurrentWidget(sunnypilotScreen); + }); + + // toggle names to trigger updateToggles() when toggleFlipped + std::vector updateTogglesNames{ + "EnforceTorqueLateral", "CustomTorqueLateral", "LiveTorque", "TorquedOverride" + }; + + // toggle for offroadTransition when going onroad/offroad + std::vector toggleOffroad{ + "EnableMads", "BelowSpeedPause", "EnforceTorqueLateral", "LiveTorqueRelaxed" + }; + + // Controls: Speed Limit Offset Values (% or actual value) + slvo = new SpeedLimitValueOffset(); + connect(slvo, &SPOptionControl::updateLabels, slvo, &SpeedLimitValueOffset::refresh); + + // Controls: Torque - FRICTION + friction = new TorqueFriction(); + connect(friction, &SPOptionControl::updateLabels, friction, &TorqueFriction::refresh); + + // Controls: Torque - LAT_ACCEL_FACTOR + lat_accel_factor = new TorqueMaxLatAccel(); + connect(lat_accel_factor, &SPOptionControl::updateLabels, lat_accel_factor, &TorqueMaxLatAccel::refresh); + + std::vector dlp_settings_texts{tr("Laneful"), tr("Laneless"), tr("Auto")}; + dlp_settings = new ButtonParamControl( + "DynamicLaneProfile", "Dynamic Lane Profile", "Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions.", + "../assets/offroad/icon_blank.png", + dlp_settings_texts, + 340 + ); + dlp_settings->showDescription(); + + std::vector speed_limit_control_settings_texts{tr("Disabled"), tr("Enabled")}; + speed_limit_control_settings = new ButtonParamControl( + "SpeedLimitControl", "Speed Limit Control (SLC)", "When SLC is Enabled, it uses map data and car interface for speed adjustment.", + "../assets/offroad/icon_blank.png", + speed_limit_control_settings_texts, + 340 + ); + speed_limit_control_settings->showDescription(); + connect(speed_limit_control_settings, &ButtonParamControl::buttonToggled, this, &SunnypilotPanel::updateToggles); + + std::vector speed_limit_offset_settings_texts{tr("Default"), tr("Fixed"), tr("Percentage")}; + speed_limit_offset_settings = new ButtonParamControl( + "SpeedLimitOffsetType", "Offset", "Set speed limit slightly higher than actual speed limit for a more natural drive.", + "../assets/offroad/icon_blank.png", + speed_limit_offset_settings_texts, + 380 + ); + connect(speed_limit_offset_settings, &ButtonParamControl::buttonToggled, this, &SunnypilotPanel::updateToggles); + connect(speed_limit_offset_settings, &ButtonParamControl::buttonToggled, slvo, &SpeedLimitValueOffset::refresh); + + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, this); + + list->addItem(toggle); + toggles[param.toStdString()] = toggle; + + if (param == "EnableMads") { + list->addItem(madsSettingsLayout); + + list->addItem(horizontal_line()); + + // Controls: Dynamic Lane Profile group + list->addItem(dlp_settings); + } + + if (param == "VisionCurveLaneless") { + list->addItem(laneChangeSettingsLayout); + list->addItem(horizontal_line()); + } + + if (param == "CustomOffsets") { + list->addItem(customOffsetsSettingsLayout); + list->addItem(horizontal_line()); + } + + if (param == "TorquedOverride") { + // Control: FRICTION + list->addItem(friction); + + // Controls: LAT_ACCEL_FACTOR + list->addItem(lat_accel_factor); + + list->addItem(horizontal_line()); + + // Controls: Speed Limit Control + list->addItem(speed_limit_control_settings); + + // Controls: Speed Limit Offset Type + list->addItem(speed_limit_offset_settings); + + // Controls: Speed Limit Offset Values (% or actual value) + list->addItem(slvo); + + list->addItem(horizontal_line()); + } + + if (param == "ReverseAccChange") { + list->addItem(horizontal_line()); + } + + // trigger updateToggles() when toggleFlipped + if (std::find(updateTogglesNames.begin(), updateTogglesNames.end(), param.toStdString()) != updateTogglesNames.end()) { + connect(toggle, &ToggleControl:: toggleFlipped, [=](bool state) { + updateToggles(); + }); + } + + // trigger offroadTransition when going onroad/offroad + if (std::find(toggleOffroad.begin(), toggleOffroad.end(), param.toStdString()) != toggleOffroad.end()) { + connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + toggle->setEnabled(offroad); + }); + } + } + + toggles["EnableMads"]->setConfirmation(true, false); + toggles["EndToEndLongAlertLight"]->setConfirmation(true, false); + + connect(toggles["EnableMads"], &ToggleControl::toggleFlipped, mads_settings, &MadsSettings::updateToggles); + connect(toggles["EnableMads"], &ToggleControl::toggleFlipped, [=](bool state) { + madsSettings->setEnabled(state); + }); + madsSettings->setEnabled(toggles["EnableMads"]->isToggled()); + + connect(toggles["CustomOffsets"], &ToggleControl::toggleFlipped, [=](bool state) { + customOffsetsSettings->setEnabled(state); + }); + customOffsetsSettings->setEnabled(toggles["CustomOffsets"]->isToggled()); + + // update "FRICTION" and "LAT_ACCEL_FACTOR" titles when going onroad/offroad + connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + friction->setEnabled(offroad || toggles["TorquedOverride"]->isToggled()); + lat_accel_factor->setEnabled(offroad || toggles["TorquedOverride"]->isToggled()); + + friction->refresh(); + lat_accel_factor->refresh(); + }); + + // update "FRICTION" and "LAT_ACCEL_FACTOR" titles when TorquedOverride is flipped + connect(toggles["TorquedOverride"], &ToggleControl::toggleFlipped, [=](bool state) { + friction->setEnabled(params.getBool("IsOffroad") || state); + lat_accel_factor->setEnabled(params.getBool("IsOffroad") || state); + + friction->setTitle(state ? "FRICTION - Live && Offline" : "FRICTION - Offline Only"); + lat_accel_factor->setTitle(state ? "LAT_ACCEL_FACTOR - Live && Offline" : "LAT_ACCEL_FACTOR - Offline Only"); + + friction->refresh(); + lat_accel_factor->refresh(); + }); + + param_watcher = new ParamWatcher(this); + + QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { + updateToggles(); + }); + + sunnypilotScreen = new QWidget(this); + QVBoxLayout* vlayout = new QVBoxLayout(sunnypilotScreen); + vlayout->setContentsMargins(50, 20, 50, 20); + + scrollView = new ScrollView(list, this); + vlayout->addWidget(scrollView, 1); + main_layout->addWidget(sunnypilotScreen); + main_layout->addWidget(mads_settings); + main_layout->addWidget(lane_change_settings); + main_layout->addWidget(custom_offsets_settings); + + setStyleSheet(R"( + #back_btn { + font-size: 50px; + margin: 0px; + padding: 15px; + border-width: 0; + border-radius: 30px; + color: #dddddd; + background-color: #393939; + } + #back_btn:pressed { + background-color: #4a4a4a; + } + )"); + + main_layout->setCurrentWidget(sunnypilotScreen); +} + +void SunnypilotPanel::showEvent(QShowEvent *event) { + updateToggles(); +} + +void SunnypilotPanel::hideEvent(QHideEvent *event) { + main_layout->setCurrentWidget(sunnypilotScreen); +} + +void SunnypilotPanel::updateToggles() { + param_watcher->addParam("DynamicLaneProfile"); + + if (!isVisible()) { + return; + } + + // toggle VisionCurveLaneless when DynamicLaneProfile == 2/Auto + auto dynamic_lane_profile_param = QString::fromStdString(params.get("DynamicLaneProfile")); + toggles["VisionCurveLaneless"]->setEnabled(dynamic_lane_profile_param == "2"); + toggles["VisionCurveLaneless"]->refresh(); + + auto enforce_torque_lateral = toggles["EnforceTorqueLateral"]; + auto custom_torque_lateral = toggles["CustomTorqueLateral"]; + auto live_torque = toggles["LiveTorque"]; + auto live_torque_relaxed = toggles["LiveTorqueRelaxed"]; + auto torqued_override = toggles["TorquedOverride"]; + + auto speed_limit_control = QString::fromStdString(params.get("SpeedLimitControl")) != "0"; + auto custom_stock_long_param = params.getBool("CustomStockLong"); + auto v_tsc = toggles["TurnVisionControl"]; + auto m_tsc = toggles["TurnSpeedControl"]; + auto reverse_acc = toggles["ReverseAccChange"]; + + // toggle names to update when EnforceTorqueLateral is flipped + std::vector enforceTorqueGroup{"CustomTorqueLateral", "LiveTorque", "LiveTorqueRelaxed", "TorquedOverride"}; + for (const auto& enforceTorqueToggle : enforceTorqueGroup) { + if (toggles.find(enforceTorqueToggle) != toggles.end()) { + toggles[enforceTorqueToggle]->setVisible(enforce_torque_lateral->isToggled()); + } + } + + // toggle names to update when CustomTorqueLateral is flipped + std::vector customTorqueGroup{friction, lat_accel_factor}; + for (const auto& customTorqueControl : customTorqueGroup) { + customTorqueControl->setVisible(custom_torque_lateral->isToggled()); + } + + if (enforce_torque_lateral->isToggled()) { + live_torque_relaxed->setVisible(live_torque->isToggled()); + torqued_override->setVisible(custom_torque_lateral->isToggled()); + } else { + params.putBool("LiveTorque", false); + params.putBool("CustomTorqueLateral", false); + for (const auto& customTorqueControl : customTorqueGroup) { + customTorqueControl->setVisible(false); + } + } + + auto cp_bytes = params.get("CarParamsPersistent"); + if (!cp_bytes.empty()) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); + cereal::CarParams::Reader CP = cmsg.getRoot(); + + if (CP.getSteerControlType() == cereal::CarParams::SteerControlType::ANGLE) { + enforce_torque_lateral->setEnabled(false); + params.remove("EnforceTorqueLateral"); + } + + if (hasLongitudinalControl(CP) || custom_stock_long_param) { + speed_limit_offset_settings->setEnabled(speed_limit_control); + slvo->setEnabled(speed_limit_control && QString::fromStdString(params.get("SpeedLimitOffsetType")) != "0"); + speed_limit_control_settings->setEnabled(true); + v_tsc->setEnabled(true); + m_tsc->setEnabled(true); + reverse_acc->setEnabled(true); + } else { + speed_limit_control_settings->setEnabled(false); + speed_limit_offset_settings->setEnabled(false); + slvo->setEnabled(false); + v_tsc->setEnabled(false); + m_tsc->setEnabled(false); + reverse_acc->setEnabled(false); + } + } else { + speed_limit_control_settings->setEnabled(false); + speed_limit_offset_settings->setEnabled(false); + slvo->setEnabled(false); + v_tsc->setEnabled(false); + m_tsc->setEnabled(false); + reverse_acc->setEnabled(false); + } +} + +TorqueFriction::TorqueFriction() : SPOptionControl ( + "TorqueFriction", + tr("FRICTION"), + tr("Adjust Friction for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), + "../assets/offroad/icon_blank.png", + {0, 50}) { + + refresh(); +} + +void TorqueFriction::refresh() { + float torqueFrictionVal = QString::fromStdString(params.get("TorqueFriction")).toInt() * 0.01; + setTitle(params.getBool("TorquedOverride") ? "FRICTION - Live && Offline" : "FRICTION - Offline Only"); + setLabel(QString::number(torqueFrictionVal)); +} + +TorqueMaxLatAccel::TorqueMaxLatAccel() : SPOptionControl ( + "TorqueMaxLatAccel", + tr("LAT_ACCEL_FACTOR"), + tr("Adjust Max Lateral Acceleration for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), + "../assets/offroad/icon_blank.png", + {1, 500}) { + + refresh(); +} + +void TorqueMaxLatAccel::refresh() { + float torqueMaxLatAccelVal = QString::fromStdString(params.get("TorqueMaxLatAccel")).toInt() * 0.01; + QString unit = "m/s²"; + setTitle(params.getBool("TorquedOverride") ? "LAT_ACCEL_FACTOR - Live && Offline" : "LAT_ACCEL_FACTOR - Offline Only"); + setLabel(QString::number(torqueMaxLatAccelVal) + " " + unit); +} + +// Speed Limit Control Custom Offset +SpeedLimitValueOffset::SpeedLimitValueOffset() : SPOptionControl ( + "SpeedLimitValueOffset", + "", + "", + "../assets/offroad/icon_blank.png", + {-30, 30}) { + + refresh(); +} + +void SpeedLimitValueOffset::refresh() { + QString option = QString::fromStdString(params.get("SpeedLimitValueOffset")); + QString offset_type = QString::fromStdString(params.get("SpeedLimitOffsetType")); + bool is_metric = params.getBool("IsMetric"); + QString unit = ""; + + if (option.toInt() >= 0) { + option = "+" + option; + } + + if (offset_type == "0") { + option = ""; + unit = "N/A"; + } else if (offset_type == "1") { + unit = is_metric ? " km/h" : " mph"; + } else if (offset_type == "2") { + unit = " %"; + } + setLabel(option + unit); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h new file mode 100644 index 0000000000..1d51ef8358 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h @@ -0,0 +1,76 @@ +#pragma once + +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/custom_offsets_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +class SpeedLimitValueOffset : public SPOptionControl { + Q_OBJECT + +public: + SpeedLimitValueOffset(); + + void refresh(); + +private: + Params params; +}; + +class TorqueFriction : public SPOptionControl { + Q_OBJECT + +public: + TorqueFriction(); + + void refresh(); + +private: + Params params; +}; + +class TorqueMaxLatAccel : public SPOptionControl { + Q_OBJECT + +public: + TorqueMaxLatAccel(); + + void refresh(); + +private: + Params params; +}; + +class SunnypilotPanel : public QFrame { + Q_OBJECT + +public: + explicit SunnypilotPanel(QWidget *parent = nullptr); + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent* event) override; + +public slots: + void updateToggles(); + +private: + QStackedLayout* main_layout = nullptr; + QWidget* sunnypilotScreen = nullptr; + MadsSettings* mads_settings = nullptr; + LaneChangeSettings* lane_change_settings = nullptr; + CustomOffsetsSettings* custom_offsets_settings = nullptr; + Params params; + std::map toggles; + ParamWatcher *param_watcher; + + SpeedLimitValueOffset *slvo; + TorqueFriction *friction; + TorqueMaxLatAccel *lat_accel_factor; + ButtonParamControl *dlp_settings; + ButtonParamControl *speed_limit_control_settings; + ButtonParamControl *speed_limit_offset_settings; + + ScrollView *scrollView = nullptr; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc new file mode 100644 index 0000000000..e21dc675ae --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.cc @@ -0,0 +1,29 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h" + +TripsPanel::TripsPanel(QWidget* parent) : QFrame(parent) { + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setMargin(0); + + // main content + main_layout->addSpacing(25); + center_layout = new QStackedLayout(); + + driveStatsWidget = new DriveStats; + driveStatsWidget->setStyleSheet(R"( + QLabel[type="title"] { font-size: 51px; font-weight: 500; } + QLabel[type="number"] { font-size: 78px; font-weight: 500; } + QLabel[type="unit"] { font-size: 51px; font-weight: 300; color: #A0A0A0; } + )"); + center_layout->addWidget(driveStatsWidget); + + main_layout->addLayout(center_layout, 1); + + setStyleSheet(R"( + * { + color: white; + } + TripsPanel > QLabel { + font-size: 55px; + } + )"); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h new file mode 100644 index 0000000000..dc77acac75 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h @@ -0,0 +1,17 @@ +#pragma once + +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h" + +class TripsPanel : public QFrame { + Q_OBJECT + +public: + explicit TripsPanel(QWidget* parent = 0); + +private: + Params params; + + QStackedLayout* center_layout; + DriveStats *driveStatsWidget; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.cc new file mode 100644 index 0000000000..87ac1857b0 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.cc @@ -0,0 +1,129 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h" + +VehiclePanel::VehiclePanel(QWidget *parent) : QWidget(parent) { + main_layout = new QStackedLayout(this); + home = new QWidget(this); + QVBoxLayout* fcr_layout = new QVBoxLayout(home); + fcr_layout->setContentsMargins(0, 20, 0, 20); + + QString set = QString::fromStdString(params.get("CarModelText")); + QPushButton* setCarBtn = new QPushButton(((set == "=== Not Selected ===") || (set.length() == 0)) ? "Select your car" : set); + setCarBtn->setObjectName("setCarBtn"); + setCarBtn->setStyleSheet("margin-right: 30px;"); + connect(setCarBtn, &QPushButton::clicked, [=]() { + QMap cars = getCarNames(); + QString currentCar = QString::fromStdString(params.get("CarModel")); + QString selection = MultiOptionDialog::getSelection("Select your car", cars.keys(), cars.key(currentCar), this); + if (!selection.isEmpty()) { + params.put("CarModel", cars[selection].toStdString()); + params.put("CarModelText", selection.toStdString()); + qApp->exit(18); + watchdog_kick(0); + } + }); + fcr_layout->addSpacing(10); + fcr_layout->addWidget(setCarBtn, 0, Qt::AlignRight); + fcr_layout->addSpacing(10); + + home_widget = new QWidget(this); + QVBoxLayout* toggle_layout = new QVBoxLayout(home_widget); + home_widget->setObjectName("homeWidget"); + + ScrollView *scroller = new ScrollView(home_widget, this); + scroller->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + fcr_layout->addWidget(scroller, 1); + + main_layout->addWidget(home); + + setStyleSheet(R"( + #setCarBtn { + border-radius: 50px; + font-size: 50px; + font-weight: 500; + height:100px; + padding: 0 35 0 35; + color: #E4E4E4; + background-color: #393939; + } + #setCarBtn:pressed { + background-color: #4a4a4a; + } + )"); + + auto toggle_panel = new SPVehiclesTogglesPanel(this); + toggle_layout->addWidget(toggle_panel); +} + +SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(VehiclePanel *parent) : ListWidget(parent, false) { + setSpacing(50); + + // Hyundai/Kia/Genesis + addItem(new LabelControl(tr("Hyundai/Kia/Genesis"))); + auto hkgSmoothStop = new ParamControl( + "HkgSmoothStop", + "HKG CAN: Smoother Stopping Performance (Beta)", + "Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control.", + "../assets/offroad/icon_blank.png" + ); + hkgSmoothStop->setConfirmation(true, false); + addItem(hkgSmoothStop); + + // Subaru + addItem(new LabelControl(tr("Subaru"))); + auto subaruManualParkingBrakeSng = new ParamControl( + "SubaruManualParkingBrakeSng", + tr("Manual Parking Brake: Stop and Go (Beta)"), + tr("Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation!"), + "../assets/offroad/icon_blank.png" + ); + subaruManualParkingBrakeSng->setConfirmation(true, false); + addItem(subaruManualParkingBrakeSng); + + // Toyota/Lexus + addItem(new LabelControl(tr("Toyota/Lexus"))); + stockLongToyota = new ParamControl( + "StockLongToyota", + "Enable Stock Toyota Longitudinal Control", + "sunnypilot will not take over control of gas and brakes. Stock Toyota longitudinal control will be used.", + "../assets/offroad/icon_blank.png" + ); + stockLongToyota->setConfirmation(true, false); + addItem(stockLongToyota); + + auto lkasToggle = new ParamControl( + "LkasToggle", + tr("Allow M.A.D.S. toggling w/ LKAS Button (Beta)"), + QString("%1
" + "

%2


") + .arg(tr("Allows M.A.D.S. engagement/disengagement with \"LKAS\" button from the steering wheel.")) + .arg(tr("Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly.")), + "../assets/offroad/icon_blank.png" + ); + lkasToggle->setConfirmation(true, false); + addItem(lkasToggle); + + auto toyotaTss2LongTune = new ParamControl( + "ToyotaTSS2Long", + "TSS2 Longitudinal: Custom Tuning", + "Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation.", + "../assets/offroad/icon_blank.png" + ); + toyotaTss2LongTune->setConfirmation(true, false); + addItem(toyotaTss2LongTune); + + auto toyotaSngHack = new ParamControl( + "ToyotaSnG", + "Enable Stop and Go Hack", + "sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk.", + "../assets/offroad/icon_blank.png" + ); + toyotaSngHack->setConfirmation(true, false); + addItem(toyotaSngHack); + + // trigger offroadTransition when going onroad/offroad + connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + hkgSmoothStop->setEnabled(offroad); + toyotaTss2LongTune->setEnabled(offroad); + toyotaSngHack->setEnabled(offroad); + }); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h new file mode 100644 index 0000000000..07e2b2f3ea --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "common/watchdog.h" +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" + +class VehiclePanel : public QWidget { + Q_OBJECT + +public: + explicit VehiclePanel(QWidget *parent = nullptr); + +private: + Params params; + + QStackedLayout* main_layout = nullptr; + QWidget* home = nullptr; + + QWidget* home_widget; +}; + +class SPVehiclesTogglesPanel : public ListWidget { + Q_OBJECT +public: + explicit SPVehiclesTogglesPanel(VehiclePanel *parent); + +private: + Params params; + + ParamControl *stockLongToyota; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc new file mode 100644 index 0000000000..0dfe86b317 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.cc @@ -0,0 +1,104 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h" + +VisualsPanel::VisualsPanel(QWidget *parent) : ListWidget(parent) { + // param, title, desc, icon + std::vector> toggle_defs{ + { + "BrakeLights", + tr("Display Braking Status"), + tr("Enable this will turn the current speed value to red while the brake is used."), + "../assets/offroad/icon_blank.png", + }, + { + "StandStillTimer", + tr("Display Stand Still Timer"), + tr("Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions)."), + "../assets/offroad/icon_blank.png", + }, + { + "ReverseDmCam", + tr("Display DM Camera in Reverse Gear"), + tr("Show Driver Monitoring camera while the car is in reverse gear."), + "../assets/offroad/icon_blank.png", + }, + { + "ShowDebugUI", + tr("OSM: Show debug UI elements"), + tr("OSM: Show UI elements that aid debugging."), + "../assets/offroad/icon_blank.png", + }, + { + "TrueVEgoUi", + tr("Speedometer: Display True Speed"), + tr("Display the true vehicle current speed from wheel speed sensors."), + "../assets/offroad/icon_blank.png", + }, + { + "HideVEgoUi", + tr("Speedometer: Hide from Onroad Screen"), + "", + "../assets/offroad/icon_blank.png", + }, + { + "EndToEndLongAlertUI", + tr("Display End-to-end Longitudinal Status (Beta)"), + tr("Enable this will display an icon that appears when the End-to-end model decides to start or stop."), + "../assets/offroad/icon_blank.png", + }, + { + "Map3DBuildings", + tr("Map: Display 3D Buildings"), + tr("Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation."), + "../assets/offroad/icon_blank.png", + }, + }; + + // Visuals: Developer UI Info (Dev UI) + std::vector dev_ui_settings_texts{tr("Off"), tr("5 Metrics"), tr("10 Metrics")}; + dev_ui_settings = new ButtonParamControl( + "DevUIInfo", "Developer UI", "Display real-time parameters and metrics from various sources.", + "../assets/offroad/icon_blank.png", + dev_ui_settings_texts, + 380 + ); + dev_ui_settings->showDescription(); + + // Visuals: Display Metrics above Chevron + std::vector chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed")}; + chevron_info_settings = new ButtonParamControl( + "ChevronInfo", "Metrics above Chevron", "Display useful metrics above the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control).", + "../assets/offroad/icon_blank.png", + chevron_info_settings_texts, + 320 + ); + chevron_info_settings->showDescription(); + + for (auto &[param, title, desc, icon] : toggle_defs) { + auto toggle = new ParamControl(param, title, desc, icon, this); + + addItem(toggle); + toggles[param.toStdString()] = toggle; + + if (param == "StandStillTimer") { + addItem(dev_ui_settings); + } + + if (param == "HideVEgoUi") { + addItem(chevron_info_settings); + } + } + + std::vector sidebar_temp_texts{tr("Off"), tr("Ambient"), tr("RAM"), tr("CPU"), tr("GPU"), tr("Max")}; + sidebar_temp_setting = new ButtonParamControl( + "SidebarTemperatureOptions", "Display Temperature on Sidebar", "", + "../assets/offroad/icon_blank.png", + sidebar_temp_texts, + 255 + ); + sidebar_temp_setting->showDescription(); + addItem(sidebar_temp_setting); + + // trigger offroadTransition when going onroad/offroad + connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { + }); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h new file mode 100644 index 0000000000..3ea5825f59 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h @@ -0,0 +1,19 @@ +#pragma once + +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/widgets/controls.h" + +class VisualsPanel : public ListWidget { + Q_OBJECT + +public: + explicit VisualsPanel(QWidget *parent = nullptr); + +private: + Params params; + std::map toggles; + + ButtonParamControl *dev_ui_settings; + ButtonParamControl *chevron_info_settings; + ButtonParamControl *sidebar_temp_setting; +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot_main.h b/selfdrive/ui/qt/offroad/sunnypilot_main.h new file mode 100644 index 0000000000..3ec64b88ac --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot_main.h @@ -0,0 +1,9 @@ +#pragma once + +#include "selfdrive/ui/qt/offroad/sunnypilot/display_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/vehicle_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/visuals_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/trips_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/monitoring_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/osm_settings.h" diff --git a/selfdrive/ui/qt/offroad/sunnypilot_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot_settings.cc deleted file mode 100644 index 07b234ac45..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot_settings.cc +++ /dev/null @@ -1,1063 +0,0 @@ -#include -#include -#include -#include -#include - -#include "common/watchdog.h" -#include "selfdrive/ui/qt/api.h" -#include "selfdrive/ui/qt/offroad/sunnypilot_settings.h" -#include "selfdrive/ui/qt/widgets/input.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" -#include "selfdrive/ui/qt/util.h" - -#include "selfdrive/ui/ui.h" - -SPGeneralPanel::SPGeneralPanel(QWidget *parent) : ListWidget(parent) { - // param, title, desc, icon - std::vector> toggle_defs{ - { - "QuietDrive", - tr("Quiet Drive 🤫"), - tr("sunnypilot will display alerts but only play the most important warning sounds. This feature can be toggled while the car is on."), - "../assets/offroad/icon_mute.png", - }, - { - "EndToEndLongAlertLight", - tr("Green Traffic Light Chime (Beta)"), - QString("%1
" - "

%2


") - .arg(tr("A chime will play when the traffic light you are waiting for turns green and you have no vehicle in front of you. If you are waiting behind another vehicle, the chime will play once the vehicle advances unless ACC is engaged.")) - .arg(tr("Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly.")), - "../assets/offroad/icon_road.png", - }, - { - "EndToEndLongAlertLead", - tr("Lead Vehicle Departure Alert"), - QString("%1
" - "

%2


") - .arg(tr("Enable this will notify when the leading vehicle drives away.")) - .arg(tr("Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly.")), - "../assets/offroad/icon_road.png", - }, - { - "HotspotOnBoot", - tr("Retain hotspot/tethering state"), - tr("Enabling this toggle will retain the hotspot/tethering toggle state across reboots."), - "../assets/offroad/icon_network.png", - }, - { - "OnroadScreenOffEvent", - tr("Driving Screen Off: Non-Critical Events"), - QString("%1
" - "

%2


" - "

%3


") - .arg(tr("When Driving Screen Off Timer is not set to \"Always On\":")) - .arg(tr("Enabled: Wake the brightness of the screen to display all events.")) - .arg(tr("Disabled: Wake the brightness of the screen to display critical events.")), - "../assets/offroad/icon_blank.png", - }, - { - "ScreenRecorder", - tr("Enable Screen Recorder"), - tr("Enable this will display a button on the onroad screen to toggle on or off real-time screen recording with UI elements."), - "../assets/offroad/icon_calibration.png" - }, - { - "DisableOnroadUploads", - tr("Disable Onroad Uploads"), - tr("Disable uploads completely when onroad. Necessary to avoid high data usage when connected to Wi-Fi hotspot. Turn on this feature if you are looking to utilize map-based features, such as Speed Limit Control (SLC) and Map-based Turn Speed Control (MTSC)."), - "../assets/offroad/icon_network.png", - }, - { - "EnableDebugSnapshot", - tr("Debug snapshot on screen center tap"), - tr("Stores snapshot file with current state of some modules."), - "../assets/offroad/icon_calibration.png" - } - }; - - // General: Onroad Screen Off (Auto Onroad Screen Timer) - onroad_screen_off = new OnroadScreenOff(); - onroad_screen_off->setUpdateOtherToggles(true); - connect(onroad_screen_off, &SPOptionControl::updateLabels, onroad_screen_off, &OnroadScreenOff::refresh); - connect(onroad_screen_off, &SPOptionControl::updateOtherToggles, this, &SPGeneralPanel::updateToggles); - - // General: Onroad Screen Off Brightness - onroad_screen_off_brightness = new OnroadScreenOffBrightness(); - connect(onroad_screen_off_brightness, &SPOptionControl::updateLabels, onroad_screen_off_brightness, &OnroadScreenOffBrightness::refresh); - - // General: Brightness Control (Global) - auto brightness_control = new BrightnessControl(); - connect(brightness_control, &SPOptionControl::updateLabels, brightness_control, &BrightnessControl::refresh); - - // General: Max Time Offroad (Shutdown timer) - auto max_time_offroad = new MaxTimeOffroad(); - connect(max_time_offroad, &SPOptionControl::updateLabels, max_time_offroad, &MaxTimeOffroad::refresh); - - for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); - - addItem(toggle); - toggles[param.toStdString()] = toggle; - - if (param == "HotspotOnBoot") { - // General: Max Time Offroad (Shutdown timer) - addItem(max_time_offroad); - - addItem(onroad_screen_off); - - addItem(onroad_screen_off_brightness); - } - - if (param == "OnroadScreenOffEvent") { - // General: Brightness Control (Global) - addItem(brightness_control); - } - } - - toggles["EndToEndLongAlertLight"]->setConfirmation(true, false); -} - -void SPGeneralPanel::showEvent(QShowEvent *event) { - updateToggles(); -} - -void SPGeneralPanel::updateToggles() { - // toggle names to update when OnroadScreenOff is toggled - onroad_screen_off_brightness->setVisible(QString::fromStdString(params.get("OnroadScreenOff")) != "-2"); - toggles["OnroadScreenOffEvent"]->setVisible(QString::fromStdString(params.get("OnroadScreenOff")) != "-2"); -} - -SPControlsPanel::SPControlsPanel(QWidget *parent) : ListWidget(parent) { - // param, title, desc, icon - std::vector> toggle_defs{ - { - "EnableMads", - tr("Enable M.A.D.S."), - tr("Enable the beloved M.A.D.S. feature. Disable toggle to revert back to stock openpilot engagement/disengagement."), - "../assets/offroad/icon_openpilot.png", - }, - { - "DisengageLateralOnBrake", - tr("Disengage ALC On Brake Pedal ℹ"), - QString("%1
" - "

%2


" - "

%3


") - .arg(tr("Define brake pedal interactions with sunnypilot when M.A.D.S. is enabled.")) - .arg(tr("Enabled: Pressing the brake pedal will disengage Automatic Lane Centering (ALC) on sunnypilot.")) - .arg(tr("Disabled: Pressing the brake pedal will NOT disengage Automatic Lane Centering (ALC) on sunnypilot.")), - "../assets/offroad/icon_openpilot.png", - }, - { - "AccMadsCombo", - tr("Enable ACC+MADS with RES+/SET-"), - QString("%1
" - "

%2


") - .arg(tr("Engage both M.A.D.S. and ACC with a single press of RES+ or SET- button.")) - .arg(tr("Note: Once M.A.D.S. is engaged via this mode, it will remain engaged until it is manually disabled via the M.A.D.S. button or car shut off.")), - "../assets/offroad/icon_openpilot.png", - }, - { - "MadsCruiseMain", - tr("Toggle M.A.D.S. with Cruise Main"), - tr("Allows M.A.D.S. engagement/disengagement with \"Cruise Main\" cruise control button from the steering wheel."), - "../assets/offroad/icon_openpilot.png", - }, - { - "BelowSpeedPause", - tr("Pause Lateral Below Speed w/ Blinker"), - tr("Enable this toggle to pause lateral actuation with blinker when traveling below 30 MPH or 50 KM/H."), - "../assets/offroad/icon_openpilot.png", - }, - { - "RoadEdge", - tr("Block Lane Change: Road Edge Detection"), - tr("Enable this toggle to block lane change when road edge is detected on the stalk actuated side."), - "../assets/offroad/icon_openpilot.png", - }, - // Dynamic Lane Profile group will fit here - { - "VisionCurveLaneless", - tr("Laneless for Curves in \"Auto lane\""), - tr("While in Auto Lane, switch to Laneless for current/future curves."), - "../assets/offroad/icon_blank.png", - }, - { - "DynamicExperimentalControlToggle", - tr("Enable Dynamic Experimental Control"), - tr("Enable toggle to allow the model to determine when to use openpilot ACC or openpilot End to End Longitudinal."), - "../assets/offroad/icon_speed_limit.png", - }, - { - "CustomOffsets", - tr("Custom Offsets"), - tr("Add custom offsets to Camera and Path in sunnypilot."), - "../assets/offroad/icon_metric.png", - }, - { - "AutoLaneChangeBsmDelay", - tr("Auto Lane Change: Delay with Blind Spot"), - tr("Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects a obstructing vehicle, ensuring safe maneuvering."), - "../assets/offroad/icon_blank.png", - }, - { - "EnforceTorqueLateral", - tr("Enforce Torque Lateral Control"), - tr("Enable this to enforce sunnypilot to steer with Torque lateral control."), - "../assets/offroad/icon_calibration.png", - }, - { - "LiveTorque", - tr("Enable Self-Tune"), - tr("Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default."), - "../assets/offroad/icon_blank.png", - }, - { - "LiveTorqueRelaxed", - tr("Less Restrict Settings for Self-Tune (Beta)"), - tr("Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values."), - "../assets/offroad/icon_blank.png", - }, - { - "CustomTorqueLateral", - tr("Enable Custom Tuning"), - tr("Enables custom tuning for Torque lateral control. Modifying FRICTION and LAT_ACCEL_FACTOR below will override the offline values indicated in the YAML files within \"selfdrive/torque_data\". The values will also be used live when \"Override Self-Tune\" toggle is enabled."), - "../assets/offroad/icon_blank.png", - }, - { - "TorquedOverride", - tr("Manual Real-Time Tuning"), - tr("Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values."), - "../assets/offroad/icon_blank.png", - }, - { - "HandsOnWheelMonitoring", - tr("Enable Hands on Wheel Monitoring"), - tr("Monitor and alert when driver is not keeping the hands on the steering wheel."), - "../assets/offroad/icon_openpilot.png", - }, - { - "TurnVisionControl", - tr("Enable Vision Based Turn Speed Control (V-TSC)"), - tr("Use vision path predictions to estimate the appropriate speed to drive through turns ahead."), - "../assets/offroad/icon_road.png", - }, - { - "SpeedLimitControl", - tr("Enable Speed Limit Control (SLC)"), - tr("Use speed limit signs information from map data and car interface (if applicable) to automatically adapt cruise speed to road limits."), - "../assets/offroad/icon_speed_limit.png", - }, - { - "SpeedLimitPercOffset", - tr("Enable Speed Limit Offset"), - tr("Set speed limit slightly higher than actual speed limit for a more natural drive."), - "../assets/offroad/icon_speed_limit.png", - }, - { - "TurnSpeedControl", - tr("Enable Map Data Turn Speed Control (M-TSC)"), - tr("Use curvature information from map data to define speed limits to take turns ahead."), - "../assets/offroad/icon_openpilot.png", - }, - { - "ReverseAccChange", - tr("ACC +/-: Long Press Reverse"), - QString("%1
" - "

%2


" - "

%3


") - .arg(tr("Change the ACC +/- buttons behavior with cruise speed change in sunnypilot.")) - .arg(tr("Disabled (Stock): Short=1, Long = 5 (imperial) / 10 (metric)")) - .arg(tr("Enabled: Short = 5 (imperial) / 10 (metric), Long=1")), - "../assets/offroad/icon_acc_change.png", - }, - { - "OsmLocalDb", - tr("OSM: Use Offline Database"), - "", - "../assets/img_map.png", - }, - }; - - // toggle names to trigger updateToggles() when toggleFlipped - std::vector updateTogglesNames{ - "EnableMads", "CustomOffsets", "EnforceTorqueLateral", "SpeedLimitPercOffset", "SpeedLimitControl", - "CustomTorqueLateral", "LiveTorque", "TorquedOverride" - }; - - // toggle for offroadTransition when going onroad/offroad - std::vector toggleOffroad{ - "EnableMads", "DisengageLateralOnBrake", "AccMadsCombo", "MadsCruiseMain", "BelowSpeedPause", "EnforceTorqueLateral", - "LiveTorqueRelaxed" - }; - - // Controls: Camera Offset (cm) - camera_offset = new CameraOffset(); - connect(camera_offset, &SPOptionControl::updateLabels, camera_offset, &CameraOffset::refresh); - - // Controls: Path Offset (cm) - path_offset = new PathOffset(); - connect(path_offset, &SPOptionControl::updateLabels, path_offset, &PathOffset::refresh); - - // Controls: Auto Lane Change Timer - auto_lane_change_timer = new AutoLaneChangeTimer(); - auto_lane_change_timer->setUpdateOtherToggles(true); - connect(auto_lane_change_timer, &SPOptionControl::updateLabels, auto_lane_change_timer, &AutoLaneChangeTimer::refresh); - connect(auto_lane_change_timer, &AutoLaneChangeTimer::updateOtherToggles, this, &SPControlsPanel::updateToggles); - - // Controls: Speed Limit Offset Type - slo_type = new SpeedLimitOffsetType(); - slo_type->setUpdateOtherToggles(true); - connect(slo_type, &SPOptionControl::updateLabels, slo_type, &SpeedLimitOffsetType::refresh); - connect(slo_type, &SPOptionControl::updateOtherToggles, this, &SPControlsPanel::updateToggles); - - // Controls: Speed Limit Offset Values (% or actual value) - slvo = new SpeedLimitValueOffset(); - connect(slvo, &SPOptionControl::updateLabels, slvo, &SpeedLimitValueOffset::refresh); - - // Controls: Torque - FRICTION - friction = new TorqueFriction(); - connect(friction, &SPOptionControl::updateLabels, friction, &TorqueFriction::refresh); - - // Controls: Torque - LAT_ACCEL_FACTOR - lat_accel_factor = new TorqueMaxLatAccel(); - connect(lat_accel_factor, &SPOptionControl::updateLabels, lat_accel_factor, &TorqueMaxLatAccel::refresh); - - std::vector dlp_settings_texts{tr("Laneful"), tr("Laneless"), tr("Auto")}; - dlp_settings = new ButtonParamControl( - "DynamicLaneProfile", "Dynamic Lane Profile", "Default is Laneless. In Auto mode, sunnnypilot dynamically chooses between Laneline or Laneless model based on lane recognition confidence level on road and certain conditions.", - "../assets/offroad/icon_blank.png", - dlp_settings_texts - ); - dlp_settings->showDescription(); - - for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); - - addItem(toggle); - toggles[param.toStdString()] = toggle; - - if (param == "RoadEdge") { - // Controls: Dynamic Lane Profile group - addItem(dlp_settings); - } - - if (param == "CustomOffsets") { - // Controls: Camera Offset (cm) - addItem(camera_offset); - - // Controls: Path Offset (cm) - addItem(path_offset); - - // Controls: Auto Lane Change Timer - addItem(auto_lane_change_timer); - } - - if (param == "TorquedOverride") { - // Control: FRICTION - addItem(friction); - - // Controls: LAT_ACCEL_FACTOR - addItem(lat_accel_factor); - } - - if (param == "SpeedLimitPercOffset") { - // Controls: Speed Limit Offset Type - addItem(slo_type); - - // Controls: Speed Limit Offset Values (% or actual value) - addItem(slvo); - } - - // trigger updateToggles() when toggleFlipped - if (std::find(updateTogglesNames.begin(), updateTogglesNames.end(), param.toStdString()) != updateTogglesNames.end()) { - connect(toggle, &ToggleControl:: toggleFlipped, [=](bool state) { - updateToggles(); - }); - } - - // trigger offroadTransition when going onroad/offroad - if (std::find(toggleOffroad.begin(), toggleOffroad.end(), param.toStdString()) != toggleOffroad.end()) { - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - toggle->setEnabled(offroad); - }); - } - } - - toggles["EnableMads"]->setConfirmation(true, false); - toggles["DisengageLateralOnBrake"]->setConfirmation(true, false); - - connect(toggles["OsmLocalDb"], &ToggleControl::toggleFlipped, [=](bool state) { - if (!state) { - params.remove("OsmLocalDb"); - } - }); - - // update "FRICTION" and "LAT_ACCEL_FACTOR" titles when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - friction->setEnabled(offroad); - lat_accel_factor->setEnabled(offroad); - - friction->refresh(); - lat_accel_factor->refresh(); - }); - - // update "FRICTION" and "LAT_ACCEL_FACTOR" titles when TorquedOverride is flipped - connect(toggles["TorquedOverride"], &ToggleControl::toggleFlipped, [=](bool state) { - friction->setEnabled(params.getBool("IsOffroad") || state); - lat_accel_factor->setEnabled(params.getBool("IsOffroad") || state); - - friction->setTitle(state ? "FRICTION - Live && Offline" : "FRICTION - Offline Only"); - lat_accel_factor->setTitle(state ? "LAT_ACCEL_FACTOR - Live && Offline" : "LAT_ACCEL_FACTOR - Offline Only"); - - friction->refresh(); - lat_accel_factor->refresh(); - }); - - param_watcher = new ParamWatcher(this); - - QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { - updateToggles(); - updateButtons(); - }); -} - -void SPControlsPanel::showEvent(QShowEvent *event) { - updateToggles(); - updateButtons(); -} - -void SPControlsPanel::updateToggles() { - // toggle names to update when EnableMads is flipped - std::vector enableMadsGroup{"DisengageLateralOnBrake", "AccMadsCombo", "MadsCruiseMain"}; - for (const auto& enableMadstoggle : enableMadsGroup) { - if (toggles.find(enableMadstoggle) != toggles.end()) { - toggles[enableMadstoggle]->setVisible(toggles["EnableMads"]->isToggled()); - } - } - - // toggle VisionCurveLaneless when DynamicLaneProfile == 2/Auto - toggles["VisionCurveLaneless"]->setEnabled(QString::fromStdString(params.get("DynamicLaneProfile")) == "2"); - - // toggle names to update when CustomOffsets is flipped - std::vector customOffsetsGroup{camera_offset, path_offset}; - for (const auto& customOffsetsControl : customOffsetsGroup) { - customOffsetsControl->setVisible(toggles["CustomOffsets"]->isToggled()); - } - - // toggle names to update when AutoLaneChangeTimer is not "Nudge" - toggles["AutoLaneChangeBsmDelay"]->setVisible(QString::fromStdString(params.get("AutoLaneChangeTimer")) != "0"); - - auto enforce_torque_lateral = toggles["EnforceTorqueLateral"]; - auto custom_torque_lateral = toggles["CustomTorqueLateral"]; - auto live_torque = toggles["LiveTorque"]; - auto live_torque_relaxed = toggles["LiveTorqueRelaxed"]; - auto torqued_override = toggles["TorquedOverride"]; - - auto speed_limit_control = toggles["SpeedLimitControl"]; - auto speed_limit_perc_offset = toggles["SpeedLimitPercOffset"]; - - // toggle names to update when EnforceTorqueLateral is flipped - std::vector enforceTorqueGroup{"CustomTorqueLateral", "LiveTorque", "TorquedOverride"}; - for (const auto& enforceTorqueToggle : enforceTorqueGroup) { - if (toggles.find(enforceTorqueToggle) != toggles.end()) { - toggles[enforceTorqueToggle]->setVisible(enforce_torque_lateral->isToggled()); - } - } - - // toggle names to update when CustomTorqueLateral is flipped - std::vector customTorqueGroup{friction, lat_accel_factor}; - for (const auto& customTorqueControl : customTorqueGroup) { - customTorqueControl->setVisible(custom_torque_lateral->isToggled()); - } - - // toggle names to update when SpeedLimitControl is flipped - std::vector speedLimitControlGroup{slo_type, slvo}; - for (const auto& speedLimitControl : speedLimitControlGroup) { - speedLimitControl->setVisible(speed_limit_control->isToggled()); - } - - if (enforce_torque_lateral->isToggled()) { - live_torque_relaxed->setVisible(live_torque->isToggled()); - torqued_override->setVisible(custom_torque_lateral->isToggled()); - } else { - params.putBool("LiveTorque", false); - params.putBool("CustomTorqueLateral", false); - for (const auto& customTorqueControl : customTorqueGroup) { - customTorqueControl->setVisible(false); - } - } - - if (speed_limit_control->isToggled()) { - if (speed_limit_perc_offset->isToggled()) { - slvo->setVisible(QString::fromStdString(params.get("SpeedLimitOffsetType")) != "0"); - slo_type->setVisible(true); - } else { - for (const auto& speedLimitControl : speedLimitControlGroup) { - speedLimitControl->setVisible(false); - } - } - } else { - for (const auto& speedLimitControl : speedLimitControlGroup) { - speedLimitControl->setVisible(false); - } - } - - // toggle names to update when SpeedLimitControl is flipped - speed_limit_perc_offset->setVisible(speed_limit_control->isToggled()); - - auto cp_bytes = params.get("CarParamsPersistent"); - if (!cp_bytes.empty()) { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); - cereal::CarParams::Reader CP = cmsg.getRoot(); - - if (CP.getSteerControlType() == cereal::CarParams::SteerControlType::ANGLE) { - enforce_torque_lateral->setEnabled(false); - params.remove("EnforceTorqueLateral"); - } - } -} - -void SPControlsPanel::updateButtons() { - param_watcher->addParam("DynamicLaneProfile"); - - if (!isVisible()) return; - - dlp_settings->setButton("DynamicLaneProfile"); -} - -SPVehiclesPanel::SPVehiclesPanel(QWidget *parent) : QWidget(parent) { - main_layout = new QStackedLayout(this); - home = new QWidget(this); - QVBoxLayout* fcr_layout = new QVBoxLayout(home); - fcr_layout->setContentsMargins(0, 20, 0, 20); - - QString set = QString::fromStdString(params.get("CarModelText")); - QPushButton* setCarBtn = new QPushButton(((set == "=== Not Selected ===") || (set.length() == 0)) ? "Select your car" : set); - setCarBtn->setObjectName("setCarBtn"); - setCarBtn->setStyleSheet("margin-right: 30px;"); - connect(setCarBtn, &QPushButton::clicked, [=]() { - QMap cars = getCarNames(); - QString currentCar = QString::fromStdString(params.get("CarModel")); - QString selection = MultiOptionDialog::getSelection("Select your car", cars.keys(), cars.key(currentCar), this); - if (!selection.isEmpty()) { - params.put("CarModel", cars[selection].toStdString()); - params.put("CarModelText", selection.toStdString()); - qApp->exit(18); - watchdog_kick(0); - } - }); - fcr_layout->addSpacing(10); - fcr_layout->addWidget(setCarBtn, 0, Qt::AlignRight); - fcr_layout->addSpacing(10); - - home_widget = new QWidget(this); - QVBoxLayout* toggle_layout = new QVBoxLayout(home_widget); - home_widget->setObjectName("homeWidget"); - - ScrollView *scroller = new ScrollView(home_widget, this); - scroller->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - fcr_layout->addWidget(scroller, 1); - - main_layout->addWidget(home); - - setStyleSheet(R"( - #setCarBtn { - border-radius: 50px; - font-size: 50px; - font-weight: 500; - height:100px; - padding: 0 35 0 35; - color: #E4E4E4; - background-color: #393939; - } - #setCarBtn:pressed { - background-color: #4a4a4a; - } - )"); - - auto toggle_panel = new SPVehiclesTogglesPanel(this); - toggle_layout->addWidget(toggle_panel); -} - -SPVehiclesTogglesPanel::SPVehiclesTogglesPanel(SPVehiclesPanel *parent) : ListWidget(parent) { - setSpacing(50); - - // Hyundai/Kia/Genesis - addItem(new LabelControl(tr("Hyundai/Kia/Genesis"))); - auto hkgSmoothStop = new ParamControl( - "HkgSmoothStop", - "HKG CAN: Smoother Stopping Performance (Beta)", - "Smoother stopping behind a stopped car or desired stopping event. This is only applicable to HKG CAN platforms using openpilot longitudinal control.", - "../assets/offroad/icon_blank.png" - ); - hkgSmoothStop->setConfirmation(true, false); - addItem(hkgSmoothStop); - - // Subaru - addItem(new LabelControl(tr("Subaru"))); - auto subaruManualParkingBrakeSng = new ParamControl( - "SubaruManualParkingBrakeSng", - tr("Manual Parking Brake: Stop and Go (Beta)"), - tr("Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation!"), - "../assets/offroad/icon_blank.png" - ); - subaruManualParkingBrakeSng->setConfirmation(true, false); - addItem(subaruManualParkingBrakeSng); - - // Toyota/Lexus - addItem(new LabelControl(tr("Toyota/Lexus"))); - stockLongToyota = new ParamControl( - "StockLongToyota", - "Enable Stock Toyota Longitudinal Control", - "sunnypilot will not take over control of gas and brakes. Stock Toyota longitudinal control will be used.", - "../assets/offroad/icon_blank.png" - ); - stockLongToyota->setConfirmation(true, false); - addItem(stockLongToyota); - - auto lkasToggle = new ParamControl( - "LkasToggle", - tr("Allow M.A.D.S. toggling w/ LKAS Button (Beta)"), - QString("%1
" - "

%2


") - .arg(tr("Allows M.A.D.S. engagement/disengagement with \"LKAS\" button from the steering wheel.")) - .arg(tr("Note: Enabling this toggle may have unexpected behavior with steering control. It is the driver's responsibility to observe their environment and make decisions accordingly.")), - "../assets/offroad/icon_blank.png" - ); - lkasToggle->setConfirmation(true, false); - addItem(lkasToggle); - - auto toyotaTss2LongTune = new ParamControl( - "ToyotaTSS2Long", - "TSS2 Longitudinal: Custom Tuning", - "Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars. Big thanks to dragonpilot-community for this implementation.", - "../assets/offroad/icon_blank.png" - ); - toyotaTss2LongTune->setConfirmation(true, false); - addItem(toyotaTss2LongTune); - - auto toyotaSngHack = new ParamControl( - "ToyotaSnG", - "Enable Stop and Go Hack", - "sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. This feature is only applicable to certain models. Use at your own risk.", - "../assets/offroad/icon_blank.png" - ); - toyotaSngHack->setConfirmation(true, false); - addItem(toyotaSngHack); - - // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - hkgSmoothStop->setEnabled(offroad); - toyotaTss2LongTune->setEnabled(offroad); - toyotaSngHack->setEnabled(offroad); - }); -} - -SPVisualsPanel::SPVisualsPanel(QWidget *parent) : ListWidget(parent) { - // param, title, desc, icon - std::vector> toggle_defs{ - { - "BrakeLights", - tr("Display Braking Status"), - tr("Enable this will turn the current speed value to red while the brake is used."), - "../assets/offroad/icon_road.png", - }, - { - "StandStillTimer", - tr("Display Stand Still Timer"), - tr("Enable this will display time spent at a stop (i.e., at a stop lights, stop signs, traffic congestions)."), - "../assets/offroad/icon_road.png", - }, - { - "DevUI", - tr("Show Developer UI"), - tr("Show developer UI (Dev UI) for real-time parameters from various sources."), - "../assets/offroad/icon_calibration.png", - }, - { - "ButtonAutoHide", - tr("Auto-Hide UI Buttons"), - tr("Hide UI buttons on driving screen after a 30-second timeout. Tap on the screen at anytime to reveal the UI buttons."), - "../assets/offroad/icon_road.png", - }, - { - "ReverseDmCam", - tr("Display DM Camera in Reverse Gear"), - tr("Show Driver Monitoring camera while the car is in reverse gear."), - "../assets/offroad/icon_road.png", - }, - { - "ShowDebugUI", - tr("OSM: Show debug UI elements"), - tr("OSM: Show UI elements that aid debugging."), - "../assets/offroad/icon_calibration.png", - }, - { - "TrueVEgoUi", - tr("Speedometer: Display True Speed"), - tr("Display the true vehicle current speed from wheel speed sensors."), - "../assets/offroad/icon_openpilot.png", - }, - { - "HideVEgoUi", - tr("Speedometer: Hide from Onroad Screen"), - "", - "../assets/offroad/icon_openpilot.png", - }, - { - "EndToEndLongAlertUI", - tr("Display End-to-end Longitudinal Status (Beta)"), - tr("Enable this will display an icon that appears when the End-to-end model decides to start or stop."), - "../assets/offroad/icon_road.png", - }, - { - "Map3DBuildings", - tr("Map: Display 3D Buildings"), - tr("Parse and display 3D buildings on map. Thanks to jakethesnake420 for this implementation."), - "../assets/img_map.png", - }, - }; - - // Visuals: Developer UI Info (Dev UI) - dev_ui_info = new DevUiInfo(); - connect(dev_ui_info, &SPOptionControl::updateLabels, dev_ui_info, &DevUiInfo::refresh); - - // Visuals: Display Metrics above Chevron - chevron_info = new ChevronInfo(); - connect(chevron_info, &SPOptionControl::updateLabels, chevron_info, &ChevronInfo::refresh); - - for (auto &[param, title, desc, icon] : toggle_defs) { - auto toggle = new ParamControl(param, title, desc, icon, this); - - addItem(toggle); - toggles[param.toStdString()] = toggle; - - if (param == "DevUI") { - addItem(dev_ui_info); - } - - if (param == "HideVEgoUi") { - addItem(chevron_info); - } - } - - std::vector sidebar_temp_texts{tr("Off"), tr("Ambient"), tr("RAM"), tr("CPU"), tr("GPU"), tr("Max")}; - sidebar_temp_setting = new ButtonParamControl( - "SidebarTemperatureOptions", "Display Temperature on Sidebar", "", - "../assets/offroad/icon_blank.png", - sidebar_temp_texts, - 255 - ); - sidebar_temp_setting->showDescription(); - addItem(sidebar_temp_setting); - - // trigger updateToggles() when toggleFlipped - connect(toggles["DevUI"], &ToggleControl::toggleFlipped, [=](bool state) { - updateToggles(); - }); - - // trigger offroadTransition when going onroad/offroad - connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - }); -} - -void SPVisualsPanel::showEvent(QShowEvent *event) { - updateToggles(); -} - -void SPVisualsPanel::updateToggles() { - dev_ui_info->setVisible(toggles["DevUI"]->isToggled()); -} - -// Max Time Offroad (Shutdown timer) -MaxTimeOffroad::MaxTimeOffroad() : SPOptionControl ( - "MaxTimeOffroad", - tr("Max Time Offroad"), - tr("Device is automatically turned off after a set time when the engine is turned off (off-road) after driving (on-road)."), - "../assets/offroad/icon_metric.png", - {0, 12}) { - - refresh(); -} - -void MaxTimeOffroad::refresh() { - QString option = QString::fromStdString(params.get("MaxTimeOffroad")); - QString second = tr("s"); - QString minute = tr("m"); - QString hour = tr("h"); - if (option == "0") { - setLabel(tr("Always On")); - } else if (option == "1") { - setLabel(tr("Immediate")); - } else if (option == "2") { - setLabel("30" + second); - } else if (option == "3") { - setLabel("1" + minute); - } else if (option == "4") { - setLabel("3" + minute); - } else if (option == "5") { - setLabel("5" + minute); - } else if (option == "6") { - setLabel("10" + minute); - } else if (option == "7") { - setLabel("30" + minute); - } else if (option == "8") { - setLabel("1" + hour); - } else if (option == "9") { - setLabel("3" + hour); - } else if (option == "10") { - setLabel("5" + hour); - } else if (option == "11") { - setLabel("10" + hour); - } else if (option == "12") { - setLabel("30" + hour); - } -} - -// Onroad Screen Off (Auto Onroad Screen Timer) -OnroadScreenOff::OnroadScreenOff() : SPOptionControl ( - "OnroadScreenOff", - tr("Driving Screen Off Timer"), - tr("Turn off the device screen or reduce brightness to protect the screen after driving starts. It automatically brightens or turns on when a touch or event occurs."), - "../assets/offroad/icon_metric.png", - {-2, 10}) { - - refresh(); -} - -void OnroadScreenOff::refresh() { - QString option = QString::fromStdString(params.get("OnroadScreenOff")); - QString second = tr("s"); - if (option == "-2") { - setLabel(tr("Always On")); - } else if (option == "-1") { - setLabel("15" + second); - } else if (option == "0") { - setLabel("30" + second); - } else { - setLabel(option + "min(s)"); - } -} - -// Onroad Screen Off Brightness -OnroadScreenOffBrightness::OnroadScreenOffBrightness() : SPOptionControl ( - "OnroadScreenOffBrightness", - tr("Driving Screen Off Brightness (%)"), - tr("When using the Driving Screen Off feature, the brightness is reduced according to the automatic brightness ratio."), - "../assets/offroad/icon_blank.png", - {0, 100}, - 10) { - - refresh(); -} - -void OnroadScreenOffBrightness::refresh() { - QString option = QString::fromStdString(params.get("OnroadScreenOffBrightness")); - if (option == "0") { - setLabel(tr("Dark")); - } else { - setLabel(option); - } -} - -// Brightness Control (Global) -BrightnessControl::BrightnessControl() : SPOptionControl ( - "BrightnessControl", - tr("Brightness Control (Global, %)"), - tr("Manually adjusts the global brightness of the screen."), - "../assets/offroad/icon_metric.png", - {0, 100}, - 5) { - - refresh(); -} - -void BrightnessControl::refresh() { - QString option = QString::fromStdString(params.get("BrightnessControl")); - if (option == "0") { - setLabel(tr("Auto")); - } else { - setLabel(option); - } -} - -// Camera Offset Value -CameraOffset::CameraOffset() : SPOptionControl ( - "CameraOffset", - tr("Camera Offset - Laneful Only (cm)"), - tr("Hack to trick vehicle to be left or right biased in its lane. Decreasing the value will make the car keep more left, increasing will make it keep more right. Changes take effect immediately. Default: +4 cm"), - "../assets/offroad/icon_blank.png", - {-20, 20}) { - - refresh(); -} - -void CameraOffset::refresh() { - QString option = QString::fromStdString(params.get("CameraOffset")); - setLabel(option); -} - -// Path Offset Value -PathOffset::PathOffset() : SPOptionControl ( - "PathOffset", - tr("Path Offset (cm)"), - tr("Hack to trick the model path to be left or right biased of the lane. Decreasing the value will shift the model more left, increasing will shift the model more right. Changes take effect immediately."), - "../assets/offroad/icon_blank.png", - {-10, 10}) { - - refresh(); -} - -void PathOffset::refresh() { - QString option = QString::fromStdString(params.get("PathOffset")); - setLabel(option); -} - -// Auto Lane Change Timer (ALCT) -AutoLaneChangeTimer::AutoLaneChangeTimer() : SPOptionControl ( - "AutoLaneChangeTimer", - tr("Auto Lane Change Timer"), - tr("Set a timer to delay the auto lane change operation when the blinker is used. No nudge on the steering wheel is required to auto lane change if a timer is set.\nPlease use caution when using this feature. Only use the blinker when traffic and road conditions permit."), - "../assets/offroad/icon_road.png", - {0, 5}) { - - refresh(); -} - -void AutoLaneChangeTimer::refresh() { - QString option = QString::fromStdString(params.get("AutoLaneChangeTimer")); - QString second = tr("s"); - if (option == "0") { - setLabel(tr("Nudge")); - } else if (option == "1") { - setLabel(tr("Nudgeless")); - } else if (option == "2") { - setLabel("0.5" + second); - } else if (option == "3") { - setLabel("1" + second); - } else if (option == "4") { - setLabel("1.5" + second); - } else { - setLabel("2" + second); - } -} - -TorqueFriction::TorqueFriction() : SPOptionControl ( - "TorqueFriction", - tr("FRICTION"), - tr("Adjust Friction for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), - "../assets/offroad/icon_blank.png", - {0, 50}) { - - refresh(); -} - -void TorqueFriction::refresh() { - QString torqueFrictionStr = QString::fromStdString(params.get("TorqueFriction")); - float valuef = torqueFrictionStr.toInt() * 0.01; - setTitle(params.getBool("TorquedOverride") ? "FRICTION - Live && Offline" : "FRICTION - Offline Only"); - setLabel(QString::number(valuef)); -} - -TorqueMaxLatAccel::TorqueMaxLatAccel() : SPOptionControl ( - "TorqueMaxLatAccel", - tr("LAT_ACCEL_FACTOR"), - tr("Adjust Max Lateral Acceleration for the Torque Lateral Controller. Live: Override self-tune values; Offline: Override self-tune offline values at car restart."), - "../assets/offroad/icon_blank.png", - {1, 500}) { - - refresh(); -} - -void TorqueMaxLatAccel::refresh() { - QString torqueMaxLatAccelStr = QString::fromStdString(params.get("TorqueMaxLatAccel")); - float valuef = torqueMaxLatAccelStr.toInt() * 0.01; - setTitle(params.getBool("TorquedOverride") ? "LAT_ACCEL_FACTOR - Live && Offline" : "LAT_ACCEL_FACTOR - Offline Only"); - setLabel(QString::number(valuef)); -} - -// Speed Limit Control Custom Offset Type -SpeedLimitOffsetType::SpeedLimitOffsetType() : SPOptionControl ( - "SpeedLimitOffsetType", - tr("Speed Limit Offset Type"), - QString("%1
" - "%2") - .arg(tr("Set speed limit higher or lower than actual speed limit for a more personalized drive.")) - .arg(tr("To use this feature, turn off \"Enable Speed Limit % Offset\".")), - "../assets/offroad/icon_speed_limit.png", - {0, 2}) { - - refresh(); -} - -void SpeedLimitOffsetType::refresh() { - QString option = QString::fromStdString(params.get("SpeedLimitOffsetType")); - if (option == "0") { - setLabel(tr("Default")); - } else if (option == "1") { - setLabel(tr("%")); - } else if (option == "2") { - setLabel(tr("Value")); - } -} - -// Speed Limit Control Custom Offset -SpeedLimitValueOffset::SpeedLimitValueOffset() : SPOptionControl ( - "SpeedLimitValueOffset", - "", - "", - "../assets/offroad/icon_blank.png", - {-30, 30}) { - - refresh(); -} - -void SpeedLimitValueOffset::refresh() { - QString option = QString::fromStdString(params.get("SpeedLimitValueOffset")); - setLabel(option); -} - -// Developer UI Info (Dev UI) -DevUiInfo::DevUiInfo() : SPOptionControl ( - "DevUIInfo", - tr("Developer UI List"), - tr("Select the number of lists of real-time parameters you would like to display on the sunnypilot screen while driving."), - "../assets/offroad/icon_blank.png", - {0, 1}) { - - refresh(); -} - -void DevUiInfo::refresh() { - QString option = QString::fromStdString(params.get("DevUIInfo")); - if (option == "0") { - setLabel(tr("5 Metrics")); - } else { - setLabel(tr("10 Metrics")); - } -} - -// Display Metrics above Chevron -ChevronInfo::ChevronInfo() : SPOptionControl ( - "ChevronInfo", - tr("Display Metrics above Chevron"), - tr("Display useful metrics above the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."), - "../assets/offroad/icon_calibration.png", - {0, 2}) { - - refresh(); -} - -void ChevronInfo::refresh() { - QString option = QString::fromStdString(params.get("ChevronInfo")); - if (option == "0") { - setLabel(tr("OFF")); - } else if (option == "1") { - setLabel(tr("Distance")); - } else if (option == "2") { - setLabel(tr("Speed")); - } -} diff --git a/selfdrive/ui/qt/offroad/sunnypilot_settings.h b/selfdrive/ui/qt/offroad/sunnypilot_settings.h deleted file mode 100644 index 123742df7c..0000000000 --- a/selfdrive/ui/qt/offroad/sunnypilot_settings.h +++ /dev/null @@ -1,348 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/controls.h" - -class SPOptionControl : public AbstractControl { - Q_OBJECT - -private: - struct MinMaxValue { - int min_value; - int max_value; - }; - -public: - SPOptionControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, - const MinMaxValue &range, const int per_value_change = 1) : AbstractControl(title, desc, icon) { - const QString style = R"( - QPushButton { - padding: 0; - border-radius: 50px; - font-size: 35px; - font-weight: 500; - color: #E4E4E4; - background-color: #393939; - } - QPushButton:pressed { - background-color: #4a4a4a; - } - QPushButton:disabled { - color: #33E4E4E4; - } - )"; - - label.setAlignment(Qt::AlignVCenter|Qt::AlignRight); - label.setStyleSheet("color: #e0e879"); - hlayout->addWidget(&label); - - const std::vector button_texts{"-", "+"}; - - key = param.toStdString(); - value = atoi(params.get(key).c_str()); - - button_group = new QButtonGroup(this); - button_group->setExclusive(true); - for (int i = 0; i < button_texts.size(); i++) { - QPushButton *button = new QPushButton(button_texts[i], this); - button->setStyleSheet(style); - button->setFixedSize(150, 100); - hlayout->addWidget(button); - button_group->addButton(button, i); - - int change_value = (i == 0) ? -per_value_change : per_value_change; - - QObject::connect(button, &QPushButton::clicked, [=]() { - key = param.toStdString(); - value = atoi(params.get(key).c_str()); - value += change_value; - value = std::clamp(value, range.min_value, range.max_value); - params.put(key, QString::number(value).toStdString()); - updateLabels(); - - if (update) { - emit updateOtherToggles(); - } - }); - } - } - - void setUpdateOtherToggles(bool _update) { - update = _update; - } - - inline void setLabel(const QString &text) { label.setText(text); } - - void setEnabled(bool enabled) { - for (auto btn : button_group->buttons()) { - btn->setEnabled(enabled); - } - } - -signals: - void updateLabels(); - void updateOtherToggles(); - -private: - std::string key; - int value; - QButtonGroup *button_group; - QLabel label; - Params params; - std::map option_label = {}; - bool update = false; -}; - -class SpeedLimitOffsetType : public SPOptionControl { - Q_OBJECT - -public: - SpeedLimitOffsetType(); - - void refresh(); - -private: - Params params; -}; - -class SpeedLimitValueOffset : public SPOptionControl { - Q_OBJECT - -public: - SpeedLimitValueOffset(); - - void refresh(); - -private: - Params params; -}; - -class MaxTimeOffroad : public SPOptionControl { - Q_OBJECT - -public: - MaxTimeOffroad(); - - void refresh(); - -private: - Params params; -}; - -class OnroadScreenOff : public SPOptionControl { - Q_OBJECT - -public: - OnroadScreenOff(); - - void refresh(); - -private: - Params params; -}; - -class OnroadScreenOffBrightness : public SPOptionControl { - Q_OBJECT - -public: - OnroadScreenOffBrightness(); - - void refresh(); - -private: - Params params; -}; - -class BrightnessControl : public SPOptionControl { - Q_OBJECT - -public: - BrightnessControl(); - - void refresh(); - -private: - Params params; -}; - -class CameraOffset : public SPOptionControl { - Q_OBJECT - -public: - CameraOffset(); - - void refresh(); - -private: - Params params; -}; - -class PathOffset : public SPOptionControl { - Q_OBJECT - -public: - PathOffset(); - - void refresh(); - -private: - Params params; -}; - -class AutoLaneChangeTimer : public SPOptionControl { - Q_OBJECT - -public: - AutoLaneChangeTimer(); - - void refresh(); - -signals: - void toggleUpdated(); - -private: - Params params; -}; - -class TorqueFriction : public SPOptionControl { - Q_OBJECT - -public: - TorqueFriction(); - - void refresh(); - -private: - Params params; -}; - -class TorqueMaxLatAccel : public SPOptionControl { - Q_OBJECT - -public: - TorqueMaxLatAccel(); - - void refresh(); - -private: - Params params; -}; - -class DevUiInfo : public SPOptionControl { - Q_OBJECT - -public: - DevUiInfo(); - - void refresh(); - -private: - Params params; -}; - -class ChevronInfo : public SPOptionControl { - Q_OBJECT - -public: - ChevronInfo(); - - void refresh(); - -private: - Params params; -}; - -class SPGeneralPanel : public ListWidget { - Q_OBJECT - -public: - explicit SPGeneralPanel(QWidget *parent = nullptr); - void showEvent(QShowEvent *event) override; - -public slots: - void updateToggles(); - -private: - Params params; - std::map toggles; - - OnroadScreenOff *onroad_screen_off; - OnroadScreenOffBrightness *onroad_screen_off_brightness; -}; - -class SPControlsPanel : public ListWidget { - Q_OBJECT - -public: - explicit SPControlsPanel(QWidget *parent = nullptr); - void showEvent(QShowEvent *event) override; - -public slots: - void updateToggles(); - -private: - Params params; - std::map toggles; - ParamWatcher *param_watcher; - - CameraOffset *camera_offset; - PathOffset *path_offset; - AutoLaneChangeTimer *auto_lane_change_timer; - SpeedLimitOffsetType *slo_type; - SpeedLimitValueOffset *slvo; - TorqueFriction *friction; - TorqueMaxLatAccel *lat_accel_factor; - ButtonParamControl *dlp_settings; - - void updateButtons(); -}; - -class SPVehiclesPanel : public QWidget { - Q_OBJECT - -public: - explicit SPVehiclesPanel(QWidget *parent = nullptr); - -private: - Params params; - - QStackedLayout* main_layout = nullptr; - QWidget* home = nullptr; - - QWidget* home_widget; -}; - -class SPVehiclesTogglesPanel : public ListWidget { - Q_OBJECT -public: - explicit SPVehiclesTogglesPanel(SPVehiclesPanel *parent); - -private: - Params params; - - ParamControl *stockLongToyota; -}; - -class SPVisualsPanel : public ListWidget { - Q_OBJECT - -public: - explicit SPVisualsPanel(QWidget *parent = nullptr); - void showEvent(QShowEvent *event) override; - -private: - Params params; - std::map toggles; - - DevUiInfo *dev_ui_info; - ChevronInfo *chevron_info; - ButtonParamControl *sidebar_temp_setting; - - void updateToggles(); -}; diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index c80299c642..8326bfe511 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -193,20 +193,11 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) { UIState *s = uiState(); UIScene &scene = s->scene; - SubMaster &sm = *(uiState()->sm); - auto longitudinal_plan_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); + SubMaster &sm = *(s->sm); QRect debug_tap_rect = QRect(rect().center().x() - 200, rect().center().y() - 200, 400, 400); - QRect speed_limit_touch_rect = speed_sgn_rc.adjusted(-50, -50, 50, 50); - if (longitudinal_plan_sp.getSpeedLimit() > 0.0 && speed_limit_touch_rect.contains(e->x(), e->y())) { - // If touching the speed limit sign area when visible - scene.last_speed_limit_sign_tap = seconds_since_boot(); - params.putBool("LastSpeedLimitSignTap", true); - scene.speed_limit_control_enabled = !scene.speed_limit_control_enabled; - params.putBool("SpeedLimitControl", scene.speed_limit_control_enabled); - propagate_event = false; - } else if (scene.debug_snapshot_enabled && debug_tap_rect.contains(e->x(), e->y())) { + if (scene.debug_snapshot_enabled && debug_tap_rect.contains(e->x(), e->y())) { issue_debug_snapshot(sm); propagate_event = false; } @@ -429,8 +420,7 @@ void OnroadSettingsButton::paintEvent(QPaintEvent *event) { void OnroadSettingsButton::updateState(const UIState &s) { const auto cp = (*s.sm)["carParams"].getCarParams(); auto dlp_enabled = true; - auto dec_enabled = s.scene.dynamic_experimental_control_toggle; - bool allow_btn = dlp_enabled || hasLongitudinalControl(cp) || dec_enabled || !cp.getPcmCruiseSpeed(); + bool allow_btn = dlp_enabled || hasLongitudinalControl(cp) || !cp.getPcmCruiseSpeed(); setVisible(allow_btn); setEnabled(allow_btn); @@ -480,6 +470,30 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par updateButtonsLayout(); } +void AnnotatedCameraWidget::mousePressEvent(QMouseEvent* e) { + bool propagate_event = true; + + UIState *s = uiState(); + UIScene &scene = s->scene; + const SubMaster &sm = *(s->sm); + auto longitudinal_plan_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); + + QRect speed_limit_touch_rect = scene.sl_sign_rect; + + if (longitudinal_plan_sp.getSpeedLimit() > 0.0 && speed_limit_touch_rect.contains(e->x(), e->y())) { + // If touching the speed limit sign area when visible + scene.last_speed_limit_sign_tap = seconds_since_boot(); + params.putBool("LastSpeedLimitSignTap", true); + scene.speed_limit_control_enabled = !scene.speed_limit_control_enabled; + params.putBool("SpeedLimitControl", scene.speed_limit_control_enabled); + propagate_event = false; + } + + if (propagate_event) { + QWidget::mousePressEvent(e); + } +} + #ifdef ENABLE_DASHCAM void AnnotatedCameraWidget::offroadTransition(bool offroad) { if (offroad) { @@ -523,6 +537,8 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { const auto nav_instruction = sm["navInstruction"].getNavInstruction(); const auto car_control = sm["carControl"].getCarControl(); const auto radar_state = sm["radarState"].getRadarState(); + const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1; + const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation(); const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters(); const auto lateral_plan_sp = sm["lateralPlanSP"].getLateralPlanSP(); @@ -572,29 +588,6 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { splitPanelVisible = s.scene.map_visible || s.scene.onroad_settings_visible; // ############################## DEV UI START ############################## - - if (sm.updated("liveLocationKalman")) { - auto locationd_location = sm["liveLocationKalman"].getLiveLocationKalman(); - auto locationd_pos = locationd_location.getPositionGeodetic(); - auto locationd_orientation = locationd_location.getCalibratedOrientationNED(); - auto locationd_velocity = locationd_location.getVelocityCalibrated(); - - // Check std norm - auto pos_ecef_std = locationd_location.getPositionECEF().getStd(); - bool pos_accurate_enough = sqrt(pow(pos_ecef_std[0], 2) + pow(pos_ecef_std[1], 2) + pow(pos_ecef_std[2], 2)) < 100; - - auto locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && pos_accurate_enough); - - if (locationd_valid) { - gpsAccuracy = 1.0; //Hardcoding to 1 because not available in liveLocationKalman - altitude = locationd_pos.getValue()[2]; - - bearingAccuracyDeg = 1.0; //Hardcoding to 1 because not available in liveLocationKalman - bearingDeg = locationd_orientation.getValue()[2]; - - } - } - lead_d_rel = radar_state.getLeadOne().getDRel(); lead_v_rel = radar_state.getLeadOne().getVRel(); lead_status = radar_state.getLeadOne().getStatus(); @@ -604,11 +597,14 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { curvature = cs.getCurvature(); roll = sm["liveParameters"].getLiveParameters().getRoll(); memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent(); - devUiEnabled = s.scene.dev_ui_enabled; devUiInfo = s.scene.dev_ui_info; + gpsAccuracy = is_gps_location_external ? gpsLocation.getAccuracy() : 1.0; //External reports accuracy, internal does not. + altitude = gpsLocation.getAltitude(); vEgo = car_state.getVEgo(); aEgo = car_state.getAEgo(); steeringTorqueEps = car_state.getSteeringTorqueEps(); + bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg(); + bearingDeg = gpsLocation.getBearingDeg(); torquedUseParams = (ltp.getUseParams() || s.scene.live_torque_toggle) && !s.scene.torqued_override; latAccelFactorFiltered = ltp.getLatAccelFactorFiltered(); frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered(); @@ -869,6 +865,7 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { p.drawText(set_speed_rect.adjusted(0, 77, 0, 0), Qt::AlignTop | Qt::AlignHCenter, setSpeedStr); const QRect sign_rect = set_speed_rect.adjusted(sign_margin, default_size.height(), -sign_margin, -sign_margin); + uiState()->scene.sl_sign_rect = sign_rect; // US/Canada (MUTCD style) sign if ((mapSourcedSpeedLimit && !is_metric && !isNavSpeedLimit) || has_us_speed_limit) { p.setPen(Qt::NoPen); @@ -921,7 +918,7 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { if (!reversing) { // ####### 1 ROW ####### QRect bar_rect1(rect().left(), rect().bottom() - 60, rect().width(), 61); - if (devUiEnabled && !splitPanelVisible && devUiInfo == 1) { + if (!splitPanelVisible && devUiInfo == 2) { p.setPen(Qt::NoPen); p.setBrush(QColor(0, 0, 0, 100)); p.drawRect(bar_rect1); @@ -930,12 +927,12 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { // ####### 1 COLUMN ######## QRect rc2(rect().right() - (UI_BORDER_SIZE * 2), UI_BORDER_SIZE * 1.5, 184, 152); - if (devUiEnabled) { + if (devUiInfo != 0) { drawRightDevUi(p, rect().right() - 184 - UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2 + rc2.height()); } int rn_btn = 0; - rn_btn = devUiEnabled && !splitPanelVisible && devUiInfo == 1 ? 35 : 0; + rn_btn = !splitPanelVisible && devUiInfo == 2 ? 35 : 0; uiState()->scene.rn_offset = rn_btn; // Stand Still Timer @@ -961,8 +958,8 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { // Turn Speed Sign if (showTurnSpeedLimit) { - QRect rc = speed_sgn_rc; - rc.moveTop(speed_sgn_rc.bottom() + UI_BORDER_SIZE); + QRect rc = uiState()->scene.sl_sign_rect; + rc.moveTop(uiState()->scene.sl_sign_rect.bottom() + UI_BORDER_SIZE); drawTrunSpeedSign(p, rc, turnSpeedLimit, tscSubText, curveSign, tscActive); } } @@ -1618,9 +1615,9 @@ void AnnotatedCameraWidget::drawFeatureStatusText(QPainter &p, int x, int y) { // Dynamic Lane Profile drawFeatureStatusElement(dynamicLaneProfile, feature_text.dlp_list_text, feature_color.dlp_list_color, true, "OFF", "DLP"); - if (uiState()->scene.dynamic_experimental_control_toggle) { + if (longitudinal) { bool cruise_enabled = (*uiState()->sm)["carState"].getCarState().getCruiseState().getEnabled(); - bool dec_enabled = uiState()->scene.dynamic_experimental_control_toggle && uiState()->scene.dynamic_experimental_control; + bool dec_enabled = uiState()->scene.dynamic_experimental_control; bool experimental_mode = (*uiState()->sm)["controlsState"].getControlsState().getExperimentalMode(); QColor dec_color((cruise_enabled && dec_enabled) ? "#4bff66" : "#ffffff"); QRect dec_btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 35cad672da..da5952cd9e 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -124,6 +124,8 @@ private: void updateButtonsLayout(); void drawFeatureStatusText(QPainter &p, int x, int y); + void mousePressEvent(QMouseEvent* e) override; + Params params; QVBoxLayout *main_layout; QHBoxLayout *buttons_layout; @@ -201,7 +203,6 @@ private: float curvature; float roll; int memoryUsagePercent; - bool devUiEnabled; int devUiInfo; float gpsAccuracy; float altitude; @@ -296,8 +297,6 @@ private: QWidget *map = nullptr; QHBoxLayout* split; - Params params; - QWidget *onroad_settings = nullptr; private slots: diff --git a/selfdrive/ui/qt/onroad_settings.cc b/selfdrive/ui/qt/onroad_settings.cc index d05de0849c..ef4304d94e 100644 --- a/selfdrive/ui/qt/onroad_settings.cc +++ b/selfdrive/ui/qt/onroad_settings.cc @@ -134,7 +134,7 @@ void OnroadSettings::changeGapAdjustCruise() { void OnroadSettings::changeDynamicExperimentalControl() { UIScene &scene = uiState()->scene; const auto cp = (*uiState()->sm)["carParams"].getCarParams(); - bool can_change = (hasLongitudinalControl(cp) || (cp.getCustomStockLongAvailable() && params.getBool("CustomStockLongPlanner"))) && scene.dynamic_experimental_control_toggle; + bool can_change = (hasLongitudinalControl(cp) || (cp.getCustomStockLongAvailable() && params.getBool("CustomStockLongPlanner"))); if (can_change) { scene.dynamic_experimental_control = !scene.dynamic_experimental_control; params.putBool("DynamicExperimentalControl", scene.dynamic_experimental_control); @@ -160,7 +160,6 @@ void OnroadSettings::refresh() { param_watcher->addParam("DynamicLaneProfile"); param_watcher->addParam("LongitudinalPersonality"); param_watcher->addParam("DynamicExperimentalControl"); - param_watcher->addParam("DynamicExperimentalControlToggle"); param_watcher->addParam("SpeedLimitControl"); UIScene &scene = uiState()->scene; @@ -186,7 +185,7 @@ void OnroadSettings::refresh() { // Dynamic Experimental Control dec_widget->updateDynamicExperimentalControl("DynamicExperimentalControl"); - dec_widget->setVisible(params.getBool("DynamicExperimentalControlToggle")); + dec_widget->setVisible(hasLongitudinalControl(cp)); // Speed Limit Control slc_widget->updateSpeedLimitControl("SpeedLimitControl"); diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 5319e74b75..77f6b8786a 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -3,6 +3,18 @@ #include #include +QFrame *horizontal_line(QWidget *parent) { + QFrame *line = new QFrame(parent); + line->setFrameShape(QFrame::StyledPanel); + line->setStyleSheet(R"( + border-width: 2px; + border-bottom-style: solid; + border-color: gray; + )"); + line->setFixedHeight(10); + return line; +} + AbstractControl::AbstractControl(const QString &title, const QString &desc, const QString &icon, QWidget *parent) : QFrame(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setMargin(0); @@ -23,7 +35,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons // title title_label = new QPushButton(title); title_label->setFixedHeight(120); - title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; border: none;"); + title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;"); hlayout->addWidget(title_label, 1); // value next to control button @@ -70,12 +82,27 @@ SPAbstractControl::SPAbstractControl(const QString &title, const QString &desc, hlayout->setSpacing(0); // title - title_label = new QPushButton(title); - title_label->setFixedHeight(120); - title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; border: none;"); - main_layout->addWidget(title_label, 1); + if (!title.isEmpty()) { + title_label = new QPushButton(title); + title_label->setFixedHeight(120); + title_label->setStyleSheet("font-size: 50px; font-weight: 450; text-align: left; border: none;"); + main_layout->addWidget(title_label, 1); + + connect(title_label, &QPushButton::clicked, [=]() { + if (!description->isVisible()) { + emit showDescriptionEvent(); + } + + if (!description->text().isEmpty()) { + description->setVisible(!description->isVisible()); + } + }); + } else { + main_layout->addSpacing(20); + } main_layout->addLayout(hlayout); + main_layout->addSpacing(2); // description description = new QLabel(desc); diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index baee0bd6e5..fdab65f9a7 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -14,6 +14,8 @@ #include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/toggle.h" +QFrame *horizontal_line(QWidget *parent = nullptr); + class ElidedLabel : public QLabel { Q_OBJECT @@ -160,6 +162,15 @@ public: toggle.togglePosition(); } }); + + hlayout->removeWidget(&toggle); + hlayout->insertWidget(0, &toggle); + + hlayout->removeWidget(this->icon_label); + this->icon_pixmap = QPixmap(icon).scaledToWidth(20, Qt::SmoothTransformation); + this->icon_label->setPixmap(this->icon_pixmap); + this->icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + hlayout->insertWidget(1, this->icon_label); } void setConfirmation(bool _confirm, bool _store_confirm) { @@ -243,31 +254,25 @@ public: const std::vector &button_texts, const int minimum_button_width = 300) : SPAbstractControl(title, desc, icon) { const QString style = R"( QPushButton { - border-radius: 15px; + border-radius: 20px; font-size: 50px; font-weight: 450; - height:124px; + height:150px; padding: 0 25 0 25; - color: #E4E4E4; + color: #FFFFFF; } QPushButton:pressed { background-color: #4a4a4a; } - QPushButton:enabled { - color: #33E4E4E4; - } QPushButton:checked:enabled { background-color: #696868; - color: #E4E4E4; - } - QPushButton:checked:enabled:hover { - border: 4px solid #696868; - border-radius: 15px; - background-color: #696868; - color: #E4E4E4; } QPushButton:disabled { - color: #33E4E4E4; + color: #33FFFFFF; + } + QPushButton:checked:disabled { + background-color: #121212; + color: #33FFFFFF; } )"; key = param.toStdString(); @@ -291,6 +296,7 @@ public: QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonToggled), [=](int id, bool checked) { if (checked) { params.put(key, std::to_string(id)); + emit buttonToggled(id); } }); } @@ -300,6 +306,8 @@ public: btn->setEnabled(enable); } button_group_enabled = enable; + + update(); } void setButton(QString param) { @@ -317,7 +325,7 @@ protected: // Calculate the total width and height for the background rectangle int w = 0; - int h = 124; + int h = 150; for (int i = 0; i < hlayout->count(); ++i) { QPushButton *button = qobject_cast(hlayout->itemAt(i)->widget()); @@ -326,13 +334,15 @@ protected: } } - // Draw the background rectangle - QRect rect(0 + 2, h + 2, w, h); - p.setBrush(button_group_enabled ? QColor(0, 0, 0, 0) : QColor("#393939")); // Background color - p.setPen(button_group_enabled ? (QPen(QColor("#696868"), 3)) : QPen(Qt::NoPen)); - p.drawRoundedRect(rect, 15, 15); + // Draw the rectangle + QRect rect(0 + 2, h - 24, w, h); + p.setPen(QPen(QColor(button_group_enabled ? "#696868" : "#121212"), 3)); + p.drawRoundedRect(rect, 20, 20); } +signals: + void buttonToggled(int btn_id); + private: std::string key; Params params; @@ -344,7 +354,7 @@ private: class ListWidget : public QWidget { Q_OBJECT public: - explicit ListWidget(QWidget *parent = 0) : QWidget(parent), outer_layout(this) { + explicit ListWidget(QWidget *parent = 0, const bool split_line = true) : QWidget(parent), _split_line(split_line), outer_layout(this) { outer_layout.setMargin(0); outer_layout.setSpacing(0); outer_layout.addLayout(&inner_layout); @@ -362,7 +372,7 @@ private: p.setPen(Qt::gray); for (int i = 0; i < inner_layout.count() - 1; ++i) { QWidget *widget = inner_layout.itemAt(i)->widget(); - if (widget == nullptr || widget->isVisible()) { + if ((widget == nullptr || widget->isVisible()) && _split_line) { QRect r = inner_layout.itemAt(i)->geometry(); int bottom = r.bottom() + inner_layout.spacing() / 2; p.drawLine(r.left() + 40, bottom, r.right() - 40, bottom); @@ -371,6 +381,8 @@ private: } QVBoxLayout outer_layout; QVBoxLayout inner_layout; + + bool _split_line; }; // convenience class for wrapping layouts @@ -382,3 +394,175 @@ public: setLayout(l); } }; + +class SPOptionControl : public SPAbstractControl { + Q_OBJECT + +private: + struct MinMaxValue { + int min_value; + int max_value; + }; + +public: + SPOptionControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + const MinMaxValue &range, const int per_value_change = 1) : _title(title), SPAbstractControl(title, desc, icon) { + const QString style = R"( + QPushButton { + border-radius: 20px; + font-size: 60px; + font-weight: 500; + width: 150px; + height: 150px; + padding: -3 25 3 25; + color: #FFFFFF; + font-weight: bold; + } + QPushButton:pressed { + color: #5C5C5C; + } + QPushButton:disabled { + color: #5C5C5C; + } + )"; + + label.setStyleSheet(label_enabled_style); + label.setFixedWidth(300); + label.setAlignment(Qt::AlignCenter); + + const std::vector button_texts{"-", "+"}; + + key = param.toStdString(); + value = atoi(params.get(key).c_str()); + + button_group = new QButtonGroup(this); + button_group->setExclusive(true); + for (int i = 0; i < button_texts.size(); i++) { + QPushButton *button = new QPushButton(button_texts[i], this); + button->setStyleSheet(style + ((i == 0) ? "QPushButton { text-align: left; }" : + "QPushButton { text-align: right; }")); + hlayout->addWidget(button, 0, ((i == 0) ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignVCenter); + if (i == 0) { + hlayout->addWidget(&label, 0, Qt::AlignCenter); + } + button_group->addButton(button, i); + + int change_value = (i == 0) ? -per_value_change : per_value_change; + + QObject::connect(button, &QPushButton::clicked, [=]() { + key = param.toStdString(); + value = atoi(params.get(key).c_str()); + value += change_value; + value = std::clamp(value, range.min_value, range.max_value); + params.put(key, QString::number(value).toStdString()); + updateLabels(); + + if (request_update) { + emit updateOtherToggles(); + } + }); + } + + hlayout->setAlignment(Qt::AlignLeft); + } + + void setUpdateOtherToggles(bool _update) { + request_update = _update; + } + + inline void setLabel(const QString &text) { label.setText(text); } + + void setEnabled(bool enabled) { + for (auto btn : button_group->buttons()) { + btn->setEnabled(enabled); + } + label.setEnabled(enabled); + label.setStyleSheet(enabled ? label_enabled_style : label_disabled_style); + button_enabled = enabled; + + update(); + } + +protected: + void paintEvent(QPaintEvent *event) override { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + // Calculate the total width and height for the background rectangle + int w = 0; + int h = 150; + + for (int i = 0; i < hlayout->count(); ++i) { + QWidget *widget = qobject_cast(hlayout->itemAt(i)->widget()); + if (widget) { + w += widget->width(); + } + } + + // Draw the rectangle + QRect rect(0, !_title.isEmpty() ? (h - 24) : 20, w, h); + p.setBrush(QColor(button_enabled ? "#b24a4a4a" : "#121212")); // Background color + p.setPen(QPen(Qt::NoPen)); + p.drawRoundedRect(rect, 20, 20); + } + +signals: + void updateLabels(); + void updateOtherToggles(); + +private: + std::string key; + int value; + QButtonGroup *button_group; + QLabel label; + Params params; + std::map option_label = {}; + bool request_update = false; + QString _title = ""; + + const QString label_enabled_style = "font-size: 50px; font-weight: 450; color: #FFFFFF;"; + const QString label_disabled_style = "font-size: 50px; font-weight: 450; color: #5C5C5C;"; + + bool button_enabled = true; +}; + +class SubPanelButton : public QPushButton { + Q_OBJECT + +public: + SubPanelButton(const QString &text, const int minimum_button_width = 800, QWidget *parent = nullptr) : QPushButton(text, parent) { + const QString buttonStyle = R"( + QPushButton { + border-radius: 20px; + font-size: 50px; + font-weight: 450; + height: 150px; + padding: 0 25px 0 25px; + color: #FFFFFF; + } + QPushButton:enabled { + background-color: #393939; + } + QPushButton:pressed { + background-color: #4A4A4A; + } + QPushButton:disabled { + background-color: #121212; + color: #5C5C5C; + } + )"; + + setStyleSheet(buttonStyle); + setFixedWidth(minimum_button_width); + } +}; + +class PanelBackButton : public QPushButton { + Q_OBJECT + +public: + PanelBackButton(const QString &label = "Back", QWidget *parent = nullptr) : QPushButton(label, parent) { + setObjectName("back_btn"); + setFixedSize(400, 100); + } +}; diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc index 978bf83a63..7f6a5ff1cc 100644 --- a/selfdrive/ui/qt/widgets/scrollview.cc +++ b/selfdrive/ui/qt/widgets/scrollview.cc @@ -47,3 +47,11 @@ ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) { void ScrollView::hideEvent(QHideEvent *e) { verticalScrollBar()->setValue(0); } + +void ScrollView::setLastScrollPosition() { + lastScrollPosition = verticalScrollBar()->value(); +} + +void ScrollView::restoreScrollPosition() { + verticalScrollBar()->setValue(lastScrollPosition); +} diff --git a/selfdrive/ui/qt/widgets/scrollview.h b/selfdrive/ui/qt/widgets/scrollview.h index 024331aa39..7e4084412c 100644 --- a/selfdrive/ui/qt/widgets/scrollview.h +++ b/selfdrive/ui/qt/widgets/scrollview.h @@ -9,4 +9,11 @@ public: explicit ScrollView(QWidget *w = nullptr, QWidget *parent = nullptr); protected: void hideEvent(QHideEvent *e) override; + +public slots: + void setLastScrollPosition(); + void restoreScrollPosition(); + +private: + int lastScrollPosition = 0; }; diff --git a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.cc b/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.cc new file mode 100644 index 0000000000..e4c127e6b5 --- /dev/null +++ b/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.cc @@ -0,0 +1,97 @@ +#include "selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h" + +#include +#include +#include +#include + +#include "common/params.h" +#include "selfdrive/ui/qt/request_repeater.h" +#include "selfdrive/ui/qt/util.h" + +static QLabel* newLabel(const QString& text, const QString &type) { + QLabel* label = new QLabel(text); + label->setProperty("type", type); + return label; +} + +DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { + metric_ = Params().getBool("IsMetric"); + + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(50, 50, 50, 60); + + auto add_stats_layouts = [=](const QString &title, StatsLabels& labels) { + QGridLayout* grid_layout = new QGridLayout; + grid_layout->setVerticalSpacing(10); + grid_layout->setContentsMargins(0, 10, 0, 10); + + int row = 0; + grid_layout->addWidget(newLabel(title, "title"), row++, 0, 1, 3); + grid_layout->addItem(new QSpacerItem(0, 50), row++, 0, 1, 1); + + grid_layout->addWidget(labels.routes = newLabel("0", "number"), row, 0, Qt::AlignLeft); + grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft); + grid_layout->addWidget(labels.hours = newLabel("0", "number"), row, 2, Qt::AlignLeft); + + grid_layout->addWidget(newLabel((tr("Drives")), "unit"), row + 1, 0, Qt::AlignLeft); + grid_layout->addWidget(labels.distance_unit = newLabel(getDistanceUnit(), "unit"), row + 1, 1, Qt::AlignLeft); + grid_layout->addWidget(newLabel(tr("Hours"), "unit"), row + 1, 2, Qt::AlignLeft); + + main_layout->addLayout(grid_layout); + }; + + add_stats_layouts(tr("ALL TIME"), all_); + main_layout->addStretch(); + add_stats_layouts(tr("PAST WEEK"), week_); + + if (auto dongleId = getDongleId()) { + QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats"; + RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_DriveStats", 30); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &DriveStats::parseResponse); + } + + setStyleSheet(R"( + DriveStats { + background-color: #333333; + border-radius: 10px; + } + + QLabel[type="title"] { font-size: 51px; font-weight: 500; } + QLabel[type="number"] { font-size: 78px; font-weight: 500; } + QLabel[type="unit"] { font-size: 51px; font-weight: 300; color: #A0A0A0; } + )"); +} + +void DriveStats::updateStats() { + auto update = [=](const QJsonObject& obj, StatsLabels& labels) { + labels.routes->setText(QString::number((int)obj["routes"].toDouble())); + labels.distance->setText(QString::number(int(obj["distance"].toDouble() * (metric_ ? MILE_TO_KM : 1)))); + labels.distance_unit->setText(getDistanceUnit()); + labels.hours->setText(QString::number((int)(obj["minutes"].toDouble() / 60))); + }; + + QJsonObject json = stats_.object(); + update(json["all"].toObject(), all_); + update(json["week"].toObject(), week_); +} + +void DriveStats::parseResponse(const QString& response, bool success) { + if (!success) return; + + QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8()); + if (doc.isNull()) { + qDebug() << "JSON Parse failed on getting past drives statistics"; + return; + } + stats_ = doc; + updateStats(); +} + +void DriveStats::showEvent(QShowEvent* event) { + bool metric = Params().getBool("IsMetric"); + if (metric_ != metric) { + metric_ = metric; + updateStats(); + } +} diff --git a/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h b/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h new file mode 100644 index 0000000000..5e2d96b240 --- /dev/null +++ b/selfdrive/ui/qt/widgets/sunnypilot/drive_stats.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +class DriveStats : public QFrame { + Q_OBJECT + +public: + explicit DriveStats(QWidget* parent = 0); + +private: + void showEvent(QShowEvent *event) override; + void updateStats(); + inline QString getDistanceUnit() const { return metric_ ? tr("KM") : tr("Miles"); } + + bool metric_; + QJsonDocument stats_; + struct StatsLabels { + QLabel *routes, *distance, *distance_unit, *hours; + } all_, week_; + +private slots: + void parseResponse(const QString &response, bool success); +}; diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 9e7a81ddb7..611332da3d 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -238,13 +238,11 @@ void ui_update_params(UIState *s) { s->scene.onroadScreenOffEvent = params.getBool("OnroadScreenOffEvent"); s->scene.brightness = std::atoi(params.get("BrightnessControl").c_str()); s->scene.stand_still_timer = params.getBool("StandStillTimer"); - s->scene.speed_limit_perc_offset = params.getBool("SpeedLimitPercOffset"); s->scene.show_debug_ui = params.getBool("ShowDebugUI"); s->scene.debug_snapshot_enabled = params.getBool("EnableDebugSnapshot"); s->scene.hide_vego_ui = params.getBool("HideVEgoUi"); s->scene.true_vego_ui = params.getBool("TrueVEgoUi"); s->scene.chevron_data = std::atoi(params.get("ChevronInfo").c_str()); - s->scene.dev_ui_enabled = params.getBool("DevUI"); s->scene.dev_ui_info = std::atoi(params.get("DevUIInfo").c_str()); s->scene.button_auto_hide = params.getBool("ButtonAutoHide"); s->scene.reverse_dm_cam = params.getBool("ReverseDmCam"); @@ -254,7 +252,6 @@ void ui_update_params(UIState *s) { s->scene.map_3d_buildings = params.getBool("Map3DBuildings"); s->scene.live_torque_toggle = params.getBool("LiveTorque"); s->scene.torqued_override = params.getBool("TorquedOverride"); - s->scene.dynamic_experimental_control_toggle = params.getBool("DynamicExperimentalControlToggle"); // Handle Onroad Screen Off params if (s->scene.onroadScreenOff > 0) { @@ -375,7 +372,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "uiPlan", "longitudinalPlanSP", "liveMapDataSP", - "carControl", "lateralPlanSP", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", "controlsStateSP" + "carControl", "lateralPlanSP", "gpsLocation", "gpsLocationExternal", "liveParameters", "liveTorqueParameters", "controlsStateSP" }); Params params; diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 50c7b55e55..5b1365c4dc 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -20,8 +20,6 @@ const int UI_BORDER_SIZE = 30; const int UI_HEADER_HEIGHT = 420; -const QRect speed_sgn_rc(UI_BORDER_SIZE * 2, UI_BORDER_SIZE * 2.5 + 202, 184, 184); - struct FeatureStatusText { const QStringList dlp_list_text = { "Laneful", "Laneless", "Auto" }; const QStringList gac_list_text = { "Maniac", "Aggressive", "Standard", "Relaxed" }; @@ -164,7 +162,6 @@ typedef struct UIScene { // Speed limit control bool speed_limit_control_enabled; - bool speed_limit_perc_offset; double last_speed_limit_sign_tap; // modelV2 @@ -212,7 +209,6 @@ typedef struct UIScene { int longitudinal_personality; bool map_visible; - bool dev_ui_enabled; int dev_ui_info; int rn_offset; bool live_torque_toggle; @@ -239,7 +235,9 @@ typedef struct UIScene { bool torqued_override; - bool dynamic_experimental_control_toggle, dynamic_experimental_control; + bool dynamic_experimental_control; + + QRect sl_sign_rect; } UIScene; class UIState : public QObject { diff --git a/system/clocksd/.gitignore b/system/clocksd/.gitignore deleted file mode 100644 index a6d841d65e..0000000000 --- a/system/clocksd/.gitignore +++ /dev/null @@ -1 +0,0 @@ -clocksd diff --git a/system/clocksd/SConscript b/system/clocksd/SConscript deleted file mode 100644 index d1cf13e9e8..0000000000 --- a/system/clocksd/SConscript +++ /dev/null @@ -1,2 +0,0 @@ -Import('env', 'common', 'cereal', 'messaging') -env.Program('clocksd.cc', LIBS=[common, cereal, messaging, 'capnp', 'zmq', 'kj']) diff --git a/system/clocksd/clocksd.cc b/system/clocksd/clocksd.cc deleted file mode 100644 index a5912cf51a..0000000000 --- a/system/clocksd/clocksd.cc +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include -#include - -#include -#include - -// Apple doesn't have timerfd -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include -#include - -#include "cereal/messaging/messaging.h" -#include "common/timing.h" -#include "common/util.h" - -ExitHandler do_exit; - -int main() { - setpriority(PRIO_PROCESS, 0, -13); - PubMaster pm({"clocks"}); - -#ifndef __APPLE__ - int timerfd = timerfd_create(CLOCK_BOOTTIME, 0); - assert(timerfd >= 0); - - struct itimerspec spec = {0}; - spec.it_interval.tv_sec = 1; - spec.it_interval.tv_nsec = 0; - spec.it_value.tv_sec = 1; - spec.it_value.tv_nsec = 0; - - int err = timerfd_settime(timerfd, 0, &spec, 0); - assert(err == 0); - - uint64_t expirations = 0; - while (!do_exit && (err = read(timerfd, &expirations, sizeof(expirations)))) { - if (err < 0) { - if (errno == EINTR) continue; - break; - } -#else - // Just run at 1Hz on apple - while (!do_exit) { - util::sleep_for(1000); -#endif - - uint64_t boottime = nanos_since_boot(); - uint64_t monotonic = nanos_monotonic(); - uint64_t monotonic_raw = nanos_monotonic_raw(); - uint64_t wall_time = nanos_since_epoch(); - - MessageBuilder msg; - auto clocks = msg.initEvent().initClocks(); - - clocks.setBootTimeNanos(boottime); - clocks.setMonotonicNanos(monotonic); - clocks.setMonotonicRawNanos(monotonic_raw); - clocks.setWallTimeNanos(wall_time); - - pm.send("clocks", msg); - } - -#ifndef __APPLE__ - close(timerfd); -#endif - return 0; -} diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py new file mode 100755 index 0000000000..df76c1a5fd --- /dev/null +++ b/system/hardware/tici/esim.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +import os +import math +import time +import binascii +import requests +import serial +import subprocess + + +def post(url, payload): + print() + print("POST to", url) + r = requests.post( + url, + data=payload, + verify=False, + headers={ + "Content-Type": "application/json", + "X-Admin-Protocol": "gsma/rsp/v2.2.0", + "charset": "utf-8", + "User-Agent": "gsma-rsp-lpad", + }, + ) + print("resp", r) + print("resp text", repr(r.text)) + print() + r.raise_for_status() + + ret = f"HTTP/1.1 {r.status_code}" + ret += ''.join(f"{k}: {v}" for k, v in r.headers.items() if k != 'Connection') + return ret.encode() + r.content + + +class LPA: + def __init__(self): + self.dev = serial.Serial('/dev/ttyUSB2', baudrate=57600, timeout=1, bytesize=8) + self.dev.reset_input_buffer() + self.dev.reset_output_buffer() + assert "OK" in self.at("AT") + + def at(self, cmd): + print(f"==> {cmd}") + self.dev.write(cmd.encode() + b'\r\n') + + r = b"" + cnt = 0 + while b"OK" not in r and b"ERROR" not in r and cnt < 20: + r += self.dev.read(8192).strip() + cnt += 1 + r = r.decode() + print(f"<== {repr(r)}") + return r + + def download_ota(self, qr): + return self.at(f'AT+QESIM="ota","{qr}"') + + def download(self, qr): + smdp = qr.split('$')[1] + out = self.at(f'AT+QESIM="download","{qr}"') + for _ in range(5): + line = out.split("+QESIM: ")[1].split("\r\n\r\nOK")[0] + + parts = [x.strip().strip('"') for x in line.split(',', maxsplit=4)] + print(repr(parts)) + trans, ret, url, payloadlen, payload = parts + assert trans == "trans" and ret == "0" + assert len(payload) == int(payloadlen) + + r = post(f"https://{smdp}/{url}", payload) + to_send = binascii.hexlify(r).decode() + + chunk_len = 1400 + for i in range(math.ceil(len(to_send) / chunk_len)): + state = 1 if (i+1)*chunk_len < len(to_send) else 0 + data = to_send[i * chunk_len : (i+1)*chunk_len] + out = self.at(f'AT+QESIM="trans",{len(to_send)},{state},{i},{len(data)},"{data}"') + assert "OK" in out + + if '+QESIM:"download",1' in out: + raise Exception("profile install failed") + elif '+QESIM:"download",0' in out: + print("done, successfully loaded") + break + + def enable(self, iccid): + self.at(f'AT+QESIM="enable","{iccid}"') + + def disable(self, iccid): + self.at(f'AT+QESIM="disable","{iccid}"') + + def delete(self, iccid): + self.at(f'AT+QESIM="delete","{iccid}"') + + def list_profiles(self): + out = self.at('AT+QESIM="list"') + return out.strip().splitlines()[1:] + + +if __name__ == "__main__": + import sys + + if "RESTART" in os.environ: + subprocess.check_call("sudo systemctl stop ModemManager", shell=True) + subprocess.check_call("/usr/comma/lte/lte.sh stop_blocking", shell=True) + subprocess.check_call("/usr/comma/lte/lte.sh start", shell=True) + while not os.path.exists('/dev/ttyUSB2'): + time.sleep(1) + time.sleep(3) + + lpa = LPA() + print(lpa.list_profiles()) + if len(sys.argv) > 1: + lpa.download(sys.argv[1]) + print(lpa.list_profiles()) diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index 1409e43cfb..1ec9b6ec59 100755 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -28,7 +28,7 @@ class Proc: PROCS = [ Proc('camerad', 2.1, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), - Proc('modeld', 0.93, atol=0.2, msgs=['modelV2']), + Proc('modeld', 1.0, atol=0.2, msgs=['modelV2']), Proc('dmonitoringmodeld', 0.4, msgs=['driverStateV2']), Proc('encoderd', 0.23, msgs=[]), Proc('mapsd', 0.05, msgs=['mapRenderState']), diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index b520adc94c..0932f28a93 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -31,6 +31,8 @@ allow_sleep = bool(os.getenv("UPLOADER_SLEEP", "1")) force_wifi = os.getenv("FORCEWIFI") is not None fake_upload = os.getenv("FAKEUPLOAD") is not None +OFFROAD_TRANSITION_TIMEOUT = 900. # wait until offroad for 15 minutes before allowing uploads + class FakeRequest: def __init__(self): @@ -249,9 +251,8 @@ def uploader_fn(exit_event: threading.Event) -> None: params = Params() dongle_id = params.get("DongleId", encoding='utf8') - transition_to_offroad_last = 0. - disable_onroad_upload_offroad_transition_timeout = 900. # wait until offroad for 15 minutes before starting uploads - offroad_last = params.get_bool("IsOffroad") + offroad_transition_prev = 0. + offroad_last = False if dongle_id is None: cloudlog.info("uploader missing dongle_id") @@ -271,10 +272,9 @@ def uploader_fn(exit_event: threading.Event) -> None: offroad = params.get_bool("IsOffroad") t = time.monotonic() if offroad and not offroad_last and t > 300.: - transition_to_offroad_last = time.monotonic() + offroad_transition_prev = time.monotonic() offroad_last = offroad - offroad = params.get_bool("IsOffroad") network_type = sm['deviceState'].networkType if not force_wifi else NetworkType.wifi if network_type == NetworkType.none: if allow_sleep: @@ -282,12 +282,12 @@ def uploader_fn(exit_event: threading.Event) -> None: continue if params.get_bool("DisableOnroadUploads"): - if not offroad or (transition_to_offroad_last > 0. and t - transition_to_offroad_last < disable_onroad_upload_offroad_transition_timeout): + if not offroad or (offroad_transition_prev > 0. and t - offroad_transition_prev < OFFROAD_TRANSITION_TIMEOUT): if not offroad: cloudlog.info("not uploading: onroad uploads disabled") else: - wait_minutes = int(disable_onroad_upload_offroad_transition_timeout / 60) - time_left = disable_onroad_upload_offroad_transition_timeout - (t - transition_to_offroad_last) + wait_minutes = int(OFFROAD_TRANSITION_TIMEOUT / 60) + time_left = OFFROAD_TRANSITION_TIMEOUT - (t - offroad_transition_prev) if time_left / 60. > 2.: time_left_str = f"{int(time_left / 60)} minute(s)" else: diff --git a/system/sensord/rawgps/test_rawgps.py b/system/sensord/rawgps/test_rawgps.py index 02777d5a1d..2a0ee656e2 100755 --- a/system/sensord/rawgps/test_rawgps.py +++ b/system/sensord/rawgps/test_rawgps.py @@ -5,13 +5,11 @@ import time import datetime import unittest import subprocess -import numpy as np import cereal.messaging as messaging from openpilot.system.hardware import TICI from openpilot.system.sensord.rawgps.rawgpsd import at_cmd, wait_for_modem from openpilot.selfdrive.manager.process_config import managed_processes -from openpilot.common.transformations.coordinates import ecef_from_geodetic GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0'))) @@ -123,22 +121,5 @@ class TestRawgpsd(unittest.TestCase): managed_processes['rawgpsd'].stop() self.check_assistance(True) - @unittest.skipIf(not GOOD_SIGNAL, "No good GPS signal") - def test_fix(self): - managed_processes['rawgpsd'].start() - managed_processes['laikad'].start() - assert self._wait_for_output(60) - assert self.sm.updated['qcomGnss'] - assert self.sm.updated['gpsLocation'] - assert self.sm['gpsLocation'].flags == 1 - module_fix = ecef_from_geodetic([self.sm['gpsLocation'].latitude, - self.sm['gpsLocation'].longitude, - self.sm['gpsLocation'].altitude]) - assert self.sm['gnssMeasurements'].positionECEF.valid - total_diff = np.array(self.sm['gnssMeasurements'].positionECEF.value) - module_fix - self.assertLess(np.linalg.norm(total_diff), 100) - managed_processes['laikad'].stop() - managed_processes['rawgpsd'].stop() - if __name__ == "__main__": unittest.main(failfast=True) diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 2bd6cfd6cc..dee7cc5248 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -279,38 +279,50 @@ void ChartView::updateSeriesPoints() { } } -void ChartView::updateSeries(const cabana::Signal *sig, bool clear) { +void ChartView::appendCanEvents(const cabana::Signal *sig, const std::vector &events, + std::vector &vals, std::vector &step_vals) { + vals.reserve(vals.size() + events.capacity()); + step_vals.reserve(step_vals.size() + events.capacity() * 2); + + double value = 0; + const uint64_t begin_mono_time = can->routeStartTime() * 1e9; + for (const CanEvent *e : events) { + if (sig->getValue(e->dat, e->size, &value)) { + const double ts = (e->mono_time - std::min(e->mono_time, begin_mono_time)) / 1e9; + vals.emplace_back(ts, value); + if (!step_vals.empty()) + step_vals.emplace_back(ts, step_vals.back().y()); + step_vals.emplace_back(ts, value); + } + } +} + +void ChartView::updateSeries(const cabana::Signal *sig, const MessageEventsMap *msg_new_events) { for (auto &s : sigs) { if (!sig || s.sig == sig) { - if (clear) { + if (!msg_new_events) { s.vals.clear(); s.step_vals.clear(); - s.last_value_mono_time = 0; + } + auto events = msg_new_events ? msg_new_events : &can->eventsMap(); + auto it = events->find(s.msg_id); + if (it == events->end() || it->second.empty()) continue; + + if (s.vals.empty() || (it->second.back()->mono_time / 1e9 - can->routeStartTime()) > s.vals.back().x()) { + appendCanEvents(s.sig, it->second, s.vals, s.step_vals); + } else { + std::vector vals, step_vals; + appendCanEvents(s.sig, it->second, vals, step_vals); + s.vals.insert(std::lower_bound(s.vals.begin(), s.vals.end(), vals.front().x(), xLessThan), + vals.begin(), vals.end()); + s.step_vals.insert(std::lower_bound(s.step_vals.begin(), s.step_vals.end(), step_vals.front().x(), xLessThan), + step_vals.begin(), step_vals.end()); } - const auto &msgs = can->events(s.msg_id); - s.vals.reserve(msgs.capacity()); - s.step_vals.reserve(msgs.capacity() * 2); - - auto first = std::upper_bound(msgs.cbegin(), msgs.cend(), s.last_value_mono_time, CompareCanEvent()); - const double route_start_time = can->routeStartTime(); - for (auto end = msgs.cend(); first != end; ++first) { - const CanEvent *e = *first; - double value = 0; - if (s.sig->getValue(e->dat, e->size, &value)) { - double ts = e->mono_time / 1e9 - route_start_time; // seconds - s.vals.append({ts, value}); - if (!s.step_vals.empty()) { - s.step_vals.append({ts, s.step_vals.back().y()}); - } - s.step_vals.append({ts, value}); - s.last_value_mono_time = e->mono_time; - } - } if (!can->liveStreaming()) { s.segment_tree.build(s.vals); } - s.series->replace(series_type == SeriesType::StepLine ? s.step_vals : s.vals); + s.series->replace(QVector::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals)); } } updateAxisY(); @@ -320,7 +332,7 @@ void ChartView::updateSeries(const cabana::Signal *sig, bool clear) { // auto zoom on yaxis void ChartView::updateAxisY() { - if (sigs.isEmpty()) return; + if (sigs.empty()) return; double min = std::numeric_limits::max(); double max = std::numeric_limits::lowest(); @@ -344,9 +356,7 @@ void ChartView::updateAxisY() { if (it->y() > s.max) s.max = it->y(); } } else { - auto [min_y, max_y] = s.segment_tree.minmax(std::distance(s.vals.cbegin(), first), std::distance(s.vals.cbegin(), last)); - s.min = min_y; - s.max = max_y; + std::tie(s.min, s.max) = s.segment_tree.minmax(std::distance(s.vals.cbegin(), first), std::distance(s.vals.cbegin(), last)); } min = std::min(min, s.min); max = std::max(max, s.max); @@ -365,7 +375,7 @@ void ChartView::updateAxisY() { axis_y->setRange(min_y, max_y); axis_y->setTickCount(tick_count); - int n = std::max(int(-std::floor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1; + int n = std::max(int(-std::floor(std::log10((max_y - min_y) / (tick_count - 1)))), 0); int max_label_width = 0; QFontMetrics fm(axis_y->labelsFont()); for (int i = 0; i < tick_count; i++) { @@ -453,7 +463,7 @@ static QPixmap getDropPixmap(const QPixmap &src) { return px; } -void ChartView::contextMenuEvent(QContextMenuEvent *event) { +void ChartView::contextMenuEvent(QContextMenuEvent *event) { QMenu context_menu(this); context_menu.addActions(menu->actions()); context_menu.addSeparator(); @@ -492,13 +502,9 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) { rubber->hide(); auto rect = rubber->geometry().normalized(); - double min = chart()->mapToValue(rect.topLeft()).x(); - double max = chart()->mapToValue(rect.bottomRight()).x(); - // Prevent zooming/seeking past the end of the route - min = std::clamp(min, 0., can->totalSeconds()); - max = std::clamp(max, 0., can->totalSeconds()); - + double min = std::clamp(chart()->mapToValue(rect.topLeft()).x(), 0., can->totalSeconds()); + double max = std::clamp(chart()->mapToValue(rect.bottomRight()).x(), 0., can->totalSeconds()); if (rubber->width() <= 0) { // no rubber dragged, seek to mouse position can->seekTo(min); @@ -623,7 +629,7 @@ void ChartView::dropEvent(QDropEvent *event) { source_chart->chart()->removeSeries(s.series); addSeries(s.series); } - sigs.append(source_chart->sigs); + sigs.insert(sigs.end(), std::move_iterator(source_chart->sigs.begin()), std::move_iterator(source_chart->sigs.end())); updateAxisY(); updateTitle(); startAnimation(); @@ -763,13 +769,8 @@ void ChartView::drawSignalValue(QPainter *painter) { painter->setPen(chart()->legend()->labelColor()); int i = 0; for (auto &s : sigs) { - QString value = "--"; - if (s.series->isVisible()) { - auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x; }); - if (it != s.vals.crend() && it->x() >= axis_x->min()) { - value = s.sig->formatValue(it->y()); - } - } + auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x; }); + QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? s.sig->formatValue(it->y()) : "--"; QRectF marker_rect = legend_markers[i++]->sceneBoundingRect(); QRectF value_rect(marker_rect.bottomLeft() - QPoint(0, 1), marker_rect.size()); QString elided_val = painter->fontMetrics().elidedText(value, Qt::ElideRight, value_rect.width()); @@ -841,9 +842,8 @@ void ChartView::setSeriesType(SeriesType type) { s.series->deleteLater(); } for (auto &s : sigs) { - auto series = createSeries(series_type, s.sig->color); - series->replace(series_type == SeriesType::StepLine ? s.step_vals : s.vals); - s.series = series; + s.series = createSeries(series_type, s.sig->color); + s.series->replace(QVector::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals)); } updateSeriesPoints(); updateTitle(); diff --git a/tools/cabana/chart/chart.h b/tools/cabana/chart/chart.h index f91b81cc91..896eaaf2a3 100644 --- a/tools/cabana/chart/chart.h +++ b/tools/cabana/chart/chart.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -31,7 +32,7 @@ public: ChartView(const std::pair &x_range, ChartsWidget *parent = nullptr); void addSignal(const MessageId &msg_id, const cabana::Signal *sig); bool hasSignal(const MessageId &msg_id, const cabana::Signal *sig) const; - void updateSeries(const cabana::Signal *sig = nullptr, bool clear = true); + void updateSeries(const cabana::Signal *sig = nullptr, const MessageEventsMap *msg_new_events = nullptr); void updatePlot(double cur, double min, double max); void setSeriesType(SeriesType type); void updatePlotArea(int left, bool force = false); @@ -43,9 +44,8 @@ public: MessageId msg_id; const cabana::Signal *sig = nullptr; QXYSeries *series = nullptr; - QVector vals; - QVector step_vals; - uint64_t last_value_mono_time = 0; + std::vector vals; + std::vector step_vals; QPointF track_pt{}; SegmentTree segment_tree; double min = 0; @@ -64,6 +64,8 @@ private slots: void signalRemoved(const cabana::Signal *sig) { removeIf([=](auto &s) { return s.sig == sig; }); } private: + void appendCanEvents(const cabana::Signal *sig, const std::vector &events, + std::vector &vals, std::vector &step_vals); void createToolButtons(); void addSeries(QXYSeries *series); void contextMenuEvent(QContextMenuEvent *event) override; @@ -107,7 +109,7 @@ private: QGraphicsProxyWidget *close_btn_proxy; QGraphicsProxyWidget *manage_btn_proxy; TipLabel tip_label; - QList sigs; + std::vector sigs; double cur_sec = 0; SeriesType series_type = SeriesType::Line; bool is_scrubbing = false; diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index 64eb99325c..63e103793c 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -12,7 +12,7 @@ #include "tools/cabana/chart/chart.h" const int MAX_COLUMN_COUNT = 4; -const int CHART_SPACING = 10; +const int CHART_SPACING = 4; ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_timer(this), QFrame(parent) { setFrameStyle(QFrame::StyledPanel | QFrame::Plain); @@ -78,8 +78,9 @@ ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_tim // charts charts_container = new ChartsContainer(this); - + charts_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); charts_scroll = new QScrollArea(this); + charts_scroll->viewport()->setBackgroundRole(QPalette::Base); charts_scroll->setFrameStyle(QFrame::NoFrame); charts_scroll->setWidgetResizable(true); charts_scroll->setWidget(charts_container); @@ -149,11 +150,10 @@ void ChartsWidget::updateTabBar() { } } -void ChartsWidget::eventsMerged() { +void ChartsWidget::eventsMerged(const MessageEventsMap &new_events) { QFutureSynchronizer future_synchronizer; - bool clear = !can->liveStreaming(); for (auto c : charts) { - future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, clear)); + future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, &new_events)); } } @@ -190,7 +190,7 @@ void ChartsWidget::updateState() { if (pos < 0 || pos > 0.8) { display_range.first = std::max(0.0, cur_sec - max_chart_range * 0.1); } - double max_sec = std::min(std::floor(display_range.first + max_chart_range), can->totalSeconds()); + double max_sec = std::min(display_range.first + max_chart_range, can->totalSeconds()); display_range.first = std::max(0.0, max_sec - max_chart_range); display_range.second = display_range.first + max_chart_range; } else if (cur_sec < (zoomed_range.first - 0.1) || cur_sec >= zoomed_range.second) { @@ -282,7 +282,7 @@ void ChartsWidget::splitChart(ChartView *src_chart) { it->series->setColor(it->sig->color); c->addSeries(it->series); - c->sigs.push_back(*it); + c->sigs.emplace_back(std::move(*it)); c->updateAxisY(); c->updateTitle(); it = src_chart->sigs.erase(it); @@ -322,7 +322,7 @@ void ChartsWidget::updateLayout(bool force) { } for (int i = 0; i < current_charts.size(); ++i) { charts_layout->addWidget(current_charts[i], i / n, i % n); - if (current_charts[i]->sigs.isEmpty()) { + if (current_charts[i]->sigs.empty()) { // the chart will be resized after add signal. delay setVisible to reduce flicker. QTimer::singleShot(0, [c = current_charts[i]]() { c->setVisible(true); }); } else { @@ -474,8 +474,9 @@ bool ChartsWidget::event(QEvent *event) { ChartsContainer::ChartsContainer(ChartsWidget *parent) : charts_widget(parent), QWidget(parent) { setAcceptDrops(true); + setBackgroundRole(QPalette::Window); QVBoxLayout *charts_main_layout = new QVBoxLayout(this); - charts_main_layout->setContentsMargins(0, 10, 0, 0); + charts_main_layout->setContentsMargins(0, CHART_SPACING, 0, CHART_SPACING); charts_layout = new QGridLayout(); charts_layout->setSpacing(CHART_SPACING); charts_main_layout->addLayout(charts_layout); @@ -519,15 +520,11 @@ void ChartsContainer::paintEvent(QPaintEvent *ev) { r.setHeight(CHART_SPACING); } - const int margin = (CHART_SPACING - 2) / 2; - QPainterPath path; - path.addPolygon(QPolygonF({r.topLeft(), QPointF(r.left() + CHART_SPACING, r.top() + r.height() / 2), r.bottomLeft()})); - path.addPolygon(QPolygonF({r.topRight(), QPointF(r.right() - CHART_SPACING, r.top() + r.height() / 2), r.bottomRight()})); - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - p.fillPath(path, palette().highlight()); - p.fillRect(r.adjusted(2, margin, -2, -margin), palette().highlight()); + p.setPen(QPen(palette().highlight(), 2)); + p.drawLine(r.topLeft() + QPoint(1, 0), r.bottomLeft() + QPoint(1, 0)); + p.drawLine(r.topLeft() + QPoint(0, r.height() / 2), r.topRight() + QPoint(0, r.height() / 2)); + p.drawLine(r.topRight(), r.bottomRight()); } } diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index 6a7b535543..c85cd09963 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -63,7 +63,7 @@ private: void removeChart(ChartView *chart); void splitChart(ChartView *chart); QRect chartVisibleRect(ChartView *chart); - void eventsMerged(); + void eventsMerged(const MessageEventsMap &new_events); void updateState(); void zoomReset(); void startAutoScroll(); diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 87fa267d5a..472ffce104 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -2,6 +2,7 @@ #include #include +#include #include "tools/cabana/commands.h" #include "tools/cabana/mainwin.h" @@ -22,19 +23,15 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart // message title QHBoxLayout *title_layout = new QHBoxLayout(); title_layout->setContentsMargins(3, 6, 3, 0); - time_label = new QLabel(this); - time_label->setToolTip(tr("Current time")); - time_label->setStyleSheet("QLabel{font-weight:bold;}"); - title_layout->addWidget(time_label); - name_label = new ElidedLabel(this); + auto spacer = new QSpacerItem(0, 1); + title_layout->addItem(spacer); + title_layout->addWidget(name_label = new ElidedLabel(this), 1); name_label->setStyleSheet("QLabel{font-weight:bold;}"); name_label->setAlignment(Qt::AlignCenter); - name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - title_layout->addWidget(name_label); auto edit_btn = new ToolButton("pencil", tr("Edit Message")); title_layout->addWidget(edit_btn); - remove_btn = new ToolButton("x-lg", tr("Remove Message")); - title_layout->addWidget(remove_btn); + title_layout->addWidget(remove_btn = new ToolButton("x-lg", tr("Remove Message"))); + spacer->changeSize(edit_btn->sizeHint().width() * 2 + 9, 1); main_layout->addLayout(title_layout); // warning @@ -151,7 +148,6 @@ void DetailWidget::refresh() { } void DetailWidget::updateState(const QHash *msgs) { - time_label->setText(QString::number(can->currentSec(), 'f', 3)); if ((msgs && !msgs->contains(msg_id))) return; diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index ed6a865f53..5bb4b7f305 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -42,7 +42,7 @@ private: void updateState(const QHash * msgs = nullptr); MessageId msg_id; - QLabel *time_label, *warning_icon, *warning_label; + QLabel *warning_icon, *warning_label; ElidedLabel *name_label; QWidget *warning_widget; TabBar *tabbar; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 5cd5e70267..9ac88032d5 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -22,12 +22,6 @@ #include "tools/cabana/streamselector.h" #include "tools/cabana/tools/findsignal.h" -static MainWindow *main_win = nullptr; -void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl; - if (main_win) emit main_win->showMessage(msg, 2000); -} - MainWindow::MainWindow() : QMainWindow() { createDockWindows(); setCentralWidget(center_widget = new CenterWidget(this)); @@ -45,20 +39,23 @@ MainWindow::MainWindow() : QMainWindow() { } restoreState(settings.window_state); + // install handlers + static auto static_main_win = this; qRegisterMetaType("uint64_t"); qRegisterMetaType("SourceSet"); qRegisterMetaType("ReplyMsgType"); - installMessageHandler([this](ReplyMsgType type, const std::string msg) { - // use queued connection to recv the log messages from replay. - emit showMessage(QString::fromStdString(msg), 2000); + installDownloadProgressHandler([](uint64_t cur, uint64_t total, bool success) { + emit static_main_win->updateProgressBar(cur, total, success); }); - installDownloadProgressHandler([this](uint64_t cur, uint64_t total, bool success) { - emit updateProgressBar(cur, total, success); + qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &context, const QString &msg) { + if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl; + emit static_main_win->showMessage(msg, 2000); + }); + installMessageHandler([](ReplyMsgType type, const std::string msg) { + qInfo() << QString::fromStdString(msg); }); - main_win = this; - qInstallMessageHandler(qLogMessageHandler); - + // load fingerprints QFile json_file(QApplication::applicationDirPath() + "/dbc/car_fingerprint_to_dbc.json"); if (json_file.open(QIODevice::ReadOnly)) { fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); @@ -597,7 +594,9 @@ void MainWindow::closeEvent(QCloseEvent *event) { cleanupAutoSaveFile(); remindSaveChanges(); - main_win = nullptr; + installDownloadProgressHandler(nullptr); + qInstallMessageHandler(nullptr); + if (floating_window) floating_window->deleteLater(); diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 1a83a0c446..6a94e013f6 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -1,9 +1,6 @@ #include "tools/cabana/messageswidget.h" -#include #include -#include -#include #include #include @@ -13,34 +10,24 @@ #include "tools/cabana/commands.h" -MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { +static QString msg_node_from_id(const MessageId &id) { + auto msg = dbc()->msg(id); + return msg ? msg->transmitter : QString(); +} + +MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - - QHBoxLayout *title_layout = new QHBoxLayout(); - num_msg_label = new QLabel(this); - title_layout->addSpacing(10); - title_layout->addWidget(num_msg_label); - - title_layout->addStretch(); - title_layout->addWidget(multiple_lines_bytes = new QCheckBox(tr("Multiple Lines &Bytes"), this)); - multiple_lines_bytes->setToolTip(tr("Display bytes in multiple lines")); - multiple_lines_bytes->setChecked(settings.multiple_lines_bytes); - QPushButton *clear_filters = new QPushButton(tr("&Clear Filters")); - clear_filters->setEnabled(false); - title_layout->addWidget(clear_filters); - main_layout->addLayout(title_layout); - + main_layout->setSpacing(0); + // toolbar + main_layout->addWidget(createToolBar()); // message table view = new MessageView(this); model = new MessageListModel(this); header = new MessageViewHeader(this); - auto delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes); - - view->setItemDelegate(delegate); + view->setItemDelegate(delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes)); view->setHeader(header); view->setModel(model); - view->setHeader(header); view->setSortingEnabled(true); view->sortByColumn(MessageListModel::Column::NAME, Qt::AscendingOrder); view->setAllColumnsShowFocus(true); @@ -51,13 +38,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { // Must be called before setting any header parameters to avoid overriding restoreHeaderState(settings.message_header_state); - view->header()->setSectionsMovable(true); - view->header()->setSectionResizeMode(MessageListModel::Column::DATA, QHeaderView::Fixed); - view->header()->setStretchLastSection(true); - - // Header context menu - view->header()->setContextMenuPolicy(Qt::CustomContextMenu); - QObject::connect(view->header(), &QHeaderView::customContextMenuRequested, view, &MessageView::headerContextMenuEvent); + header->setSectionsMovable(true); + header->setSectionResizeMode(MessageListModel::Column::DATA, QHeaderView::Fixed); + header->setStretchLastSection(true); + header->setContextMenuPolicy(Qt::CustomContextMenu); main_layout->addWidget(view); @@ -73,20 +57,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { main_layout->addLayout(suppress_layout); // signals/slots + QObject::connect(menu, &QMenu::aboutToShow, this, &MessagesWidget::menuAboutToShow); QObject::connect(header, &MessageViewHeader::filtersUpdated, model, &MessageListModel::setFilterStrings); - QObject::connect(header, &MessageViewHeader::filtersUpdated, [=](const QMap &filters) { - clear_filters->setEnabled(!filters.isEmpty()); - }); + QObject::connect(header, &MessageViewHeader::customContextMenuRequested, this, &MessagesWidget::headerContextMenuEvent); QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions); - QObject::connect(clear_filters, &QPushButton::clicked, header, &MessageViewHeader::clearFilters); - QObject::connect(multiple_lines_bytes, &QCheckBox::stateChanged, [=](int state) { - settings.multiple_lines_bytes = (state == Qt::Checked); - delegate->setMultipleLines(settings.multiple_lines_bytes); - view->setUniformRowHeights(!settings.multiple_lines_bytes); - - // Reset model to force recalculation of the width of the bytes column - model->forceResetModel(); - }); QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, [=](int state) { settings.suppress_defined_signals = (state == Qt::Checked); emit settings.changed(); @@ -130,6 +104,21 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { )")); } +QToolBar *MessagesWidget::createToolBar() { + QToolBar *toolbar = new QToolBar(this); + toolbar->setIconSize({12, 12}); + toolbar->addWidget(num_msg_label = new QLabel(this)); + num_msg_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + auto views_btn = toolbar->addAction(utils::icon("three-dots"), tr("View...")); + views_btn->setMenu(menu); + auto view_button = qobject_cast(toolbar->widgetForAction(views_btn)); + view_button->setPopupMode(QToolButton::InstantPopup); + view_button->setToolButtonStyle(Qt::ToolButtonIconOnly); + view_button->setStyleSheet("QToolButton::menu-indicator { image: none; }"); + return toolbar; +} + void MessagesWidget::dbcModified() { num_msg_label->setText(tr("%1 Messages, %2 Signals").arg(dbc()->msgCount()).arg(dbc()->signalCount())); model->dbcModified(); @@ -152,6 +141,34 @@ void MessagesWidget::updateSuppressedButtons() { } } +void MessagesWidget::headerContextMenuEvent(const QPoint &pos) { + menu->exec(header->mapToGlobal(pos)); +} + +void MessagesWidget::menuAboutToShow() { + menu->clear(); + for (int i = 0; i < header->count(); ++i) { + int logical_index = header->logicalIndex(i); + auto action = menu->addAction(model->headerData(logical_index, Qt::Horizontal).toString(), + [=](bool checked) { header->setSectionHidden(logical_index, !checked); }); + action->setCheckable(true); + action->setChecked(!header->isSectionHidden(logical_index)); + // Can't hide the name column + action->setEnabled(logical_index > 0); + } + menu->addSeparator(); + auto action = menu->addAction(tr("Mutlti-Line bytes"), this, &MessagesWidget::setMultiLineBytes); + action->setCheckable(true); + action->setChecked(settings.multiple_lines_bytes); +} + +void MessagesWidget::setMultiLineBytes(bool multi) { + settings.multiple_lines_bytes = multi; + delegate->setMultipleLines(multi); + view->updateBytesSectionSize(); + view->doItemsLayout(); +} + // MessageListModel QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const { @@ -160,6 +177,7 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, case Column::NAME: return tr("Name"); case Column::SOURCE: return tr("Bus"); case Column::ADDRESS: return tr("ID"); + case Column::NODE: return tr("Node"); case Column::FREQ: return tr("Freq"); case Column::COUNT: return tr("Count"); case Column::DATA: return tr("Bytes"); @@ -171,22 +189,22 @@ QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, QVariant MessageListModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= msgs.size()) return {}; - const auto &id = msgs[index.row()]; - auto &can_data = can->lastMessage(id); - - auto getFreq = [](const CanData &d) -> QString { + auto getFreq = [](const CanData &d) { if (d.freq > 0 && (can->currentSec() - d.ts - 1.0 / settings.fps) < (5.0 / d.freq)) { return d.freq >= 0.95 ? QString::number(std::nearbyint(d.freq)) : QString::number(d.freq, 'f', 2); } else { - return "--"; + return QStringLiteral("--"); } }; + const auto &id = msgs[index.row()]; + auto &can_data = can->lastMessage(id); if (role == Qt::DisplayRole) { switch (index.column()) { case Column::NAME: return msgName(id); case Column::SOURCE: return id.source != INVALID_SOURCE ? QString::number(id.source) : "N/A"; case Column::ADDRESS: return QString::number(id.address, 16); + case Column::NODE: return msg_node_from_id(id); case Column::FREQ: return id.source != INVALID_SOURCE ? getFreq(can_data) : "N/A"; case Column::COUNT: return id.source != INVALID_SOURCE ? QString::number(can_data.count) : "N/A"; case Column::DATA: return id.source != INVALID_SOURCE ? toHex(can_data.dat) : "N/A"; @@ -214,7 +232,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { void MessageListModel::setFilterStrings(const QMap &filters) { filter_str = filters; - fetchData(); + filterAndSort(); } void MessageListModel::dbcModified() { @@ -222,40 +240,22 @@ void MessageListModel::dbcModified() { for (const auto &[_, m] : dbc()->getMessages(-1)) { dbc_address.insert(m.address); } - fetchData(); + filterAndSort(); } void MessageListModel::sortMessages(std::vector &new_msgs) { - if (sort_column == Column::NAME) { - std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) { - auto ll = std::pair{msgName(l), l}; - auto rr = std::pair{msgName(r), r}; - return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; - }); - } else if (sort_column == Column::SOURCE) { - std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) { - auto ll = std::pair{l.source, l}; - auto rr = std::pair{r.source, r}; - return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; - }); - } else if (sort_column == Column::ADDRESS) { - std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) { - auto ll = std::pair{l.address, l}; - auto rr = std::pair{r.address, r}; - return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; - }); - } else if (sort_column == Column::FREQ) { - std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) { - auto ll = std::pair{can->lastMessage(l).freq, l}; - auto rr = std::pair{can->lastMessage(r).freq, r}; - return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; - }); - } else if (sort_column == Column::COUNT) { - std::sort(new_msgs.begin(), new_msgs.end(), [=](auto &l, auto &r) { - auto ll = std::pair{can->lastMessage(l).count, l}; - auto rr = std::pair{can->lastMessage(r).count, r}; - return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; + auto do_sort = [order = sort_order](std::vector &m, auto proj) { + std::sort(m.begin(), m.end(), [order, proj = std::move(proj)](auto &l, auto &r) { + return order == Qt::AscendingOrder ? proj(l) < proj(r) : proj(l) > proj(r); }); + }; + switch (sort_column) { + case Column::NAME: do_sort(new_msgs, [](auto &id) { return std::make_pair(msgName(id), id); }); break; + case Column::SOURCE: do_sort(new_msgs, [](auto &id) { return std::tie(id.source, id); }); break; + case Column::ADDRESS: do_sort(new_msgs, [](auto &id) { return std::tie(id.address, id);}); break; + case Column::NODE: do_sort(new_msgs, [](auto &id) { return std::make_pair(msg_node_from_id(id), id);}); break; + case Column::FREQ: do_sort(new_msgs, [](auto &id) { return std::tie(can->lastMessage(id).freq, id); }); break; + case Column::COUNT: do_sort(new_msgs, [](auto &id) { return std::tie(can->lastMessage(id).count, id); }); break; } } @@ -295,6 +295,9 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co match = match || parseRange(txt, id.address, 16); break; } + case Column::NODE: + match = re.match(msg_node_from_id(id)).hasMatch(); + break; case Column::FREQ: // TODO: Hide stale messages? match = parseRange(txt, data.freq); @@ -313,7 +316,7 @@ bool MessageListModel::matchMessage(const MessageId &id, const CanData &data, co return match; } -void MessageListModel::fetchData() { +void MessageListModel::filterAndSort() { std::vector new_msgs; new_msgs.reserve(can->last_msgs.size() + dbc_address.size()); @@ -344,7 +347,7 @@ void MessageListModel::fetchData() { void MessageListModel::msgsReceived(const QHash *new_msgs, bool has_new_ids) { if (has_new_ids || filter_str.contains(Column::FREQ) || filter_str.contains(Column::COUNT) || filter_str.contains(Column::DATA)) { - fetchData(); + filterAndSort(); } for (int i = 0; i < msgs.size(); ++i) { if (new_msgs->contains(msgs[i])) { @@ -358,7 +361,7 @@ void MessageListModel::sort(int column, Qt::SortOrder order) { if (column != columnCount() - 1) { sort_column = column; sort_order = order; - fetchData(); + filterAndSort(); } } @@ -380,11 +383,6 @@ void MessageListModel::clearSuppress() { suppressed_bytes.clear(); } -void MessageListModel::forceResetModel() { - beginResetModel(); - endResetModel(); -} - // MessageView void MessageView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -425,35 +423,7 @@ void MessageView::updateBytesSectionSize() { } } -void MessageView::headerContextMenuEvent(const QPoint &pos) { - QMenu menu(this); - int cur_index = header()->logicalIndexAt(pos); - - QAction *action; - for (int visual_index = 0; visual_index < header()->count(); visual_index++) { - int logical_index = header()->logicalIndex(visual_index); - QString column_name = model()->headerData(logical_index, Qt::Horizontal).toString(); - - // Hide show action - if (header()->isSectionHidden(logical_index)) { - action = menu.addAction(tr("  %1").arg(column_name), [=]() { header()->showSection(logical_index); }); - } else { - action = menu.addAction(tr("✓ %1").arg(column_name), [=]() { header()->hideSection(logical_index); }); - } - - // Can't hide the name column - action->setEnabled(logical_index > 0); - - // Make current column bold - if (logical_index == cur_index) { - QFont font = action->font(); - font.setBold(true); - action->setFont(font); - } - } - - menu.exec(header()->mapToGlobal(pos)); -} +// MessageViewHeader MessageViewHeader::MessageViewHeader(QWidget *parent) : QHeaderView(Qt::Horizontal, parent) { QObject::connect(this, &QHeaderView::sectionResized, this, &MessageViewHeader::updateHeaderPositions); @@ -463,22 +433,13 @@ MessageViewHeader::MessageViewHeader(QWidget *parent) : QHeaderView(Qt::Horizont void MessageViewHeader::updateFilters() { QMap filters; for (int i = 0; i < count(); i++) { - if (editors[i]) { - QString filter = editors[i]->text(); - if (!filter.isEmpty()) { - filters[i] = filter; - } + if (editors[i] && !editors[i]->text().isEmpty()) { + filters[i] = editors[i]->text(); } } emit filtersUpdated(filters); } -void MessageViewHeader::clearFilters() { - for (QLineEdit *editor : editors) { - editor->clear(); - } -} - void MessageViewHeader::updateHeaderPositions() { QSize sz = QHeaderView::sizeHint(); for (int i = 0; i < count(); i++) { @@ -508,11 +469,7 @@ void MessageViewHeader::updateGeometries() { updateHeaderPositions(); } - QSize MessageViewHeader::sizeHint() const { QSize sz = QHeaderView::sizeHint(); - if (editors[0]) { - sz.setHeight(sz.height() + editors[0]->minimumSizeHint().height() + 1); - } - return sz; + return editors[0] ? QSize(sz.width(), sz.height() + editors[0]->height() + 1) : sz; } diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index f6d71a5a2e..11d95dd3f2 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -5,13 +5,12 @@ #include #include -#include -#include #include #include #include #include #include +#include #include #include "tools/cabana/dbc/dbcmanager.h" @@ -21,11 +20,11 @@ class MessageListModel : public QAbstractTableModel { Q_OBJECT public: - enum Column { NAME = 0, SOURCE, ADDRESS, + NODE, FREQ, COUNT, DATA, @@ -39,10 +38,9 @@ public: void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; void setFilterStrings(const QMap &filters); void msgsReceived(const QHash *new_msgs, bool has_new_ids); - void fetchData(); + void filterAndSort(); void suppress(); void clearSuppress(); - void forceResetModel(); void dbcModified(); std::vector msgs; QSet> suppressed_bytes; @@ -65,23 +63,17 @@ public: void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override {} void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override; void updateBytesSectionSize(); - void headerContextMenuEvent(const QPoint &pos); }; class MessageViewHeader : public QHeaderView { // https://stackoverflow.com/a/44346317 - Q_OBJECT public: MessageViewHeader(QWidget *parent); void updateHeaderPositions(); - void updateGeometries() override; QSize sizeHint() const override; -public slots: - void clearFilters(); - signals: void filtersUpdated(const QMap &filters); @@ -108,12 +100,18 @@ signals: void msgSelectionChanged(const MessageId &message_id); protected: + QToolBar *createToolBar(); + void headerContextMenuEvent(const QPoint &pos); + void menuAboutToShow(); + void setMultiLineBytes(bool multi); + MessageView *view; MessageViewHeader *header; + MessageBytesDelegate *delegate; std::optional current_msg_id; - QCheckBox *multiple_lines_bytes; MessageListModel *model; QPushButton *suppress_add; QPushButton *suppress_clear; QLabel *num_msg_label; + QMenu *menu; }; diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index ee345c490c..c408179fdd 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -14,6 +14,7 @@ Settings settings; QSettings::Status Settings::save() { QSettings s(filePath(), QSettings::IniFormat); + s.setValue("absolute_time", absolute_time); s.setValue("fps", fps); s.setValue("max_cached_minutes", max_cached_minutes); s.setValue("chart_height", chart_height); @@ -40,6 +41,7 @@ QSettings::Status Settings::save() { void Settings::load() { QSettings s(filePath(), QSettings::IniFormat); + absolute_time = s.value("absolute_time", false).toBool(); fps = s.value("fps", 10).toInt(); max_cached_minutes = s.value("max_cached_minutes", 30).toInt(); chart_height = s.value("chart_height", 200).toInt(); diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index 42073a72de..da7781e6e4 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -29,6 +29,7 @@ public: void load(); inline static QString filePath() { return QApplication::applicationDirPath() + "/settings"; } + bool absolute_time = false; int fps = 10; int max_cached_minutes = 30; int chart_height = 200; diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index 4a07e25beb..934609a439 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -447,7 +447,7 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QRegularExpression re("\\S+"); filter_edit->setValidator(new QRegularExpressionValidator(re, this)); filter_edit->setClearButtonEnabled(true); - filter_edit->setPlaceholderText(tr("filter signals")); + filter_edit->setPlaceholderText(tr("Filter Signal")); hl->addWidget(filter_edit); hl->addStretch(1); @@ -502,6 +502,12 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QObject::connect(tree->verticalScrollBar(), &QScrollBar::valueChanged, [this]() { updateState(); }); QObject::connect(tree->verticalScrollBar(), &QScrollBar::rangeChanged, [this]() { updateState(); }); QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalView::updateState); + QObject::connect(tree->header(), &QHeaderView::sectionResized, [this](int logicalIndex, int oldSize, int newSize) { + if (logicalIndex == 1) { + value_column_width = newSize - delegate->button_size.width(); + updateState(); + } + }); setWhatsThis(tr(R"( Signal view
@@ -638,11 +644,10 @@ void SignalView::updateState(const QHash *msgs) { last_visible_row = bottom.parent().isValid() ? bottom.parent().row() : bottom.row(); } - QSize size(tree->columnWidth(1) - delegate->button_size.width(), delegate->button_size.height()); - int min_max_width = std::min(size.width() - 10, QFontMetrics(delegate->minmax_font).width("-000.00") + 5); - int value_width = std::min(max_value_width, size.width() * 0.35); - size -= {value_width + min_max_width, style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2}; - + const static int min_max_width = QFontMetrics(delegate->minmax_font).width("-000.00") + 5; + int value_width = std::min(max_value_width + min_max_width, value_column_width / 2); + QSize size(value_column_width - value_width, + delegate->button_size.height() - style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2); QFutureSynchronizer synchronizer; for (int i = first_visible_row; i <= last_visible_row; ++i) { auto item = model->getItem(model->index(i, 1)); diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h index bcf0019bc4..c08579bc55 100644 --- a/tools/cabana/signalview.h +++ b/tools/cabana/signalview.h @@ -136,6 +136,7 @@ private: } }; int max_value_width = 0; + int value_column_width = 0; TreeView *tree; QLabel *sparkline_label; QSlider *sparkline_range_slider; diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index 6fa479815d..eb2859e5a3 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -1,7 +1,6 @@ #include "tools/cabana/streams/abstractstream.h" #include -#include #include @@ -139,9 +138,10 @@ void AbstractStream::updateLastMsgsTo(double sec) { } void AbstractStream::mergeEvents(std::vector::const_iterator first, std::vector::const_iterator last) { - static std::unordered_map> new_events_map; + static MessageEventsMap msg_events; static std::vector new_events; - new_events_map.clear(); + + std::for_each(msg_events.begin(), msg_events.end(), [](auto &e) { e.second.clear(); }); new_events.clear(); for (auto it = first; it != last; ++it) { @@ -156,25 +156,25 @@ void AbstractStream::mergeEvents(std::vector::const_iterator first, std e->size = dat.size(); memcpy(e->dat, (uint8_t *)dat.begin(), e->size); - new_events_map[{.source = e->src, .address = e->address}].push_back(e); + msg_events[{.source = e->src, .address = e->address}].push_back(e); new_events.push_back(e); } } } - for (auto &[id, new_e] : new_events_map) { - auto &e = events_[id]; - auto insert_pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent()); - e.insert(insert_pos, new_e.cbegin(), new_e.cend()); - } - if (!new_events.empty()) { - auto insert_pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), new_events.front()->mono_time, CompareCanEvent()); - all_events_.insert(insert_pos, new_events.cbegin(), new_events.cend()); + for (auto &[id, new_e] : msg_events) { + if (!new_e.empty()) { + auto &e = events_[id]; + auto pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent()); + e.insert(pos, new_e.cbegin(), new_e.cend()); + } + } + auto pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), new_events.front()->mono_time, CompareCanEvent()); + all_events_.insert(pos, new_events.cbegin(), new_events.cend()); + emit eventsMerged(msg_events); } - lastest_event_ts = all_events_.empty() ? 0 : all_events_.back()->mono_time; - emit eventsMerged(); } // CanData diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 3a89d4e57d..4a17affebe 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -2,13 +2,13 @@ #include #include -#include #include #include #include #include #include +#include #include #include "common/timing.h" @@ -52,6 +52,8 @@ struct BusConfig { bool can_fd = false; }; +typedef std::unordered_map> MessageEventsMap; + class AbstractStream : public QObject { Q_OBJECT @@ -63,6 +65,7 @@ public: virtual void seekTo(double ts) {} virtual QString routeName() const = 0; virtual QString carFingerprint() const { return ""; } + virtual QDateTime beginDateTime() const { return {}; } virtual double routeStartTime() const { return 0; } virtual double currentSec() const = 0; virtual double totalSeconds() const { return lastEventMonoTime() / 1e9 - routeStartTime(); } @@ -73,6 +76,7 @@ public: virtual double getSpeed() { return 1; } virtual bool isPaused() const { return false; } virtual void pause(bool pause) {} + const MessageEventsMap &eventsMap() const { return events_; } const std::vector &allEvents() const { return all_events_; } const std::vector &events(const MessageId &id) const; virtual const std::vector> getTimeline() { return {}; } @@ -82,7 +86,7 @@ signals: void resume(); void seekedTo(double sec); void streamStarted(); - void eventsMerged(); + void eventsMerged(const MessageEventsMap &events_map); void updated(); void msgsReceived(const QHash *new_msgs, bool has_new_ids); void sourcesUpdated(const SourceSet &s); @@ -104,7 +108,7 @@ protected: std::atomic processing = false; std::unique_ptr> new_msgs; QHash all_msgs; - std::unordered_map> events_; + MessageEventsMap events_; std::vector all_events_; std::unique_ptr event_buffer; std::mutex mutex; diff --git a/tools/cabana/streams/livestream.cc b/tools/cabana/streams/livestream.cc index 17805a0b66..fd140eb8bf 100644 --- a/tools/cabana/streams/livestream.cc +++ b/tools/cabana/streams/livestream.cc @@ -46,6 +46,7 @@ void LiveStream::start() { emit streamStarted(); stream_thread->start(); startUpdateTimer(); + begin_date_time = QDateTime::currentDateTime(); } LiveStream::~LiveStream() { diff --git a/tools/cabana/streams/livestream.h b/tools/cabana/streams/livestream.h index 38ef2c67f9..d72112c604 100644 --- a/tools/cabana/streams/livestream.h +++ b/tools/cabana/streams/livestream.h @@ -15,6 +15,7 @@ public: LiveStream(QObject *parent); virtual ~LiveStream(); void start() override; + inline QDateTime beginDateTime() const { return begin_date_time; } inline double routeStartTime() const override { return begin_event_ts / 1e9; } inline double currentSec() const override { return (current_event_ts - begin_event_ts) / 1e9; } void setSpeed(float speed) override { speed_ = speed; } @@ -49,6 +50,7 @@ private: int timer_id; QBasicTimer update_timer; + QDateTime begin_date_time; uint64_t begin_event_ts = 0; uint64_t current_event_ts = 0; uint64_t first_event_ts = 0; diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index 2e580c0f0d..8a6b806a8a 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -8,10 +8,13 @@ ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) { unsetenv("ZMQ"); + setenv("COMMA_CACHE", "/tmp/comma_download_cache", 1); + // TODO: Remove when OpenpilotPrefix supports ZMQ #ifndef __APPLE__ op_prefix = std::make_unique(); #endif + QObject::connect(&settings, &Settings::changed, [this]() { if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes); }); @@ -37,6 +40,7 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint replay->installEventFilter(event_filter, this); QObject::connect(replay.get(), &Replay::seekedTo, this, &AbstractStream::seekedTo); QObject::connect(replay.get(), &Replay::segmentsMerged, this, &ReplayStream::mergeSegments); + QObject::connect(replay.get(), &Replay::qLogLoaded, this, &ReplayStream::qLogLoaded, Qt::QueuedConnection); return replay->load(); } diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index de69d9e86c..d69922a432 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -22,16 +22,21 @@ public: inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); } double totalSeconds() const override { return replay->totalSeconds(); } inline VisionStreamType visionStreamType() const override { return replay->hasFlag(REPLAY_FLAG_ECAM) ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD; } + inline QDateTime beginDateTime() const { return replay->route()->datetime(); } inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; } inline double currentSec() const override { return replay->currentSeconds(); } inline const Route *route() const override { return replay->route(); } inline void setSpeed(float speed) override { replay->setSpeed(speed); } inline float getSpeed() const { return replay->getSpeed(); } + inline Replay *getReplay() const { return replay.get(); } inline bool isPaused() const override { return replay->isPaused(); } void pause(bool pause) override; inline const std::vector> getTimeline() override { return replay->getTimeline(); } static AbstractOpenStreamWidget *widget(AbstractStream **stream); +signals: + void qLogLoaded(int segnum, std::shared_ptr qlog); + private: void mergeSegments(); std::unique_ptr replay = nullptr; diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index 4c21530774..38190da301 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -19,7 +19,7 @@ // SegmentTree -void SegmentTree::build(const QVector &arr) { +void SegmentTree::build(const std::vector &arr) { size = arr.size(); tree.resize(4 * size); // size of the tree is 4 times the size of the array if (size > 0) { @@ -27,7 +27,7 @@ void SegmentTree::build(const QVector &arr) { } } -void SegmentTree::build_tree(const QVector &arr, int n, int left, int right) { +void SegmentTree::build_tree(const std::vector &arr, int n, int left, int right) { if (left == right) { const double y = arr[left].y(); tree[n] = {y, y}; @@ -242,6 +242,13 @@ void setTheme(int theme) { } } +QString formatSeconds(double sec, bool include_milliseconds, bool absolute_time) { + QString format = absolute_time ? "yyyy-MM-dd hh:mm:ss" + : (sec > 60 * 60 ? "hh:mm:ss" : "mm:ss"); + if (include_milliseconds) format += ".zzz"; + return QDateTime::fromMSecsSinceEpoch(sec * 1000).toString(format); +} + } // namespace utils QString toHex(uint8_t byte) { diff --git a/tools/cabana/util.h b/tools/cabana/util.h index 2c1bc5cf7b..da476ab31a 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -57,12 +57,12 @@ enum { class SegmentTree { public: SegmentTree() = default; - void build(const QVector &arr); + void build(const std::vector &arr); inline std::pair minmax(int left, int right) const { return get_minmax(1, 0, size - 1, left, right); } private: std::pair get_minmax(int n, int left, int right, int range_left, int range_right) const; - void build_tree(const QVector &arr, int n, int left, int right); + void build_tree(const std::vector &arr, int n, int left, int right); std::vector> tree; int size = 0; }; @@ -103,9 +103,7 @@ public: namespace utils { QPixmap icon(const QString &id); void setTheme(int theme); -inline QString formatSeconds(int seconds) { - return QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); -} +QString formatSeconds(double sec, bool include_milliseconds = false, bool absolute_time = false); inline void drawStaticText(QPainter *p, const QRect &r, const QStaticText &text) { auto size = (r.size() - text.size()) / 2; p->drawStaticText(r.left() + size.width(), r.top() + size.height(), text); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 7e051ef3b9..0a1d2855a5 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -1,17 +1,15 @@ #include "tools/cabana/videowidget.h" #include -#include -#include #include -#include -#include +#include +#include +#include #include #include #include #include -#include #include #include @@ -30,50 +28,18 @@ static const QColor timeline_colors[] = { VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { setFrameStyle(QFrame::StyledPanel | QFrame::Plain); auto main_layout = new QVBoxLayout(this); - if (!can->liveStreaming()) { + if (!can->liveStreaming()) main_layout->addWidget(createCameraWidget()); - } + main_layout->addLayout(createPlaybackController()); - // btn controls - QButtonGroup *group = new QButtonGroup(this); - group->setExclusive(true); - - QHBoxLayout *control_layout = new QHBoxLayout(); - play_btn = new QToolButton(); - play_btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - control_layout->addWidget(play_btn); - if (can->liveStreaming()) { - control_layout->addWidget(skip_to_end_btn = new QToolButton(this)); - skip_to_end_btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - skip_to_end_btn->setIcon(utils::icon("skip-end-fill")); - skip_to_end_btn->setToolTip(tr("Skip to the end")); - QObject::connect(skip_to_end_btn, &QToolButton::clicked, [group]() { - // set speed to 1.0 - group->buttons()[2]->click(); - can->pause(false); - can->seekTo(can->totalSeconds() + 1); - }); - } - - for (float speed : {0.1, 0.5, 1., 2.}) { - QToolButton *btn = new QToolButton(this); - btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - btn->setText(QString("%1x").arg(speed)); - btn->setCheckable(true); - QObject::connect(btn, &QToolButton::clicked, [speed]() { can->setSpeed(speed); }); - control_layout->addWidget(btn); - group->addButton(btn); - if (speed == 1.0) btn->setChecked(true); - } - main_layout->addLayout(control_layout); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - QObject::connect(play_btn, &QToolButton::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState); QObject::connect(can, &AbstractStream::resume, this, &VideoWidget::updatePlayBtnState); + QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState); QObject::connect(&settings, &Settings::changed, this, &VideoWidget::updatePlayBtnState); - updatePlayBtnState(); + updatePlayBtnState(); setWhatsThis(tr(R"( Video
@@ -96,6 +62,71 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { timeline_colors[(int)TimelineType::AlertCritical].name())); } +QHBoxLayout *VideoWidget::createPlaybackController() { + QHBoxLayout *layout = new QHBoxLayout(); + layout->addWidget(seek_backward_btn = new ToolButton("rewind", tr("Seek backward"))); + layout->addWidget(play_btn = new ToolButton("play", tr("Play"))); + layout->addWidget(seek_forward_btn = new ToolButton("fast-forward", tr("Seek forward"))); + + if (can->liveStreaming()) { + layout->addWidget(skip_to_end_btn = new ToolButton("skip-end", tr("Skip to the end"), this)); + QObject::connect(skip_to_end_btn, &QToolButton::clicked, [this]() { + // set speed to 1.0 + speed_btn->menu()->actions()[7]->setChecked(true); + can->pause(false); + can->seekTo(can->totalSeconds() + 1); + }); + } + + layout->addWidget(time_btn = new QToolButton); + time_btn->setToolTip(settings.absolute_time ? tr("Elapsed time") : tr("Absolute time")); + time_btn->setAutoRaise(true); + layout->addStretch(0); + + if (!can->liveStreaming()) { + layout->addWidget(loop_btn = new ToolButton("repeat", tr("Loop playback"))); + QObject::connect(loop_btn, &QToolButton::clicked, this, &VideoWidget::loopPlaybackClicked); + } + + // speed selector + layout->addWidget(speed_btn = new QToolButton(this)); + speed_btn->setAutoRaise(true); + speed_btn->setMenu(new QMenu(speed_btn)); + speed_btn->setPopupMode(QToolButton::InstantPopup); + QActionGroup *speed_group = new QActionGroup(this); + speed_group->setExclusive(true); + + int max_width = 0; + QFont font = speed_btn->font(); + font.setBold(true); + speed_btn->setFont(font); + QFontMetrics fm(font); + for (float speed : {0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 0.8, 1., 2., 3., 5.}) { + QString name = QString("%1x").arg(speed); + max_width = std::max(max_width, fm.width(name) + fm.horizontalAdvance(QLatin1Char(' ')) * 2); + + QAction *act = new QAction(name, speed_group); + act->setCheckable(true); + QObject::connect(act, &QAction::toggled, [this, speed]() { + can->setSpeed(speed); + speed_btn->setText(QString("%1x ").arg(speed)); + }); + speed_btn->menu()->addAction(act); + if (speed == 1.0)act->setChecked(true); + } + speed_btn->setMinimumWidth(max_width + style()->pixelMetric(QStyle::PM_MenuButtonIndicator)); + + QObject::connect(play_btn, &QToolButton::clicked, []() { can->pause(!can->isPaused()); }); + QObject::connect(seek_backward_btn, &QToolButton::clicked, []() { can->seekTo(can->currentSec() - 1); }); + QObject::connect(seek_forward_btn, &QToolButton::clicked, []() { can->seekTo(can->currentSec() + 1); }); + QObject::connect(time_btn, &QToolButton::clicked, [this]() { + settings.absolute_time = !settings.absolute_time; + time_btn->setToolTip(settings.absolute_time ? tr("Elapsed time") : tr("Absolute time")); + updateState(); + }); + return layout; +} + QWidget *VideoWidget::createCameraWidget() { QWidget *w = new QWidget(this); QVBoxLayout *l = new QVBoxLayout(w); @@ -109,29 +140,30 @@ QWidget *VideoWidget::createCameraWidget() { stacked->addWidget(alert_label = new InfoLabel(this)); l->addLayout(stacked); - // slider controls - auto slider_layout = new QHBoxLayout(); - slider_layout->addWidget(time_label = new QLabel("00:00")); - - slider = new Slider(this); + l->addWidget(slider = new Slider(this)); slider->setSingleStep(0); - slider_layout->addWidget(slider); - - slider_layout->addWidget(end_time_label = new QLabel(this)); - l->addLayout(slider_layout); setMaximumTime(can->totalSeconds()); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->currentSecond()); }); - QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(slider->currentSecond())); }); QObject::connect(slider, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime, Qt::QueuedConnection); QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); - QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState); + QObject::connect(static_cast(can), &ReplayStream::qLogLoaded, slider, &Slider::parseQLog); return w; } +void VideoWidget::loopPlaybackClicked() { + auto replay = qobject_cast(can)->getReplay(); + if (replay->hasFlag(REPLAY_FLAG_NO_LOOP)) { + replay->removeFlag(REPLAY_FLAG_NO_LOOP); + loop_btn->setIcon("repeat"); + } else { + replay->addFlag(REPLAY_FLAG_NO_LOOP); + loop_btn->setIcon("repeat-1"); + } +} + void VideoWidget::setMaximumTime(double sec) { maximum_time = sec; - end_time_label->setText(utils::formatSeconds(sec)); slider->setTimeRange(0, sec); } @@ -145,19 +177,29 @@ void VideoWidget::updateTimeRange(double min, double max, bool is_zoomed) { min = 0; max = maximum_time; } - end_time_label->setText(utils::formatSeconds(max)); slider->setTimeRange(min, max); } +QString VideoWidget::formatTime(double sec, bool include_milliseconds) { + if (settings.absolute_time) + sec = can->beginDateTime().addMSecs(sec * 1000).toMSecsSinceEpoch() / 1000.0; + return utils::formatSeconds(sec, include_milliseconds, settings.absolute_time); +} + void VideoWidget::updateState() { - if (!slider->isSliderDown()) { - slider->setCurrentSecond(can->currentSec()); + if (slider) { + if (!slider->isSliderDown()) + slider->setCurrentSecond(can->currentSec()); + alert_label->showAlert(slider->alertInfo(can->currentSec())); + time_btn->setText(QString("%1 / %2").arg(formatTime(can->currentSec(), true), + formatTime(slider->maximum() / slider->factor))); + } else { + time_btn->setText(formatTime(can->currentSec(), true)); } - alert_label->showAlert(slider->alertInfo(can->currentSec())); } void VideoWidget::updatePlayBtnState() { - play_btn->setIcon(utils::icon(can->isPaused() ? "play" : "pause")); + play_btn->setIcon(can->isPaused() ? "play" : "pause"); play_btn->setToolTip(can->isPaused() ? tr("Play") : tr("Pause")); } @@ -165,29 +207,9 @@ void VideoWidget::updatePlayBtnState() { Slider::Slider(QWidget *parent) : thumbnail_label(parent), QSlider(Qt::Horizontal, parent) { setMouseTracking(true); - auto timer = new QTimer(this); - timer->callOnTimeout([this]() { - timeline = can->getTimeline(); - std::sort(timeline.begin(), timeline.end(), [](auto &l, auto &r) { return std::get<2>(l) < std::get<2>(r); }); - update(); - }); - timer->start(2000); - QObject::connect(can, &AbstractStream::eventsMerged, [this]() { - if (!qlog_future) { - qlog_future = std::make_unique>(QtConcurrent::run(this, &Slider::parseQLog)); - } - }); - QObject::connect(qApp, &QApplication::aboutToQuit, [this]() { - abort_parse_qlog = true; - if (qlog_future && qlog_future->isRunning()) { - qDebug() << "stopping thumbnail thread"; - qlog_future->waitForFinished(); - } - }); } AlertInfo Slider::alertInfo(double seconds) { - std::lock_guard lk(thumbnail_lock); uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9; auto alert_it = alerts.lower_bound(mono_time); bool has_alert = (alert_it != alerts.end()) && ((alert_it->first - mono_time) <= 1e8); @@ -195,7 +217,6 @@ AlertInfo Slider::alertInfo(double seconds) { } QPixmap Slider::thumbnail(double seconds) { - std::lock_guard lk(thumbnail_lock); uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9; auto it = thumbnails.lowerBound(mono_time); return it != thumbnails.end() ? it.value() : QPixmap(); @@ -206,36 +227,32 @@ void Slider::setTimeRange(double min, double max) { setRange(min * factor, max * factor); } -void Slider::parseQLog() { - const auto &segments = can->route()->segments(); - for (auto it = segments.rbegin(); it != segments.rend() && !abort_parse_qlog; ++it) { - LogReader log; - std::string qlog = it->second.qlog.toStdString(); - if (!qlog.empty() && log.load(qlog, &abort_parse_qlog, {cereal::Event::Which::THUMBNAIL, cereal::Event::Which::CONTROLS_STATE}, true, 0, 3)) { - if (it == segments.rbegin() && !log.events.empty()) { - double max_time = log.events.back()->mono_time / 1e9 - can->routeStartTime(); - emit updateMaximumTime(max_time); +void Slider::parseQLog(int segnum, std::shared_ptr qlog) { + const auto &segments = qobject_cast(can)->route()->segments(); + if (segments.size() > 0 && segnum == segments.rbegin()->first && !qlog->events.empty()) { + emit updateMaximumTime(qlog->events.back()->mono_time / 1e9 - can->routeStartTime()); + } + + std::mutex mutex; + QtConcurrent::blockingMap(qlog->events.cbegin(), qlog->events.cend(), [&mutex, this](const Event *e) { + if (e->which == cereal::Event::Which::THUMBNAIL) { + auto thumb = e->event.getThumbnail(); + auto data = thumb.getThumbnail(); + if (QPixmap pm; pm.loadFromData(data.begin(), data.size(), "jpeg")) { + QPixmap scaled = pm.scaledToHeight(MIN_VIDEO_HEIGHT - THUMBNAIL_MARGIN * 2, Qt::SmoothTransformation); + std::lock_guard lk(mutex); + thumbnails[thumb.getTimestampEof()] = scaled; } - for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_parse_qlog; ++ev) { - if ((*ev)->which == cereal::Event::Which::THUMBNAIL) { - auto thumb = (*ev)->event.getThumbnail(); - auto data = thumb.getThumbnail(); - if (QPixmap pm; pm.loadFromData(data.begin(), data.size(), "jpeg")) { - pm = pm.scaledToHeight(MIN_VIDEO_HEIGHT - THUMBNAIL_MARGIN * 2, Qt::SmoothTransformation); - std::lock_guard lk(thumbnail_lock); - thumbnails[thumb.getTimestampEof()] = pm; - } - } else if ((*ev)->which == cereal::Event::Which::CONTROLS_STATE) { - auto cs = (*ev)->event.getControlsState(); - if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0 && - cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE) { - std::lock_guard lk(thumbnail_lock); - alerts.emplace((*ev)->mono_time, AlertInfo{cs.getAlertStatus(), cs.getAlertText1().cStr(), cs.getAlertText2().cStr()}); - } - } + } else if (e->which == cereal::Event::Which::CONTROLS_STATE) { + auto cs = e->event.getControlsState(); + if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0 && + cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE) { + std::lock_guard lk(mutex); + alerts.emplace(e->mono_time, AlertInfo{cs.getAlertStatus(), cs.getAlertText1().cStr(), cs.getAlertText2().cStr()}); } } - } + }); + update(); } void Slider::paintEvent(QPaintEvent *ev) { @@ -245,7 +262,7 @@ void Slider::paintEvent(QPaintEvent *ev) { double min = minimum() / factor; double max = maximum() / factor; - for (auto [begin, end, type] : timeline) { + for (auto [begin, end, type] : qobject_cast(can)->getTimeline()) { if (begin > max || end < min) continue; r.setLeft(((std::max(min, begin) - min) / (max - min)) * width()); @@ -311,8 +328,7 @@ void InfoLabel::showPixmap(const QPoint &pt, const QString &sec, const QPixmap & second = sec; pixmap = pm; alert_info = alert; - resize(pm.size()); - move(pt); + setGeometry(QRect(pt, pm.size())); setVisible(true); update(); } diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index aca44d0ac1..68e0461117 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,19 +1,14 @@ #pragma once -#include #include #include -#include -#include -#include -#include -#include +#include #include #include #include "selfdrive/ui/qt/widgets/cameraview.h" -#include "tools/cabana/streams/abstractstream.h" +#include "tools/cabana/streams/replaystream.h" struct AlertInfo { cereal::ControlsState::AlertStatus status; @@ -42,6 +37,9 @@ public: void setTimeRange(double min, double max); AlertInfo alertInfo(double sec); QPixmap thumbnail(double sec); + void parseQLog(int segnum, std::shared_ptr qlog); + + const double factor = 1000.0; signals: void updateMaximumTime(double); @@ -51,15 +49,9 @@ private: void mouseMoveEvent(QMouseEvent *e) override; bool event(QEvent *event) override; void paintEvent(QPaintEvent *ev) override; - void parseQLog(); - const double factor = 1000.0; - std::vector> timeline; - std::mutex thumbnail_lock; - std::atomic abort_parse_qlog = false; QMap thumbnails; std::map alerts; - std::unique_ptr> qlog_future; InfoLabel thumbnail_label; }; @@ -72,16 +64,22 @@ public: void setMaximumTime(double sec); protected: + QString formatTime(double sec, bool include_milliseconds = false); void updateState(); void updatePlayBtnState(); QWidget *createCameraWidget(); + QHBoxLayout *createPlaybackController(); + void loopPlaybackClicked(); CameraWidget *cam_widget; double maximum_time = 0; - QLabel *end_time_label; - QLabel *time_label; - QToolButton *play_btn; - QToolButton *skip_to_end_btn = nullptr; - InfoLabel *alert_label; - Slider *slider; + QToolButton *time_btn = nullptr; + ToolButton *seek_backward_btn = nullptr; + ToolButton *play_btn = nullptr; + ToolButton *seek_forward_btn = nullptr; + ToolButton *loop_btn = nullptr; + QToolButton *speed_btn = nullptr; + ToolButton *skip_to_end_btn = nullptr; + InfoLabel *alert_label = nullptr; + Slider *slider = nullptr; }; diff --git a/tools/gpstest/fuzzy_testing.py b/tools/gpstest/fuzzy_testing.py index 3bad2770cd..532fd2d34c 100755 --- a/tools/gpstest/fuzzy_testing.py +++ b/tools/gpstest/fuzzy_testing.py @@ -33,8 +33,7 @@ def run_remote_checker(lat, lon, alt, duration, ip_addr): return False, None, None matched, log, info = con.root.exposed_run_checker(lat, lon, alt, - timeout=duration, - use_laikad=True) + timeout=duration) con.close() # TODO: might wanna fetch more logs here con = None @@ -43,7 +42,7 @@ def run_remote_checker(lat, lon, alt, duration, ip_addr): stats = defaultdict(int) # type: ignore -keys = ['success', 'failed', 'ublox_fail', 'laikad_fail', 'proc_crash', 'checker_crash'] +keys = ['success', 'failed', 'ublox_fail', 'proc_crash', 'checker_crash'] def print_report(): print("\nFuzzy testing report summary:") @@ -62,10 +61,7 @@ def update_stats(matched, log, info): if log == "CHECKER CRASHED": stats['checker_crash'] += 1 if log == "TIMEOUT": - if "LAIKAD" in info: - stats['laikad_fail'] += 1 - else: # "UBLOX" in info - stats['ublox_fail'] += 1 + stats['ublox_fail'] += 1 def main(ip_addr, continuous_mode, timeout, pos): diff --git a/tools/gpstest/rpc_server.py b/tools/gpstest/rpc_server.py deleted file mode 100644 index 798237142d..0000000000 --- a/tools/gpstest/rpc_server.py +++ /dev/null @@ -1,185 +0,0 @@ -import os -import time -import shutil -from datetime import datetime -from collections import defaultdict -from openpilot.system.hardware.hw import Paths - -import rpyc -from rpyc.utils.server import ThreadedServer - -#from openpilot.common.params import Params -import cereal.messaging as messaging -from openpilot.selfdrive.manager.process_config import managed_processes -from laika.lib.coordinates import ecef2geodetic - -DELTA = 0.001 -ALT_DELTA = 30 -MATCH_NUM = 10 -REPORT_STATS = 10 - -EPHEM_CACHE = "/data/params/d/LaikadEphemerisV3" - -SERVER_LOG_FILE = "/tmp/fuzzy_server.log" -server_log = open(SERVER_LOG_FILE, "w+") - -def slog(msg): - server_log.write(f"{datetime.now().strftime('%H:%M:%S.%f')} | {msg}\n") - server_log.flush() - -def handle_laikad(msg): - if not hasattr(msg, 'correctedMeasurements'): - return None - - num_corr = len(msg.correctedMeasurements) - pos_ecef = msg.positionECEF.value - pos_geo = [] - if len(pos_ecef) > 0: - pos_geo = ecef2geodetic(pos_ecef) - - pos_std = msg.positionECEF.std - pos_valid = msg.positionECEF.valid - - slog(f"{num_corr} {pos_geo} {pos_ecef} {pos_std} {pos_valid}") - return pos_geo, (num_corr, pos_geo, list(pos_ecef), list(msg.positionECEF.std)) - -hw_msgs = 0 -ephem_msgs: dict = defaultdict(int) -def handle_ublox(msg): - global hw_msgs - - d = msg.to_dict() - - if 'hwStatus2' in d: - hw_msgs += 1 - - if 'ephemeris' in d: - ephem_msgs[msg.ephemeris.svId] += 1 - - num_meas = None - if 'measurementReport' in d: - num_meas = msg.measurementReport.numMeas - - return [hw_msgs, ephem_msgs, num_meas] - - -def start_procs(procs): - for p in procs: - managed_processes[p].start() - time.sleep(1) - -def kill_procs(procs, no_retry=False): - for p in procs: - managed_processes[p].stop() - time.sleep(1) - - if not no_retry: - for p in procs: - mp = managed_processes[p].proc - if mp is not None and mp.is_alive(): - managed_processes[p].stop() - time.sleep(3) - -def check_alive_procs(procs): - for p in procs: - mp = managed_processes[p].proc - if mp is None or not mp.is_alive(): - return False, p - return True, None - - -class RemoteCheckerService(rpyc.Service): - def on_connect(self, conn): - pass - - def on_disconnect(self, conn): - #kill_procs(self.procs, no_retry=False) - # this execution is delayed, it will kill the next run of laikad - # TODO: add polling to wait for everything is killed - pass - - def run_checker(self, slat, slon, salt, sockets, procs, timeout): - global hw_msgs, ephem_msgs - hw_msgs = 0 - ephem_msgs = defaultdict(int) - - slog(f"Run test: {slat} {slon} {salt}") - - # quectel_mod = Params().get_bool("UbloxAvailable") - - match_cnt = 0 - msg_cnt = 0 - stats_laikad = [] - stats_ublox = [] - - self.procs = procs - start_procs(procs) - sm = messaging.SubMaster(sockets) - - start_time = time.monotonic() - while True: - sm.update() - - if sm.updated['ubloxGnss']: - stats_ublox.append(handle_ublox(sm['ubloxGnss'])) - - if sm.updated['gnssMeasurements']: - pos_geo, stats = handle_laikad(sm['gnssMeasurements']) - if pos_geo is None or len(pos_geo) == 0: - continue - - match = all(abs(g-s) < DELTA for g,s in zip(pos_geo[:2], [slat, slon], strict=True)) - match &= abs(pos_geo[2] - salt) < ALT_DELTA - if match: - match_cnt += 1 - if match_cnt >= MATCH_NUM: - return True, "MATCH", f"After: {round(time.monotonic() - start_time, 4)}" - - # keep some stats for error reporting - stats_laikad.append(stats) - - if (msg_cnt % 10) == 0: - a, p = check_alive_procs(procs) - if not a: - return False, "PROC CRASH", f"{p}" - msg_cnt += 1 - - if (time.monotonic() - start_time) > timeout: - h = f"LAIKAD: {stats_laikad[-REPORT_STATS:]}" - if len(h) == 0: - h = f"UBLOX: {stats_ublox[-REPORT_STATS:]}" - return False, "TIMEOUT", h - - - def exposed_run_checker(self, slat, slon, salt, timeout=180, use_laikad=True): - try: - procs = [] - sockets = [] - - if use_laikad: - procs.append("laikad") # pigeond, ubloxd # might wanna keep them running - sockets += ['ubloxGnss', 'gnssMeasurements'] - - if os.path.exists(EPHEM_CACHE): - os.remove(EPHEM_CACHE) - shutil.rmtree(Paths.download_cache_root(), ignore_errors=True) - - ret = self.run_checker(slat, slon, salt, sockets, procs, timeout) - kill_procs(procs) - return ret - - except Exception as e: - # always make sure processes get killed - kill_procs(procs) - return False, "CHECKER CRASHED", f"{str(e)}" - - - def exposed_kill_procs(self): - kill_procs(self.procs, no_retry=True) - - -if __name__ == "__main__": - print(f"Sever Log written to: {SERVER_LOG_FILE}") - t = ThreadedServer(RemoteCheckerService, port=18861) - t.start() - diff --git a/tools/gpstest/test_laikad.py b/tools/gpstest/test_laikad.py deleted file mode 100755 index abb47d4bfc..0000000000 --- a/tools/gpstest/test_laikad.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -import os -import time -import unittest - -import cereal.messaging as messaging -import openpilot.system.sensord.pigeond as pd - -from openpilot.common.params import Params -from openpilot.system.hardware import TICI -from openpilot.selfdrive.manager.process_config import managed_processes -from openpilot.selfdrive.test.helpers import with_processes - - -def wait_for_location(sm, timeout, con=10): - cons_meas = 0 - start_time = time.monotonic() - while (time.monotonic() - start_time) < timeout: - sm.update() - if not sm.updated["gnssMeasurements"]: - continue - - msg = sm["gnssMeasurements"] - cons_meas = (cons_meas + 1) if 'positionECEF' in msg.to_dict() else 0 - if cons_meas >= con: - return True - return False - - -class TestLaikad(unittest.TestCase): - @classmethod - def setUpClass(self): - if not TICI: - raise unittest.SkipTest - - ublox_available = Params().get_bool("UbloxAvailable") - if not ublox_available: - raise unittest.SkipTest - - def setUp(self): - # ensure laikad cold start - Params().remove("LaikadEphemerisV3") - os.environ["LAIKAD_NO_INTERNET"] = "1" - managed_processes['laikad'].start() - - def tearDown(self): - managed_processes['laikad'].stop() - - - @with_processes(['pigeond', 'ubloxd']) - def test_laikad_cold_start(self): - time.sleep(5) - - start_time = time.monotonic() - sm = messaging.SubMaster(["gnssMeasurements"]) - - success = wait_for_location(sm, 60*2, con=10) - duration = time.monotonic() - start_time - - assert success, "Waiting for location timed out (2min)!" - assert duration < 60, f"Received Location {duration}!" - - - @with_processes(['ubloxd']) - def test_laikad_ublox_reset_start(self): - time.sleep(2) - - pigeon, pm = pd.create_pigeon() - pd.init_baudrate(pigeon) - assert pigeon.reset_device(), "Could not reset device!" - - laikad_sock = messaging.sub_sock("gnssMeasurements", timeout=0.1) - ublox_gnss_sock = messaging.sub_sock("ubloxGnss", timeout=0.1) - - pd.init_baudrate(pigeon) - pd.initialize_pigeon(pigeon) - pd.run_receiving(pigeon, pm, 180) - - ublox_msgs = messaging.drain_sock(ublox_gnss_sock) - laikad_msgs = messaging.drain_sock(laikad_sock) - - gps_ephem_cnt = 0 - glonass_ephem_cnt = 0 - for um in ublox_msgs: - if um.ubloxGnss.which() == 'ephemeris': - gps_ephem_cnt += 1 - elif um.ubloxGnss.which() == 'glonassEphemeris': - glonass_ephem_cnt += 1 - - assert gps_ephem_cnt > 0, "NO gps ephemeris collected!" - assert glonass_ephem_cnt > 0, "NO glonass ephemeris collected!" - - pos_meas = 0 - duration = -1 - for lm in laikad_msgs: - pos_meas = (pos_meas + 1) if 'positionECEF' in lm.gnssMeasurements.to_dict() else 0 - if pos_meas > 5: - duration = (lm.logMonoTime - laikad_msgs[0].logMonoTime)*1e-9 - break - - assert pos_meas > 5, "NOT enough positions at end of read!" - assert duration < 120, "Laikad took too long to get a Position!" - -if __name__ == "__main__": - unittest.main() diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh index 07bb8ac9a4..6753afffb9 100755 --- a/tools/install_python_dependencies.sh +++ b/tools/install_python_dependencies.sh @@ -54,8 +54,8 @@ fi eval "$(pyenv init --path)" echo "update pip" -pip install pip==23.2.1 -pip install poetry==1.5.1 +pip install pip==23.3 +pip install poetry==1.6.1 poetry config virtualenvs.prefer-active-python true --local poetry config virtualenvs.in-project true --local diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index 8ab9e10edc..275b9b65b8 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -3,7 +3,6 @@ import os import pickle import struct import subprocess -import tempfile import threading from enum import IntEnum from functools import wraps @@ -61,16 +60,14 @@ def fingerprint_video(fn): def ffprobe(fn, fmt=None): fn = resolve_name(fn) - cmd = ["ffprobe", - "-v", "quiet", - "-print_format", "json", - "-show_format", "-show_streams"] + cmd = ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams"] if fmt: cmd += ["-f", fmt] - cmd += [fn] + cmd += ["-i", "-"] try: - ffprobe_output = subprocess.check_output(cmd) + with FileReader(fn) as f: + ffprobe_output = subprocess.check_output(cmd, input=f.read(4096)) except subprocess.CalledProcessError as e: raise DataUnreadableError(fn) from e @@ -170,31 +167,21 @@ def rgb24tonv12(rgb): def decompress_video_data(rawdat, vid_fmt, w, h, pix_fmt): - # using a tempfile is much faster than proc.communicate for some reason - - with tempfile.TemporaryFile() as tmpf: - tmpf.write(rawdat) - tmpf.seek(0) - - threads = os.getenv("FFMPEG_THREADS", "0") - cuda = os.getenv("FFMPEG_CUDA", "0") == "1" - args = ["ffmpeg", - "-threads", threads, - "-hwaccel", "none" if not cuda else "cuda", - "-c:v", "hevc", - "-vsync", "0", - "-f", vid_fmt, - "-flags2", "showall", - "-i", "pipe:0", - "-threads", threads, - "-f", "rawvideo", - "-pix_fmt", pix_fmt, - "pipe:1"] - with subprocess.Popen(args, stdin=tmpf, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) as proc: - # dat = proc.communicate()[0] - dat = proc.stdout.read() - if proc.wait() != 0: - raise DataUnreadableError("ffmpeg failed") + threads = os.getenv("FFMPEG_THREADS", "0") + cuda = os.getenv("FFMPEG_CUDA", "0") == "1" + args = ["ffmpeg", "-v", "quiet", + "-threads", threads, + "-hwaccel", "none" if not cuda else "cuda", + "-c:v", "hevc", + "-vsync", "0", + "-f", vid_fmt, + "-flags2", "showall", + "-i", "-", + "-threads", threads, + "-f", "rawvideo", + "-pix_fmt", pix_fmt, + "-"] + dat = subprocess.check_output(args, input=rawdat) if pix_fmt == "rgb24": ret = np.frombuffer(dat, dtype=np.uint8).reshape(-1, h, w, 3) diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index d3a32a2651..b83f657e39 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -153,12 +153,10 @@ void Replay::buildTimeline() { const auto &route_segments = route_->segments(); for (auto it = route_segments.cbegin(); it != route_segments.cend() && !exit_; ++it) { - LogReader log; - if (!log.load(it->second.qlog.toStdString(), &exit_, - {cereal::Event::Which::CONTROLS_STATE, cereal::Event::Which::USER_FLAG}, - !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; + std::shared_ptr log(new LogReader()); + if (!log->load(it->second.qlog.toStdString(), &exit_, {}, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; - for (const Event *e : log.events) { + for (const Event *e : log->events) { if (e->which == cereal::Event::Which::CONTROLS_STATE) { auto cs = e->event.getControlsState(); @@ -186,6 +184,8 @@ void Replay::buildTimeline() { timeline.push_back({toSeconds(e->mono_time), toSeconds(e->mono_time), TimelineType::UserFlag}); } } + std::sort(timeline.begin(), timeline.end(), [](auto &l, auto &r) { return std::get<2>(l) < std::get<2>(r); }); + emit qLogLoaded(it->first, log); } } diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 5ed4ff11b5..01969b0a9f 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -44,6 +44,7 @@ enum class FindFlag { enum class TimelineType { None, Engaged, AlertInfo, AlertWarning, AlertCritical, UserFlag }; typedef bool (*replayEventFilter)(const Event *, void *); +Q_DECLARE_METATYPE(std::shared_ptr); class Replay : public QObject { Q_OBJECT @@ -91,6 +92,7 @@ signals: void streamStarted(); void segmentsMerged(); void seekedTo(double sec); + void qLogLoaded(int segnum, std::shared_ptr qlog); protected slots: void segmentLoadFinished(bool success); diff --git a/tools/replay/util.cc b/tools/replay/util.cc index 2c2de69e78..b4f72d0530 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -19,10 +19,7 @@ #include "common/util.h" ReplayMessageHandler message_handler = nullptr; -DownloadProgressHandler download_progress_handler = nullptr; - void installMessageHandler(ReplayMessageHandler handler) { message_handler = handler; } -void installDownloadProgressHandler(DownloadProgressHandler handler) { download_progress_handler = handler; } void logMessage(ReplyMsgType type, const char *fmt, ...) { static std::mutex lock; @@ -94,6 +91,11 @@ size_t write_cb(char *data, size_t size, size_t count, void *userp) { size_t dumy_write_cb(char *data, size_t size, size_t count, void *userp) { return size * count; } struct DownloadStats { + void installDownloadProgressHandler(DownloadProgressHandler handler) { + std::lock_guard lk(lock); + download_progress_handler = handler; + } + void add(const std::string &url, uint64_t total_bytes) { std::lock_guard lk(lock); items[url] = {0, total_bytes}; @@ -121,10 +123,17 @@ struct DownloadStats { std::mutex lock; std::map> items; double prev_tm = 0; + DownloadProgressHandler download_progress_handler = nullptr; }; +static DownloadStats download_stats; + } // namespace +void installDownloadProgressHandler(DownloadProgressHandler handler) { + download_stats.installDownloadProgressHandler(handler); +} + std::string formattedDataSize(size_t size) { if (size < 1024) { return std::to_string(size) + " B"; @@ -167,7 +176,6 @@ std::string getUrlWithoutQuery(const std::string &url) { template bool httpDownload(const std::string &url, T &buf, size_t chunk_size, size_t content_length, std::atomic *abort) { - static DownloadStats download_stats; download_stats.add(url, content_length); int parts = 1; diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim index 9fd8a56101..7dffa7b5e5 100644 --- a/tools/sim/Dockerfile.sim +++ b/tools/sim/Dockerfile.sim @@ -31,6 +31,7 @@ COPY ./panda ${OPENPILOT_PATH}/panda COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive COPY ./system ${OPENPILOT_PATH}/system COPY ./tools ${OPENPILOT_PATH}/tools +COPY ./release ${OPENPILOT_PATH}/release RUN --mount=type=bind,source=.ci_cache/scons_cache,target=/tmp/scons_cache,rw scons -j$(nproc) --cache-readonly