diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 5873e7aee6..0f782107ec 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -261,7 +261,7 @@ jobs: name: process_replay_diff.txt path: selfdrive/test/process_replay/diff.txt - name: Upload reference logs - if: ${{ failure() && steps.print-diff.outcome == 'success' && github.event_name == 'pull_request' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} + if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} run: | ${{ env.RUN }} "CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" - name: "Upload coverage to Codecov" diff --git a/RELEASES.md b/RELEASES.md index 9cfcbafea1..55edc47c27 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,8 @@ Version 0.9.2 (2023-03-XX) ======================== +* New driving model, trained on a new dataset * Draw MPC path instead of model predicted path, this is a more accurate representation of what the car will do. +* Buick LaCrosse 2017-19 support thanks to koch-cf! * Škoda Fabia 2022-23 support thanks to jyoung8607! Version 0.9.1 (2023-02-28) diff --git a/cereal b/cereal index 9888e0476c..d70d215de6 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 9888e0476c05069d46f273569467e4371b2d8690 +Subproject commit d70d215de6c584f671272d2de2f46a4f778e9f14 diff --git a/common/params.cc b/common/params.cc index e74c63bf6a..428830a112 100644 --- a/common/params.cc +++ b/common/params.cc @@ -98,6 +98,7 @@ std::unordered_map keys = { {"CarVin", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"CompletedTrainingVersion", PERSISTENT}, {"ControlsReady", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, + {"CurrentBootlog", PERSISTENT}, {"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisablePowerDown", PERSISTENT}, @@ -110,7 +111,7 @@ std::unordered_map keys = { {"DoReboot", CLEAR_ON_MANAGER_START}, {"DoShutdown", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START}, - {"FirmwareObdQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, + {"FirmwareQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"ForcePowerDown", CLEAR_ON_MANAGER_START}, {"GitBranch", PERSISTENT}, {"GitCommit", PERSISTENT}, @@ -137,7 +138,7 @@ std::unordered_map keys = { {"IsReleaseBranch", CLEAR_ON_MANAGER_START}, {"IsUpdateAvailable", CLEAR_ON_MANAGER_START}, {"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, - {"LaikadEphemerisV2", PERSISTENT | DONT_LOG}, + {"LaikadEphemerisV3", PERSISTENT | DONT_LOG}, {"LanguageSetting", PERSISTENT}, {"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastGPSPosition", PERSISTENT}, @@ -154,7 +155,8 @@ std::unordered_map keys = { {"NavSettingTime24h", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT}, {"NavdRender", PERSISTENT}, - {"ObdMultiplexingDisabled", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, + {"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, + {"ObdMultiplexingEnabled", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"OpenpilotEnabledToggle", PERSISTENT}, {"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"PandaSignatures", CLEAR_ON_MANAGER_START}, diff --git a/docs/CARS.md b/docs/CARS.md index 070d068cab..7c8170c275 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 three. All supported cars provide a better experience than any stock system. -# 238 Supported Cars +# 239 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -17,6 +17,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| |Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Buick|LaCrosse 2017-19[3](#footnotes)|Driver Confidence Package 2|openpilot|18 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| |Cadillac|Escalade 2017[3](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| |Cadillac|Escalade ESV 2016[3](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| |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)](##)|GM|| @@ -43,9 +44,9 @@ A supported vehicle is one that just works when you install a comma three. All s |Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| |Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| |Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| -|Honda|Civic 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B|| +|Honda|Civic 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B|| |Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| -|Honda|Civic Hatchback 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B|| +|Honda|Civic Hatchback 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B|| |Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| |Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| |Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| @@ -116,7 +117,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| -|Kia|Sorento 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Kia|Sorento 2021-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| |Kia|Sorento Plug-in Hybrid 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| |Kia|Sportage 2023[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| |Kia|Sportage Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| @@ -125,14 +126,14 @@ A supported vehicle is one that just works when you install a comma three. All s |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| -|Lexus|ES Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| |Lexus|ES Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| |Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| -|Lexus|RC 2017-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| |Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| @@ -148,7 +149,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Nissan|Leaf 2018-22|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|| |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|| -|Ram|1500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram|| +|Ram|1500 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram|| |SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| |Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| diff --git a/laika_repo b/laika_repo index b740b71c82..6fadabd860 160000 --- a/laika_repo +++ b/laika_repo @@ -1 +1 @@ -Subproject commit b740b71c82a748e3520b1599487d9a7aaf728670 +Subproject commit 6fadabd86043ee19e06c6ed59aa4e688c14fa8e4 diff --git a/panda b/panda index b231281c8b..a12c0a7956 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit b231281c8b2c79c46892576dee4c8a56d5e0b2d2 +Subproject commit a12c0a795678373d946b644b43d9402b72e502a9 diff --git a/poetry.lock b/poetry.lock index ec4022c81f..b7554ac03a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -113,9 +113,12 @@ category = "dev" optional = false python-versions = "*" +[package.dependencies] +packaging = ">20.6" + [package.source] type = "url" -url = "https://github.com/commaai/apex/releases/download/pytorch1.10.0%2Bcu11.1/apex-0.1-cp38-cp38-linux_x86_64.whl" +url = "https://github.com/commaai/apex/releases/download/pytorch2.0.0%2Bcu11.8/apex-0.1-cp38-cp38-linux_x86_64.whl" [[package]] name = "appdirs" @@ -457,6 +460,14 @@ python-versions = "*" docutils = ">=0.12" Sphinx = ">=4.0,<5.0.0 || >5.0.0,<6" +[[package]] +name = "brotli" +version = "1.0.9" +description = "Python bindings for the Brotli compression library" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "cachecontrol" version = "0.12.11" @@ -600,6 +611,17 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "cmake" +version = "3.26.0" +description = "CMake is an open-source, cross-platform family of tools designed to build, test and package software" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +test = ["codecov (>=2.0.5)", "coverage (>=4.2)", "flake8 (>=3.0.4)", "path.py (>=11.5.0)", "pytest (>=3.0.3)", "pytest-cov (>=2.4.0)", "pytest-runner (>=2.9)", "pytest-virtualenv (>=1.7.0)", "scikit-build (>=0.10.0)", "setuptools (>=28.0.0)", "virtualenv (>=15.0.3)", "wheel"] + [[package]] name = "colorama" version = "0.4.5" @@ -716,8 +738,8 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] [[package]] -name = "cupy-cuda113" -version = "10.6.0" +name = "cupy-cuda11x" +version = "11.6.0" description = "CuPy: NumPy & SciPy for GPU" category = "dev" optional = false @@ -725,12 +747,12 @@ python-versions = ">=3.7" [package.dependencies] fastrlock = ">=0.5" -numpy = ">=1.18,<1.25" +numpy = ">=1.20,<1.27" [package.extras] -all = ["Cython (>=0.29.22,<3)", "optuna (>=2.0)", "scipy (>=1.4,<1.11)"] +all = ["Cython (>=0.29.22,<3)", "optuna (>=2.0)", "scipy (>=1.6,<1.12)"] stylecheck = ["autopep8 (==1.5.5)", "flake8 (==3.8.4)", "mypy (==0.950)", "pbr (==5.5.1)", "pycodestyle (==2.6.0)", "types-setuptools (==57.4.14)"] -test = ["hypothesis (>=6.37.2)", "pytest (>=6.2)"] +test = ["hypothesis (>=6.37.2,<6.55.0)", "pytest (>=7.2)"] [[package]] name = "cycler" @@ -1180,6 +1202,20 @@ monitor = ["psutil (>=5.7.0)"] recommended = ["backports.socketpair", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)", "selectors2"] test = ["backports.socketpair", "cffi (>=1.12.2)", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "dnspython (>=1.16.0,<2.0)", "futures", "idna", "mock", "objgraph", "psutil (>=5.7.0)", "requests", "selectors2"] +[[package]] +name = "geventhttpclient" +version = "2.0.2" +description = "http client library for gevent" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +brotli = "*" +certifi = "*" +gevent = ">=0.13" +six = "*" + [[package]] name = "greenlet" version = "1.1.3.post0" @@ -1898,6 +1934,14 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "lit" +version = "15.0.7" +description = "A Software Testing Tool" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "lockfile" version = "0.12.2" @@ -2586,7 +2630,7 @@ numpy = [ [package.source] type = "url" -url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu113/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl" +url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu118/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl" [[package]] name = "osmium" @@ -3339,6 +3383,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "python-rapidjson" +version = "1.10" +description = "Python wrapper around rapidjson" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "python-socketio" version = "5.7.2" @@ -4220,18 +4272,26 @@ python-versions = ">=3.6,<4.0" [[package]] name = "torch" -version = "1.11.0+cu113" +version = "2.0.0+cu118" description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" category = "dev" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" [package.dependencies] +filelock = "*" +jinja2 = "*" +networkx = "*" +sympy = "*" +triton = {version = "2.0.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} typing-extensions = "*" +[package.extras] +opt-einsum = ["opt-einsum (>=3.3)"] + [package.source] type = "url" -url = "https://download.pytorch.org/whl/cu113/torch-1.11.0%2Bcu113-cp38-cp38-linux_x86_64.whl" +url = "https://download.pytorch.org/whl/cu118/torch-2.0.0%2Bcu118-cp38-cp38-linux_x86_64.whl" [[package]] name = "torchsummary" @@ -4243,25 +4303,24 @@ python-versions = "*" [[package]] name = "torchvision" -version = "0.12.0+cu113" +version = "0.15.1+cu118" description = "image and video datasets and models for torch deep learning" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" [package.dependencies] numpy = "*" pillow = ">=5.3.0,<8.3.0 || >=8.4.0" requests = "*" -torch = "1.11.0" -typing-extensions = "*" +torch = "2.0.0" [package.extras] scipy = ["scipy"] [package.source] type = "url" -url = "https://download.pytorch.org/whl/cu113/torchvision-0.12.0%2Bcu113-cp38-cp38-linux_x86_64.whl" +url = "https://download.pytorch.org/whl/cu118/torchvision-0.15.1%2Bcu118-cp38-cp38-linux_x86_64.whl" [[package]] name = "tornado" @@ -4302,16 +4361,41 @@ test = ["pre-commit", "pytest"] [[package]] name = "triton" -version = "1.1.1" +version = "2.0.0" description = "A language and compiler for custom Deep Learning operations" category = "dev" optional = false python-versions = "*" [package.dependencies] +cmake = "*" filelock = "*" +lit = "*" torch = "*" +[package.extras] +tests = ["autopep8", "flake8", "isort", "numpy", "pytest", "scipy (>=1.7.1)"] +tutorials = ["matplotlib", "pandas", "tabulate"] + +[[package]] +name = "tritonclient" +version = "2.28.0" +description = "Python client library and utilities for communicating with Triton Inference Server" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +aiohttp = {version = ">=3.8.1", optional = true, markers = "extra == \"http\""} +geventhttpclient = {version = ">=1.4.4,<=2.0.2", optional = true, markers = "extra == \"http\""} +numpy = ">=1.19.1" +python-rapidjson = ">=0.9.1" + +[package.extras] +all = ["aiohttp (>=3.8.1)", "geventhttpclient (>=1.4.4,<=2.0.2)", "grpcio (==1.41.0)", "numpy (>=1.19.1)", "protobuf (>=3.5.0,<3.20)", "python-rapidjson (>=0.9.1)"] +grpc = ["grpcio (==1.41.0)", "numpy (>=1.19.1)", "protobuf (>=3.5.0,<3.20)", "python-rapidjson (>=0.9.1)"] +http = ["aiohttp (>=3.8.1)", "geventhttpclient (>=1.4.4,<=2.0.2)", "numpy (>=1.19.1)", "python-rapidjson (>=0.9.1)"] + [[package]] name = "types-atomicwrites" version = "1.4.5.1" @@ -4357,9 +4441,9 @@ types-urllib3 = "<1.27" [[package]] name = "types-tabulate" -version = "0.9.0.1" +version = "0.8.11" description = "Typing stubs for tabulate" -category = "main" +category = "dev" optional = false python-versions = "*" @@ -4575,7 +4659,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "~3.8" -content-hash = "669485055bf8d77336509cb7a3878e06aa32431c520825948914d76b57347fde" +content-hash = "b7cd75dfa0dcddff224696ccc7f41d87aac64652f744ab386321c1eee920fbe9" [metadata.files] adal = [ @@ -4885,6 +4969,90 @@ breathe = [ {file = "breathe-4.34.0-py3-none-any.whl", hash = "sha256:48804dcf0e607a89fb6ad88c729ef12743a42db03ae9489be4ef8f7c4011774a"}, {file = "breathe-4.34.0.tar.gz", hash = "sha256:ac0768a5e84addad3e632028fe67749c567aba2b29088493b64c2c1634bcdba1"}, ] +brotli = [ + {file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"}, + {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"}, + {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"}, + {file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"}, + {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"}, + {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"}, + {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"}, + {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, + {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, + {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, + {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a"}, + {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f"}, + {file = "Brotli-1.0.9-cp311-cp311-win32.whl", hash = "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d"}, + {file = "Brotli-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679"}, + {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, + {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"}, + {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"}, + {file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"}, + {file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"}, + {file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, + {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, + {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, + {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, + {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, + {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, + {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, + {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, + {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, + {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, + {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, + {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, + {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, + {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"}, + {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, +] cachecontrol = [ {file = "CacheControl-0.12.11-py2.py3-none-any.whl", hash = "sha256:2c75d6a8938cb1933c75c50184549ad42728a27e9f6b92fd677c3151aa72555b"}, {file = "CacheControl-0.12.11.tar.gz", hash = "sha256:a5b9fcc986b184db101aa280b42ecdcdfc524892596f606858e0b7a8b4d9e144"}, @@ -5040,6 +5208,25 @@ cloudpickle = [ {file = "cloudpickle-2.2.0-py3-none-any.whl", hash = "sha256:7428798d5926d8fcbfd092d18d01a2a03daf8237d8fcdc8095d256b8490796f0"}, {file = "cloudpickle-2.2.0.tar.gz", hash = "sha256:3f4219469c55453cfe4737e564b67c2a149109dabf7f242478948b895f61106f"}, ] +cmake = [ + {file = "cmake-3.26.0-py2.py3-none-macosx_10_10_universal2.macosx_10_10_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:4881727389325af84e39f1ec646b7249d8910b4ed637205bee6d589cb2b2ebd2"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:babd1e38c85d38a4bf4164c3126ec8cf4cd8d374072e2a4a181e52e953007f8c"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:1851be29d79bb39505954165e934d31994268d49f566ead6fff840a5092e444d"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:483aaaeb9535deaa2657c928af5d9f0da9329f89bc249f494923495745a03677"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2f78c6194f224d462333e08d3acd571c553b58d04935971b87efbe76f241353c"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:cf129c8b71f1344975f179f30287baa8804c4c61ff1b13003244b4157b676e13"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fabb82c69223eda27e673c5c6bb02985d60cf0baa631a2e4932eed87e8229928"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502cbed2335557920b88687c6f3ff4ce433bd416204c928ad489e399e76149f5"}, + {file = "cmake-3.26.0-py2.py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:b81c7775b125786c1696232078e9ceb0c9c66d26fb0a2259f40e33983170f924"}, + {file = "cmake-3.26.0-py2.py3-none-musllinux_1_1_i686.whl", hash = "sha256:f4cc1dde7613cc813d15fd840a33e18cd07a443aa67205d74b9b05f55f08a459"}, + {file = "cmake-3.26.0-py2.py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:ac6ca9d9ff58900138bbbebc55857647fb99f1c40b84d8a232f23b0d27d7f48a"}, + {file = "cmake-3.26.0-py2.py3-none-musllinux_1_1_s390x.whl", hash = "sha256:85e0bad5aeb3a82919ed7d78b76a0462eafe2f918076a1823a09c6f37910f3e0"}, + {file = "cmake-3.26.0-py2.py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:5523b9701be367572155e77294bf8aa3ac9aa0d73c50dcd6c57903d12bcb308e"}, + {file = "cmake-3.26.0-py2.py3-none-win32.whl", hash = "sha256:96f0e887260255eeb83bcf3465d51bc6c94078251c9312fa142dce6d3e80acac"}, + {file = "cmake-3.26.0-py2.py3-none-win_amd64.whl", hash = "sha256:a0719a6f79cdc4d7b16caf757b8ae13eae1c6ce5a08d594cc09774afe129515d"}, + {file = "cmake-3.26.0-py2.py3-none-win_arm64.whl", hash = "sha256:11159c9b64c6473d84361ab44ef2c85e1bd2db7a2b1b798ce8bf5988f32adf43"}, + {file = "cmake-3.26.0.tar.gz", hash = "sha256:c18185c9cc147d0fa1e9228962aa37901b37866bd5d617e9efa23dfe706f7321"}, +] colorama = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, @@ -5209,15 +5396,17 @@ cryptography = [ {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, ] -cupy-cuda113 = [ - {file = "cupy_cuda113-10.6.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:27e5efe2c3afa80ff48654cb27f9e0eddb36f8b26ef0d32d3ba0a233e1359b51"}, - {file = "cupy_cuda113-10.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8b96076d1ddd33fdb2c908ed0f8109caf69d37d36f839a8a8cdae1312508336f"}, - {file = "cupy_cuda113-10.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:22363c2863727cae5154aa4bab9e8a648d7fe66c9e2195d81dd4e8693c2e61ce"}, - {file = "cupy_cuda113-10.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8cc69b9d5735372477a7af3822c8f8e996ffe6de05cfc917500af9dc0117ca3e"}, - {file = "cupy_cuda113-10.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:10dc6899577e445426d81f0960ba9059d9aaa750426997c61fad882d6345264c"}, - {file = "cupy_cuda113-10.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:c6893ac9040a11610e63973063dfd715dbda8bd07ef99951bab7a09c7f335e1e"}, - {file = "cupy_cuda113-10.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4bf4bc06d991c06b95f6fe558d117cafd93bd4eeaf80606f18dd31d20d2eff25"}, - {file = "cupy_cuda113-10.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:3745fc42dca86ba8a1109ddc7964aed8e1efc0ce8085cb2f140dcd6429f26354"}, +cupy-cuda11x = [ + {file = "cupy_cuda11x-11.6.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:1b9914f57868a1559e9bfabfbae8c724585914e8e1f277acb9cdb6aa0756eaa4"}, + {file = "cupy_cuda11x-11.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:ac8dd082ddb00996bc4d37cc5765907048f467aadb61bcbff25f3c2a88c50583"}, + {file = "cupy_cuda11x-11.6.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:bf7496340dfbc2eaa3e0ebfd03c4ab8b1fc36d7d14f68718c33eeb395aaa6eed"}, + {file = "cupy_cuda11x-11.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:e3bbd55c26d60069d7e5af2200fb47e7c42ca6437acac297637a2d3c00f6fe46"}, + {file = "cupy_cuda11x-11.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0d7ebcfd7234946719ad28890593d9fc78fad5753ef4b073e0d7bdb7da2f5640"}, + {file = "cupy_cuda11x-11.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3bc3f81fbc5a86c7155c6036809f2ada4023fc0870dce158d3d9f6d0b575727f"}, + {file = "cupy_cuda11x-11.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:1d6810568e683cb153972b3019ddb5efc369036511122117d9eda09cf84d1042"}, + {file = "cupy_cuda11x-11.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:31c1ec72ffe9ad6fda9fb0a2aff1fcca38da66a8e521e333bae0d67ebc80ead0"}, + {file = "cupy_cuda11x-11.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6a4822daadfce0464cb619099eb82c7ba30ae7755d6869ba5eba1c675a9eed67"}, + {file = "cupy_cuda11x-11.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:3d0f44d747b38ce2b213d64270265f953b68cf29860ec5fa65848b9faaea3fc1"}, ] cycler = [ {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, @@ -5648,6 +5837,86 @@ gevent = [ {file = "gevent-22.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0569e133bb620de1001ac807ad9a8abaadedd25349c6d695f80c9048a3f59d42"}, {file = "gevent-22.10.1.tar.gz", hash = "sha256:df3042349c9a4460eeaec8d0e56d737cb183eed055e75a6af9dbda94aaddaf4d"}, ] +geventhttpclient = [ + {file = "geventhttpclient-2.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd76acdc7e7ee5c54c7b279f806b28957a6b092f79c40db34adcfd972749343c"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:320a2c756d8a4f296de370476a1515485c186d9e22c3fc29e04f8f743a7d47bb"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36d3345c6585b09738195a7c45d279a87ccbab0350f1cce3679d3f0dce8577a1"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:407d54499556c2741b93691b86da93232590b013f4a0b773327d766fe3e5c0a9"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcf325131b0e4600b793643108cd85dddd66bbf532fd2eb498be5727ef532a1e"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5841dd02e6f792a4ef15dbd04fefe620c831ba0b78105808160bb779a31af4"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ba69422d4e8670dd99803b1313ba574a4d41f52e92b512af51068c9c577bdc1"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e3af579c6b46b9caa515a8baf6a2cadeafcd1d41ad22ca5712851f074a40b47"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ff7fc19f9a4fdd54a2b1c106a705ea2c679fa049685ed763051d417725bdab1"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-win32.whl", hash = "sha256:eec7c52e8eb817674a193e0124486b507215d9e86d34f2638bf9a9292d16f815"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:0e9f7283c01d970e643d89da81127869a8d94bb7a0081020dcad5b590bc007c4"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5ceb492d43a659b895794999dc40d0e7c23b1d41dd34040bbacd0dc264b57d5b"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95959c201d3151fa8f57e0f1ce184476d1173996bdde41dc7d600006023dc5be"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:31c7febba298ecf44838561074a3fb7a01523adca286469b5a82dcc90e8d6a07"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:996c5f453d810b3c592160193d6832a065cca0112e92adc74e62df0e4c564df6"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f817e226c02b5a71d86de3772d6accdf250288d1e6825e426c713759830162d"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c55b7ac0ba0e1e1afbf297b7608f0b3a0bbc34fb4b0c19b7869f32a77ddc6209"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6775bc81e25c48fa58b034444aecfa508b0c3d1bc1e4ae546cc17661be1f51aa"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a0156882c73537bbbbc7c693ae44c9808119963174078692613ffa4feea21fcf"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ebb582a291c4c5daaac2ea115b413f4be86874baa60def44d333301cee17bd7"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-win32.whl", hash = "sha256:716f1f72f50b841daf9c9511a01fc31a030866510a11863f27741e26e4f556a7"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:777fcdb72077dfbf70516ecb9e9022246dd337b83a4c1e96f17f3ab9e15f4547"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:379d90d8b1fcdda94e74d693806e0b0116c0610504e7f62d5576bac738dc66a5"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00b7b2b836294c091c53789a469c5671202d79420b5191931df4e3a767d607fa"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d075355862d7726eb3436f0136fce7650c884f2d04eaae7a39fed3aad9798bc"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa7b1a27f950d209fe223a97906fe41312dc12c92372424639b8a9b96f1adf91"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fe4e06313aad353b103950780b050d3958000464cc732d621ff8ea3cacbd2bc4"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:84d7be660b6bc53dd53e3f46b3bc5d275972a8116bd183a77139bb4d9d6d9fb1"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:81f839d6becd664d0972b488422f5dc821f8ad2f2196d53aa5e4d799a3a35a66"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:e707f62271a093e6e3af6f1bbd8cc398b414b8c508fe6b15505dd8e76c4409ac"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:28d7655d1d50bc75ece683a0ae8faf978821d4aeae358d77b59371548db07f1e"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58877b4440a580063571a23fbc616aed7c735c6bf9ef525c5129783df8b6966"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57c993c4b2bea551c4a71b75ae1e172e9f3e4352f704ff1b619a0f16aa762f76"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f67e789e31c7b1ce440cd1465dcdefeca29ba6108735eac0b1a593d3a55b7f"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f3326e115ec7e7ce95a5d0d47698e8f3584944c4c434a7404937d56b17136b8"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ef328ee3e7dca5055b833fdf3c181647a335abf0249947b27f5df2d95390198c"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:27049ea40e3b559eee380310272aaa9b7c19e73c1d9e51e2ec137362be2caa70"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b88a10538341e33fed1682c0dd4579c655d49db5863e7456583085a1cd6bd9d4"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:d52aba2c38420b3fc518188449f1c2a46b1a99adf1c0266c68e72ee0422cd0fa"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3648626ca58ea4b340e695d78e5d533e6b8be78d375edbd42ff188bc3447e095"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fcf96e212b55b93490f3a5fcdfe7a2ef4995a0d13b7d9df398b11e319b7a86b1"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e9f2ff09706e3a64a99886d5f2595f3bf364821bc609f2865dbc3e499e21a36"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:721c3075897bfc81e918066f16ae3d1a88c7bb14eeeb831a4f89ea636474643e"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91615fed7931acd49cfe5fc30984acd5411dc1f2643b1544c879d1a537233c6d"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7adaa29e5699dea54e0224d1d2d9d8869668d8ad79f5b89433ff9c46f9424a6c"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9be5000ba57336a90b438782117c1e43205f51f49aa9b1499a82e210e8431b11"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:12d271cc53486efb3716e99855dc5cb84f2cd3fc9f3243721747bb39ec0fff8a"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b9c0c6b75b3905000d2490dc64b4c98a8bac155efbc0ff8917ac082ae0bad261"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e956a457d8831dc81d6f046ab09ebeec680f9a1e9c07e25a1906e77b287918ee"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-win32.whl", hash = "sha256:bc46d5479673dfb293ea428c057d2e23e48ebef5c5d44587cdbaada7f87553e4"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:f44153e4b3ef9b901edcd14be54145a0058bf5fa371b3e583153865fac866245"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ebf98db9435824cf0b80b5247be6c88b20bfafd6249f7ebaabb85297da37e380"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c8b7298eb1ebd015257bf4503e34f5fbbe64bd83324140f76b511046aba5a0d5"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:60b81a6d4e65db7c1a5350c9fb72ebf800b478849a7e8020d1ab93af237a3747"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad6c2fcbc3733785bd3b8c2bb43d1f605f9085b0a8b70ce354d198f37143f884"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94edb022fa50d576cf63f6dd0c437c1acd24a719872a5935991aaf08f8e88cb2"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca459cedb3827d960362e05ea3a4ae600a6d0d93de77eac2ac0f79828e5e18c"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7551b6db860b56411de1f96618e91b54f65e1a7be8d10255bd1adfb738bb6ee5"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bcb7e061c243308d9a44b02de5298001e917f1636a9f270c10da86601fcc8dfa"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:96922d170ef8933f4c20036e8d70d4fbe861f54c543e32e7459ebdbaafa65a2e"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ebb3c993903d40fd4bb1f3e55b84c62c8fc1d14433ae6d4d477dd9a325354c94"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:dbccf1ba155dea3ea99ba0e67a835c05b4303f05298e85f5bb2a46700ccdf092"}, + {file = "geventhttpclient-2.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8770b8ab9e8c31d2aaf8a6fbc63fbb7239c58db10bb49cee191ca5c141c61542"}, + {file = "geventhttpclient-2.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daff1e977fccf98f27266d3891afdc101f1d705a48331754909e960bcae83f8a"}, + {file = "geventhttpclient-2.0.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2435e0f2a60e00d977822ec4c12e7851deb7aa49a23d32d648e72c641aae3b05"}, + {file = "geventhttpclient-2.0.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09acd03d0a8c1bb7d5a1cb6fcb77aaa19a907c1b4915ab58da5d283675edb0a5"}, + {file = "geventhttpclient-2.0.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:5d0813d97050446dab2fb243312e6c446e4ef5e9591befd597ef8f2887f8e2a8"}, + {file = "geventhttpclient-2.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:852da9bb0fc792cdca5ffc9327490094783e42415494b3569e5d532615027439"}, + {file = "geventhttpclient-2.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79304a63a9d0512f2757c5862487b332b18a9c85feebecf6ebc3526c6dd1ba2"}, + {file = "geventhttpclient-2.0.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c1c783fce45f16db448d7e34864f1e9c22fe60a7780d2c1c14edbb1fb7262e"}, + {file = "geventhttpclient-2.0.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c407c2b4bea817c6f752502db4ab0e9f9465b4fb85b459d1332b5f93a3096c"}, + {file = "geventhttpclient-2.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f0d70a83ef4ab93102c6601477c13e9cdbc87205e5237fbf5797e30dc9d3ee8"}, + {file = "geventhttpclient-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b03f298ec19b8a4717cce8112fe30322c9e5bfada84dde61a1a44d1eeffc1d3c"}, + {file = "geventhttpclient-2.0.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2dc94b9a23eb6744a8c729aec2b1cdc4e39acf1d8f16ea85a62810aa6b2cae5"}, + {file = "geventhttpclient-2.0.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:805554594bb29231fd990cc2cbbe493d223d76a6085fec891dd76bb4e0928933"}, + {file = "geventhttpclient-2.0.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb23527d98f626ca7a4e8961ed9bdc6aed3388de306614c69a133b34262460f4"}, + {file = "geventhttpclient-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a594ab319872a38fb7f16be4cfb107d3c63c43a081f2abe241834e9877f27401"}, + {file = "geventhttpclient-2.0.2.tar.gz", hash = "sha256:8135a85200b170def7293d01dd1557931fcd1bec1ac78c52ad7cedd22368b9ba"}, +] greenlet = [ {file = "greenlet-1.1.3.post0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:949c9061b8c6d3e6e439466a9be1e787208dec6246f4ec5fffe9677b4c19fcc3"}, {file = "greenlet-1.1.3.post0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d7815e1519a8361c5ea2a7a5864945906f8e386fa1bc26797b4d443ab11a4589"}, @@ -6045,6 +6314,9 @@ libusb1 = [ {file = "libusb1-3.0.0-py3-none-win_amd64.whl", hash = "sha256:6f6bb010632ada35c661d17a65e135077beef0fbb2434d5ffdb3a4a911fd9490"}, {file = "libusb1-3.0.0.tar.gz", hash = "sha256:5792a9defee40f15d330a40d9b1800545c32e47ba7fc66b6f28f133c9fcc8538"}, ] +lit = [ + {file = "lit-15.0.7.tar.gz", hash = "sha256:ed08ac55afe714a193653df293ae8a6ee6c45d6fb11eeca72ce347d99b88ecc8"}, +] lockfile = [ {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, @@ -6624,6 +6896,7 @@ onnx = [ ] onnx2torch = [ {file = "onnx2torch-1.5.4-py3-none-any.whl", hash = "sha256:fd1a0fe05072bfb9f3d86d9330299b130b41f11bd4ae634db17078974e711725"}, + {file = "onnx2torch-1.5.4.tar.gz", hash = "sha256:df837b557a63540223d85fde4a1d679fde0ca8d8bb89d5379c030b01eddc9c24"}, ] onnxoptimizer = [ {file = "onnxoptimizer-0.3.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e73a5e2e3ca4db9bff54f7131768749c861677b97ee811a136fcf1a52783cf6e"}, @@ -7009,6 +7282,7 @@ pycryptodome = [ {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c9ed8aa31c146bef65d89a1b655f5f4eab5e1120f55fc297713c89c9e56ff0b"}, {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:5099c9ca345b2f252f0c28e96904643153bae9258647585e5e6f649bb7a1844a"}, {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2ec709b0a58b539a4f9d33fb8508264c3678d7edb33a68b8906ba914f71e8c13"}, + {file = "pycryptodome-3.15.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:2ae53125de5b0d2c95194d957db9bb2681da8c24d0fb0fe3b056de2bcaf5d837"}, {file = "pycryptodome-3.15.0-cp27-cp27m-win32.whl", hash = "sha256:fd2184aae6ee2a944aaa49113e6f5787cdc5e4db1eb8edb1aea914bd75f33a0c"}, {file = "pycryptodome-3.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:7e3a8f6ee405b3bd1c4da371b93c31f7027944b2bcce0697022801db93120d83"}, {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:b9c5b1a1977491533dfd31e01550ee36ae0249d78aae7f632590db833a5012b8"}, @@ -7016,12 +7290,14 @@ pycryptodome = [ {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2aa55aae81f935a08d5a3c2042eb81741a43e044bd8a81ea7239448ad751f763"}, {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c3640deff4197fa064295aaac10ab49a0d55ef3d6a54ae1499c40d646655c89f"}, {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:045d75527241d17e6ef13636d845a12e54660aa82e823b3b3341bcf5af03fa79"}, + {file = "pycryptodome-3.15.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:eb6fce570869e70cc8ebe68eaa1c26bed56d40ad0f93431ee61d400525433c54"}, {file = "pycryptodome-3.15.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9ee40e2168f1348ae476676a2e938ca80a2f57b14a249d8fe0d3cdf803e5a676"}, {file = "pycryptodome-3.15.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:4c3ccad74eeb7b001f3538643c4225eac398c77d617ebb3e57571a897943c667"}, {file = "pycryptodome-3.15.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:1b22bcd9ec55e9c74927f6b1f69843cb256fb5a465088ce62837f793d9ffea88"}, {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:57f565acd2f0cf6fb3e1ba553d0cb1f33405ec1f9c5ded9b9a0a5320f2c0bd3d"}, {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:4b52cb18b0ad46087caeb37a15e08040f3b4c2d444d58371b6f5d786d95534c2"}, {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:092a26e78b73f2530b8bd6b3898e7453ab2f36e42fd85097d705d6aba2ec3e5e"}, + {file = "pycryptodome-3.15.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:50ca7e587b8e541eb6c192acf92449d95377d1f88908c0a32ac5ac2703ebe28b"}, {file = "pycryptodome-3.15.0-cp35-abi3-win32.whl", hash = "sha256:e244ab85c422260de91cda6379e8e986405b4f13dc97d2876497178707f87fc1"}, {file = "pycryptodome-3.15.0-cp35-abi3-win_amd64.whl", hash = "sha256:c77126899c4b9c9827ddf50565e93955cb3996813c18900c16b2ea0474e130e9"}, {file = "pycryptodome-3.15.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:9eaadc058106344a566dc51d3d3a758ab07f8edde013712bc8d22032a86b264f"}, @@ -7290,6 +7566,64 @@ python-engineio = [ python-logstash = [ {file = "python-logstash-0.4.8.tar.gz", hash = "sha256:d04e1ce11ecc107e4a4f3b807fc57d96811e964a554081b3bbb44732f74ef5f9"}, ] +python-rapidjson = [ + {file = "python-rapidjson-1.10.tar.gz", hash = "sha256:acfecbf5edb91ec72a20a125de7f56b8c2f6161eff4c65382c8ee6a2484d3540"}, + {file = "python_rapidjson-1.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1db7b0af882999f5685eb7046a0f3b3aca5d55a3e84b3089747d29a4ec6fdade"}, + {file = "python_rapidjson-1.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87c8c8b615513f9dc414af1554140589036d14840f5e1f1845965e1c0a080e1"}, + {file = "python_rapidjson-1.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0a2f5c4abe529ca2764343416e35710a263832533b7bdc76c3285efb5b5ecc8"}, + {file = "python_rapidjson-1.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40467c3a6d8f070cc4d196fe46a79ed59d1a13a4d3fdc6a0325a21816600e5a7"}, + {file = "python_rapidjson-1.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df4e7237a3e77666ccb9b437013294e6aa3968528f7c61f60f6f38eea0f8f79"}, + {file = "python_rapidjson-1.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:99a5215f24ff1fa6cc67ee275a6852aa56d934d3b8cd7a40197feb632b54fd76"}, + {file = "python_rapidjson-1.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3dbea0ee9fa1cd6ecc13a949f6bb94013639d39cdb56f58df4ab61130d35e57c"}, + {file = "python_rapidjson-1.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:6d1d0c5da3bd5f701b1aed550e1e7bd59b16ae642877cddf18815006cf998f9a"}, + {file = "python_rapidjson-1.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:072f76c1f1483bcc4056d7d3a8b0319bf841a73e955f188302094b62b2163bf9"}, + {file = "python_rapidjson-1.10-cp310-cp310-win32.whl", hash = "sha256:c95d466307a2140a7687a575103980c6e81c9f62d19556cafad3d6b2932b7eb1"}, + {file = "python_rapidjson-1.10-cp310-cp310-win_amd64.whl", hash = "sha256:454ffda58cc6fed64d983b1b8ae4b39a563b4fd671dae9132e06450025898539"}, + {file = "python_rapidjson-1.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fff343076fbeee0cd7e4e3fb9472f2d567a127ec7b8b5b7ecba6bf7960a3ce07"}, + {file = "python_rapidjson-1.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:686482c67727edad4b6d0c753bc159f35134a5a623e9651c4b7c008ef2996252"}, + {file = "python_rapidjson-1.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ef7d55688b7123d62690b193537cc048fa9f35cfa43d249fedc0d9fd398890a"}, + {file = "python_rapidjson-1.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f27c0601792533ab6e98452961d61566480dc155da19d2a358a5fd9a85d9321"}, + {file = "python_rapidjson-1.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95176e35e3bacb8a1a27f563e815b5b57c717992c871b1c25fd76a835fbba32c"}, + {file = "python_rapidjson-1.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47a0ec20886b8be86af307c10d699a447e22979ed7dd1f2b7ed5cb7496b3d920"}, + {file = "python_rapidjson-1.10-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f7968c0cb09d9a76aa2483556ba46ab42634baf216cb2f2c7cd6bf77119a33c1"}, + {file = "python_rapidjson-1.10-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5faab270a4dd49216ceaca7169682680b2f5df8311c1ed259e4612d9d0cf61b7"}, + {file = "python_rapidjson-1.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81b797934dc037810f5f98af138b55a3b6f18dd569cc5e8f81fe79956a4717ca"}, + {file = "python_rapidjson-1.10-cp311-cp311-win32.whl", hash = "sha256:6c1d62cc58a61629fc5e216fb7b3a1b02787c98fded874a7b474b1e6325e377e"}, + {file = "python_rapidjson-1.10-cp311-cp311-win_amd64.whl", hash = "sha256:29d31fc4254f1a4dca420e58bd1331e990fc2959d09ff2daa7934d52732a8491"}, + {file = "python_rapidjson-1.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:718f4e217b511cfbf9166f55ccf4bf4e4538495bee403e390cf89791c0debc26"}, + {file = "python_rapidjson-1.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:541bbb96353cf3fe2bdb29e727087226532be4e4573daad6f042cfdea533a564"}, + {file = "python_rapidjson-1.10-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50f0402a6899c6a177d4a37152deefcd59c61e44bef56b71e8d006a186c86286"}, + {file = "python_rapidjson-1.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c035e17744d6d6fba073b550b0040a74e55f2ad33fd798df206ff6879b41ad10"}, + {file = "python_rapidjson-1.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da970bde42309a74a5556e696673ea11c4545b8bee5081b84265ded460b2e9ef"}, + {file = "python_rapidjson-1.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7437a649821008aa456f2fbec737880d7f9bdda7ec94cc1743a43ccf32b5d26"}, + {file = "python_rapidjson-1.10-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:362d969bbd277f78bf0b1ffaa810857ea40351146b827f896f8d49e9c25fc99c"}, + {file = "python_rapidjson-1.10-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:409256e7748c4ab7f17b3793c7a78ca01914c487644fc42140d116ed4dec8c4f"}, + {file = "python_rapidjson-1.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a34a7e2853456fe50ba2ee22e38e7841e55eee10021d4496cce62285f148e8e7"}, + {file = "python_rapidjson-1.10-cp37-cp37m-win32.whl", hash = "sha256:bc4a97940e5afa60a598483d0eb863b26e4810aaf030d92a4301f5fc183e1b6d"}, + {file = "python_rapidjson-1.10-cp37-cp37m-win_amd64.whl", hash = "sha256:89586b67f9c69b66885774acebf3d018e7b8f93cea2b3cffa306ec9d37877594"}, + {file = "python_rapidjson-1.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2e38082b1a8ce3e2bd55821852c0cd643cdabe6497fd9c054f6b47a099afbea0"}, + {file = "python_rapidjson-1.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0955ef22fabb36b26fcad702ae54c1bce2bc2a74b1883c42d251d72011d0d426"}, + {file = "python_rapidjson-1.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a9db03c68ab0158bcdf80299b2c980186d148aa3e05d5650fea5148a425a29a"}, + {file = "python_rapidjson-1.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fde8ab0f06debaa06d93085f19dc3ec3db53f22883f1625dd32b96a87e7009de"}, + {file = "python_rapidjson-1.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23539c9f7d85d64a00d3cb44c7d9ab3be2184d4da42a5f3263dcfd1d0203ee43"}, + {file = "python_rapidjson-1.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bfd484285f3477acef0bb45abd2b80b6252e35a5a53395ce48f0327cbe43c23"}, + {file = "python_rapidjson-1.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8b0ed643ebaa8ddf3f40422752efe83abda29aa30a9e6866ccd9dd591b5057d0"}, + {file = "python_rapidjson-1.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:5aeca661a0f229f1312fb3ad3e1a5c6736d49942d80d4931810158559eb8f119"}, + {file = "python_rapidjson-1.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8a68ed066e8f0878b7112f943cf35ba9e5217395bcdd8cb478cde01871e2701c"}, + {file = "python_rapidjson-1.10-cp38-cp38-win32.whl", hash = "sha256:d286be6f63446776c4958bb37824c683194b4878fc9cd5b7255134fb5a6ba536"}, + {file = "python_rapidjson-1.10-cp38-cp38-win_amd64.whl", hash = "sha256:aece5270c6e6d5c3d54586c9a5fb9677d70d7019744a59560c5c369c7b9bba25"}, + {file = "python_rapidjson-1.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc09c5ad0fe71f262cdcc5655409f132f1560a8af80e76e7757945ce401fdbab"}, + {file = "python_rapidjson-1.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f6447bd7a8ff5135ab7e372b48a174d3c560d5b322e32bd465e8458e6e4593"}, + {file = "python_rapidjson-1.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22ede69213885391b46cc14596bfd4cd1a5c6f34a2db6600fb08b03982dbc7b7"}, + {file = "python_rapidjson-1.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a9425129623718a04b885a12190faa23e7997c4e8632054e18df7ea473f746d"}, + {file = "python_rapidjson-1.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60e10f32e1a8d155448842934cbe71eb620b4b4a0cb3627ba4c4856e27556534"}, + {file = "python_rapidjson-1.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8d23caab17b87ed5b82e28cdc19172ba1ca65c982e3fff387961d3f33710031f"}, + {file = "python_rapidjson-1.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f07d4fcdcfd64bdad0143b9705c5d5089677ebddf60ac6c1f8074a34b1c70cf9"}, + {file = "python_rapidjson-1.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:87b991c7ae435489c56a46cef228d2b65a3df689ee4fe24fab69c791c841f633"}, + {file = "python_rapidjson-1.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3706a5c4f21073c04be133c36565efc6e3f5646a03c8d19af78c19d7c70eb708"}, + {file = "python_rapidjson-1.10-cp39-cp39-win32.whl", hash = "sha256:47f9078ea6884f700166a8728d863609fec62232e66a33b8fb4a7706ce7c731c"}, + {file = "python_rapidjson-1.10-cp39-cp39-win_amd64.whl", hash = "sha256:9e4921ab7002ae9faad7f439a7c50aa195039f177e9e51a76c34c97966c79a79"}, +] python-socketio = [ {file = "python-socketio-5.7.2.tar.gz", hash = "sha256:92395062d9db3c13d30e7cdedaa0e1330bba78505645db695415f9a3c628d097"}, {file = "python_socketio-5.7.2-py3-none-any.whl", hash = "sha256:d9a9f047e6fdd306c852fbac36516f4b495c2096f8ad9ceb8803b8e5ff5622e3"}, @@ -7663,6 +7997,18 @@ setproctitle = [ {file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1"}, {file = "setproctitle-1.3.2-cp310-cp310-win32.whl", hash = "sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099"}, {file = "setproctitle-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d"}, + {file = "setproctitle-1.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a97d51c17d438cf5be284775a322d57b7ca9505bb7e118c28b1824ecaf8aeaa"}, + {file = "setproctitle-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587c7d6780109fbd8a627758063d08ab0421377c0853780e5c356873cdf0f077"}, + {file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d17c8bd073cbf8d141993db45145a70b307385b69171d6b54bcf23e5d644de"}, + {file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e932089c35a396dc31a5a1fc49889dd559548d14cb2237adae260382a090382e"}, + {file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e4f8f12258a8739c565292a551c3db62cca4ed4f6b6126664e2381acb4931bf"}, + {file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:570d255fd99c7f14d8f91363c3ea96bd54f8742275796bca67e1414aeca7d8c3"}, + {file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a8e0881568c5e6beff91ef73c0ec8ac2a9d3ecc9edd6bd83c31ca34f770910c4"}, + {file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4bba3be4c1fabf170595b71f3af46c6d482fbe7d9e0563999b49999a31876f77"}, + {file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:37ece938110cab2bb3957e3910af8152ca15f2b6efdf4f2612e3f6b7e5459b80"}, + {file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db684d6bbb735a80bcbc3737856385b55d53f8a44ce9b46e9a5682c5133a9bf7"}, + {file = "setproctitle-1.3.2-cp311-cp311-win32.whl", hash = "sha256:ca58cd260ea02759238d994cfae844fc8b1e206c684beb8f38877dcab8451dfc"}, + {file = "setproctitle-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:88486e6cce2a18a033013d17b30a594f1c5cb42520c49c19e6ade40b864bb7ff"}, {file = "setproctitle-1.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927"}, {file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02"}, {file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83"}, @@ -8032,11 +8378,28 @@ traitlets = [ {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"}, ] triton = [ - {file = "triton-1.1.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8441e6f44517aef8f6345f621c003926cbe970892802411a949ccda516cbd5ba"}, - {file = "triton-1.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840776bc1f4757fb2d6af974694c5e5313220ceec238ee6118b9728bc2aa9ade"}, - {file = "triton-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d42cdaa7d56de463d762c18cc876bfd0828a2b6a706263393fe7e10d1c83ca"}, - {file = "triton-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc19c0e902bbf7d29de4d444455608065a2c56e3524f4bc94e724511ca518f3"}, - {file = "triton-1.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02f798cd2dd922228082ce1a4e9d81badb9a6217a9aac6d783e95bf7055974d"}, + {file = "triton-2.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38806ee9663f4b0f7cd64790e96c579374089e58f49aac4a6608121aa55e2505"}, + {file = "triton-2.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:226941c7b8595219ddef59a1fdb821e8c744289a132415ddd584facedeb475b1"}, + {file = "triton-2.0.0-1-cp36-cp36m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4c9fc8c89874bc48eb7e7b2107a9b8d2c0bf139778637be5bfccb09191685cfd"}, + {file = "triton-2.0.0-1-cp37-cp37m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d2684b6a60b9f174f447f36f933e9a45f31db96cb723723ecd2dcfd1c57b778b"}, + {file = "triton-2.0.0-1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9d4978298b74fcf59a75fe71e535c092b023088933b2f1df933ec32615e4beef"}, + {file = "triton-2.0.0-1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:74f118c12b437fb2ca25e1a04759173b517582fcf4c7be11913316c764213656"}, + {file = "triton-2.0.0-1-pp37-pypy37_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9618815a8da1d9157514f08f855d9e9ff92e329cd81c0305003eb9ec25cc5add"}, + {file = "triton-2.0.0-1-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aca3303629cd3136375b82cb9921727f804e47ebee27b2677fef23005c3851a"}, + {file = "triton-2.0.0-1-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e3e13aa8b527c9b642e3a9defcc0fbd8ffbe1c80d8ac8c15a01692478dc64d8a"}, + {file = "triton-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f05a7e64e4ca0565535e3d5d3405d7e49f9d308505bb7773d21fb26a4c008c2"}, + {file = "triton-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4b99ca3c6844066e516658541d876c28a5f6e3a852286bbc97ad57134827fd"}, + {file = "triton-2.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47b4d70dc92fb40af553b4460492c31dc7d3a114a979ffb7a5cdedb7eb546c08"}, + {file = "triton-2.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fedce6a381901b1547e0e7e1f2546e4f65dca6d91e2d8a7305a2d1f5551895be"}, + {file = "triton-2.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75834f27926eab6c7f00ce73aaf1ab5bfb9bec6eb57ab7c0bfc0a23fac803b4c"}, + {file = "triton-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0117722f8c2b579cd429e0bee80f7731ae05f63fe8e9414acd9a679885fcbf42"}, + {file = "triton-2.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcd9be5d0c2e45d2b7e6ddc6da20112b6862d69741576f9c3dbaf941d745ecae"}, + {file = "triton-2.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42a0d2c3fc2eab4ba71384f2e785fbfd47aa41ae05fa58bf12cb31dcbd0aeceb"}, + {file = "triton-2.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c47b72c72693198163ece9d90a721299e4fb3b8e24fd13141e384ad952724f"}, +] +tritonclient = [ + {file = "tritonclient-2.28.0-py3-none-any.whl", hash = "sha256:1f58bbe09a88c35f7979de8ab6579a5337372951f723c0aba31e8bee3e8d79da"}, + {file = "tritonclient-2.28.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:47d93197a0876a743012db4c03f1100f7b225b9aaf8d5f8025bf4a5d9e61bfd2"}, ] types-atomicwrites = [ {file = "types-atomicwrites-1.4.5.1.tar.gz", hash = "sha256:9e9f0923ebf93524b28bcece5a23ac8c3820f39b060df29f671936d2e4bc04bc"}, @@ -8059,8 +8422,8 @@ types-requests = [ {file = "types_requests-2.28.11.2-py3-none-any.whl", hash = "sha256:14941f8023a80b16441b3b46caffcbfce5265fd14555844d6029697824b5a2ef"}, ] types-tabulate = [ - {file = "types-tabulate-0.9.0.1.tar.gz", hash = "sha256:e486292c279f19247865bdabe802419740a0e74b53444e7f7a8009e08129da5d"}, - {file = "types_tabulate-0.9.0.1-py3-none-any.whl", hash = "sha256:be2ea0de05f615ccfcbadf6206aa720e265955eb1de23e343aec9d8bf3fa9aaa"}, + {file = "types-tabulate-0.8.11.tar.gz", hash = "sha256:17a5fa3b5ca453815778fc9865e8ecd0118b07b2b9faff3e2b06fe448174dd5e"}, + {file = "types_tabulate-0.8.11-py3-none-any.whl", hash = "sha256:af811268241e8fb87b63c052c87d1e329898a93191309d5d42111372232b2e0e"}, ] types-urllib3 = [ {file = "types-urllib3-1.26.25.1.tar.gz", hash = "sha256:a948584944b2412c9a74b9cf64f6c48caf8652cb88b38361316f6d15d8a184cd"}, diff --git a/pyproject.toml b/pyproject.toml index b1ad273cd7..47994779b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,6 @@ urllib3 = "^1.26.10" utm = "^0.7.0" websocket_client = "^1.3.3" polyline = "^1.4.0" -types-tabulate = "^0.9.0.1" [tool.poetry.group.dev.dependencies] @@ -84,7 +83,7 @@ mypy = "^0.961" myst-parser = "^0.18.0" natsort = "^8.1.0" numpy = "^1.23.0" -opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu113/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl", platform = "linux" } +opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu118/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl", platform = "linux" } pandas = "^1.4.3" parameterized = "^0.8.1" paramiko = "^2.11.0" @@ -108,6 +107,7 @@ types-certifi = "^2021.10.8" types-pycurl = "^7.45.1" types-PyYAML = "^6.0" types-requests = "^2.28.11" +types-tabulate = "^0.8.10" [tool.poetry.group.xx] @@ -117,7 +117,7 @@ optional = true aenum = "^3.1.11" aiohttp = "^3.8.1" albumentations = "^1.2.1" -apex = { url = "https://github.com/commaai/apex/releases/download/pytorch1.10.0%2Bcu11.1/apex-0.1-cp38-cp38-linux_x86_64.whl" } +apex = { url = "https://github.com/commaai/apex/releases/download/pytorch2.0.0%2Bcu11.8/apex-0.1-cp38-cp38-linux_x86_64.whl" } azure-cli-core = "^2.38.0" azure-common = "^1.1.28" azure-core = "^1.24.2" @@ -127,7 +127,7 @@ azure-storage-nspkg = "~3.1" blosc = "==1.9.2" cloudpickle = "^2.1.0" configargparse = "^1.5.3" -cupy-cuda113 = "^10.6.0" +cupy-cuda11x = "^11.6.0" datadog = "^0.44.0" dotmap = "^1.3.30" einops = "^0.5.0" @@ -166,14 +166,15 @@ scikit-learn = "^1.1.1" segmentation-models-pytorch = "==0.2.1" simplejson = "^3.17.6" SQLAlchemy = "^1.4.39" -torch = { url = "https://download.pytorch.org/whl/cu113/torch-1.11.0%2Bcu113-cp38-cp38-linux_x86_64.whl" } +torch = { url = "https://download.pytorch.org/whl/cu118/torch-2.0.0%2Bcu118-cp38-cp38-linux_x86_64.whl" } torchsummary = "^1.5.1" -torchvision = { url = "https://download.pytorch.org/whl/cu113/torchvision-0.12.0%2Bcu113-cp38-cp38-linux_x86_64.whl" } -triton = "^1.1.1" +torchvision = { url = "https://download.pytorch.org/whl/cu118/torchvision-0.15.1%2Bcu118-cp38-cp38-linux_x86_64.whl" } +triton = "^2.0.0" Werkzeug = "^2.1.2" zerorpc = { git = "https://github.com/commaai/zerorpc-python.git", branch = "master" } omegaconf = "^2.3.0" osmnx = "==1.2.2" +tritonclient = {version = "2.28.0", extras = ["http"]} [build-system] diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 5d885c2c79..8f045c2a69 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -113,32 +113,35 @@ bool safety_setter_thread(std::vector pandas) { return false; } - // set to ELM327 for fingerprinting + // initialize to ELM327 without OBD multiplexing for fingerprinting + bool obd_multiplexing_enabled = false; for (int i = 0; i < pandas.size(); i++) { - const uint16_t safety_param = (i > 0) ? 1U : 0U; - pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); + pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); } - // wait for FW query at OBD port to finish + // openpilot can switch between multiplexing modes for different FW queries while (true) { if (do_exit || !check_all_connected(pandas) || !ignition) { return false; } - if (p.getBool("FirmwareObdQueryDone")) { - LOGW("finished FW query at OBD port"); + bool obd_multiplexing_requested = p.getBool("ObdMultiplexingEnabled"); + if (obd_multiplexing_requested != obd_multiplexing_enabled) { + for (int i = 0; i < pandas.size(); i++) { + const uint16_t safety_param = (i > 0 || !obd_multiplexing_requested) ? 1U : 0U; + pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); + } + obd_multiplexing_enabled = obd_multiplexing_requested; + p.putBool("ObdMultiplexingChanged", true); + } + + if (p.getBool("FirmwareQueryDone")) { + LOGW("finished FW query"); break; } util::sleep_for(20); } - // set to ELM327 to finish fingerprinting and for potential ECU knockouts - for (Panda *panda : pandas) { - panda->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); - } - - p.putBool("ObdMultiplexingDisabled", true); - std::string params; LOGW("waiting for params to set safety model"); while (true) { @@ -415,7 +418,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector size_t j = 0; for (size_t f = size_t(cereal::PandaState::FaultType::RELAY_MALFUNCTION); - f <= size_t(cereal::PandaState::FaultType::INTERRUPT_RATE_EXTI); f++) { + f <= size_t(cereal::PandaState::FaultType::SIREN_MALFUNCTION); f++) { if (fault_bits.test(f)) { faults.set(j, cereal::PandaState::FaultType(f)); j++; diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index 647a0d9c78..4ad4b5e652 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -203,7 +203,7 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data assert(can_data.size() <= 64); assert(can_data.size() == dlc_to_len[data_len_code]); - can_header header; + can_header header = {}; header.addr = cmsg.getAddress(); header.extended = (cmsg.getAddress() >= 0x800) ? 1 : 0; header.data_len_code = data_len_code; diff --git a/selfdrive/boardd/tests/test_boardd_loopback.py b/selfdrive/boardd/tests/test_boardd_loopback.py index b8ebbd88a3..d614f0b126 100755 --- a/selfdrive/boardd/tests/test_boardd_loopback.py +++ b/selfdrive/boardd/tests/test_boardd_loopback.py @@ -51,7 +51,7 @@ class TestBoardd(unittest.TestCase): cp.safetyConfigs = [safety_config]*num_pandas params = Params() - params.put_bool("FirmwareObdQueryDone", True) + params.put_bool("FirmwareQueryDone", True) params.put_bool("ControlsReady", True) params.put("CarParams", cp.to_bytes()) diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index dc82f56197..6a131c77de 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -8,7 +8,7 @@ from system.version import is_comma_remote, is_tested_branch from selfdrive.car.interfaces import get_interface_attr from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN -from selfdrive.car.fw_versions import disable_obd_multiplexing, get_fw_versions_ordered, match_fw_to_car, get_present_ecus +from selfdrive.car.fw_versions import get_fw_versions_ordered, get_present_ecus, match_fw_to_car, set_obd_multiplexing from system.swaglog import cloudlog import cereal.messaging as messaging from selfdrive.car import gen_empty_fingerprint @@ -80,12 +80,13 @@ def fingerprint(logcan, sendcan, num_pandas): fixed_fingerprint = os.environ.get('FINGERPRINT', "") skip_fw_query = os.environ.get('SKIP_FW_QUERY', False) ecu_rx_addrs = set() + params = Params() if not skip_fw_query: # Vin query only reliably works through OBDII bus = 1 - cached_params = Params().get("CarParamsCache") + cached_params = params.get("CarParamsCache") if cached_params is not None: cached_params = car.CarParams.from_bytes(cached_params) if cached_params.carName == "mock": @@ -98,6 +99,7 @@ def fingerprint(logcan, sendcan, num_pandas): cached = True else: cloudlog.warning("Getting VIN & FW versions") + set_obd_multiplexing(params, True) vin_rx_addr, vin = get_vin(logcan, sendcan, bus) ecu_rx_addrs = get_present_ecus(logcan, sendcan, num_pandas=num_pandas) car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, num_pandas=num_pandas) @@ -113,10 +115,11 @@ def fingerprint(logcan, sendcan, num_pandas): cloudlog.event("Malformed VIN", vin=vin, error=True) vin = VIN_UNKNOWN cloudlog.warning("VIN %s", vin) - - params = Params() params.put("CarVin", vin) - disable_obd_multiplexing(params) + + # disable OBD multiplexing for potential ECU knockouts + set_obd_multiplexing(params, False) + params.put_bool("FirmwareQueryDone", True) finger = gen_empty_fingerprint() candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1 diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 9f00484f34..8799f073c9 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -50,6 +50,7 @@ class CarControllerParams: self.STEER_DELTA_DOWN = 3 self.STEER_MAX = 261 # higher than this faults the EPS + STEER_THRESHOLD = 120 RAM_DT = {CAR.RAM_1500, } @@ -62,6 +63,7 @@ class ChryslerCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC)" harness: Enum = Harness.fca + CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { CAR.PACIFICA_2017_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2017-18"), CAR.PACIFICA_2018_HYBRID: None, # same platforms @@ -73,10 +75,10 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { ], CAR.JEEP_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), CAR.JEEP_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), - CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-22", harness=Harness.ram), + CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-23", harness=Harness.ram), CAR.RAM_HD: [ ChryslerCarInfo("Ram 2500 2020-22", harness=Harness.ram), - ChryslerCarInfo("Ram 3500 2020-22", harness=Harness.ram), + ChryslerCarInfo("Ram 3500 2019-22", harness=Harness.ram), ], } @@ -180,79 +182,153 @@ FW_QUERY_CONFIG = FwQueryConfig( ) FW_VERSIONS = { + CAR.JEEP_CHEROKEE_2019: { + (Ecu.combinationMeter, 0x742, None): [ + b'68402971AD', + ], + (Ecu.srs, 0x744, None): [ + b'68355363AB', + ], + (Ecu.abs, 0x747, None): [ + b'68408639AD', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'68456722AC', + ], + (Ecu.eps, 0x75A, None): [ + b'68453431AA', + ], + (Ecu.engine, 0x7e0, None): [ + b'05035674AB ', + ], + (Ecu.transmission, 0x7e1, None): [ + b'05035707AA', + ], + }, + CAR.RAM_1500: { (Ecu.combinationMeter, 0x742, None): [ - b'68294063AH', + b'68294051AG', + b'68294051AI', + b'68294052AG', b'68294063AG', + b'68294063AH', + b'68294063AI', + b'68434846AC', + b'68434858AC', b'68434860AC', - b'68527375AD', b'68453503AC', + b'68453505AC', + b'68453511AC', + b'68453513AD', + b'68453514AD', + b'68510283AG', + b'68527375AD', ], (Ecu.srs, 0x744, None): [ + b'68428609AB', b'68441329AB', + b'68473844AB', b'68490898AA', - b'68428609AB', b'68500728AA', + b'68615033AA', ], (Ecu.abs, 0x747, None): [ - b'68432418AD', + b'68292406AH', b'68432418AB', + b'68432418AD', + b'68436004AD', b'68436004AE', + b'68438454AC', b'68438454AD', - b'68436004AD', + b'68438456AE', + b'68438456AF', b'68535469AB', - b'68438454AC', + b'68535470AC', + b'68586307AB', ], (Ecu.fwdRadar, 0x753, None): [ - b'68320950AL', + b'04672892AB', + b'04672932AB', + b'68320950AH', + b'68320950AI', b'68320950AJ', + b'68320950AL', + b'68320950AM', b'68454268AB', - b'68475160AG', - b'04672892AB', b'68475160AE', + b'68475160AF', + b'68475160AG', ], (Ecu.eps, 0x75A, None): [ + b'68273275AF', b'68273275AG', + b'68312176AE', + b'68312176AG', + b'68440789AC', + b'68466110AB', b'68469901AA', + b'68522583AB', + b'68522585AB', b'68552788AA', + b'68552790AA', + b'68585112AB', ], (Ecu.engine, 0x7e0, None): [ + b'05036065AE ', + b'05036066AE ', + b'68378701AI ', + b'68378758AM ', b'68448163AJ', + b'68448165AK', b'68500630AD', + b'68500630AE', b'68539650AD', - b'68378758AM ', ], (Ecu.transmission, 0x7e1, None): [ b'68360078AL', - b'68384328AD', - b'68360085AL', + b'68360080AM', b'68360081AM', - b'68502994AD', + b'68360085AL', + b'68384328AD', + b'68384332AD', b'68445533AB', - b'68540431AB', b'68484467AC', + b'68502994AD', + b'68540431AB', ], }, CAR.RAM_HD: { (Ecu.combinationMeter, 0x742, None): [ b'68361606AH', + b'68437735AC', b'68492693AD', + b'68525485AB', + b'68525487AB', + b'68525498AB', ], (Ecu.srs, 0x744, None): [ b'68399794AC', b'68428503AA', b'68428505AA', + b'68428507AA', ], (Ecu.abs, 0x747, None): [ b'68334977AH', + b'68455481AC', + b'68504022AA', b'68504022AB', - b'68530686AB', b'68504022AC', + b'68530686AB', + b'68530686AC', ], (Ecu.fwdRadar, 0x753, None): [ b'04672895AB', b'56029827AG', + b'56029827AH', + b'68462657AE', + b'68484694AD', b'68484694AE', ], (Ecu.eps, 0x761, None): [ @@ -260,7 +336,13 @@ FW_VERSIONS = { b'68507906AB', ], (Ecu.engine, 0x7e0, None): [ + b'52370131AF', + b'52370231AF', + b'52370231AG', + b'52370931CT', + b'52401032AE', b'52421132AF', + b'68527616AD ', b'M2370131MB', b'M2421132MB', ], diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py index e5d550fac8..86bae91b06 100755 --- a/selfdrive/car/ecu_addrs.py +++ b/selfdrive/car/ecu_addrs.py @@ -9,6 +9,8 @@ from selfdrive.car import make_can_msg from selfdrive.boardd.boardd import can_list_to_can_capnp from system.swaglog import cloudlog +EcuAddrBusType = Tuple[int, Optional[int], int] + def make_tester_present_msg(addr, bus, subaddr=None): dat = [0x02, SERVICE_TYPE.TESTER_PRESENT, 0x0] @@ -33,16 +35,16 @@ def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subadd return False -def get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> Set[Tuple[int, Optional[int], int]]: +def get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> Set[EcuAddrBusType]: addr_list = [0x700 + i for i in range(256)] + [0x18da00f1 + (i << 8) for i in range(256)] - queries: Set[Tuple[int, Optional[int], int]] = {(addr, None, bus) for addr in addr_list} + queries: Set[EcuAddrBusType] = {(addr, None, bus) for addr in addr_list} responses = queries return get_ecu_addrs(logcan, sendcan, queries, responses, timeout=timeout, debug=debug) -def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, queries: Set[Tuple[int, Optional[int], int]], - responses: Set[Tuple[int, Optional[int], int]], timeout: float = 1, debug: bool = False) -> Set[Tuple[int, Optional[int], int]]: - ecu_responses: Set[Tuple[int, Optional[int], int]] = set() # set((addr, subaddr, bus),) +def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, queries: Set[EcuAddrBusType], + responses: Set[EcuAddrBusType], timeout: float = 1, debug: bool = False) -> Set[EcuAddrBusType]: + ecu_responses: Set[EcuAddrBusType] = set() # set((addr, subaddr, bus),) try: msgs = [make_tester_present_msg(addr, bus, subaddr) for addr, subaddr, bus in queries] diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index dd3b19f6de..7ae9bee404 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import capnp +import copy from dataclasses import dataclass, field import struct from typing import Dict, List, Optional, Tuple @@ -57,10 +58,12 @@ class Request: whitelist_ecus: List[int] = field(default_factory=list) rx_offset: int = 0x8 bus: int = 1 + # Whether this query should be run on the first auxiliary panda (CAN FD cars for example) + auxiliary: bool = False # FW responses from these queries will not be used for fingerprinting logging: bool = False - # These requests are done once OBD multiplexing is disabled, after all others - non_obd: bool = False + # boardd toggles OBD multiplexing on/off as needed + obd_multiplexing: bool = True @dataclass @@ -71,3 +74,10 @@ class FwQueryConfig: non_essential_ecus: Dict[capnp.lib.capnp._EnumModule, List[str]] = field(default_factory=dict) # Ecus added for data collection, not to be fingerprinted on extra_ecus: List[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]]] = field(default_factory=list) + + def __post_init__(self): + for i in range(len(self.requests)): + if self.requests[i].auxiliary: + new_request = copy.deepcopy(self.requests[i]) + new_request.bus += 4 + self.requests.append(new_request) diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 8092ac0b76..f85b7f6b7d 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 from collections import defaultdict -from typing import Any, Optional, Set, Tuple +from typing import Any, Dict, List, Set from tqdm import tqdm import panda.python.uds as uds from cereal import car from common.params import Params -from selfdrive.car.ecu_addrs import get_ecu_addrs +from selfdrive.car.ecu_addrs import EcuAddrBusType, get_ecu_addrs from selfdrive.car.interfaces import get_interface_attr from selfdrive.car.fingerprints import FW_VERSIONS from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery @@ -19,7 +19,7 @@ FW_QUERY_CONFIGS = get_interface_attr('FW_QUERY_CONFIG', ignore_none=True) VERSIONS = get_interface_attr('FW_VERSIONS', ignore_none=True) MODEL_TO_BRAND = {c: b for b, e in VERSIONS.items() for c in e} -REQUESTS = [(brand, r) for brand, config in FW_QUERY_CONFIGS.items() for r in config.requests] +REQUESTS = [(brand, config, r) for brand, config in FW_QUERY_CONFIGS.items() for r in config.requests] def chunks(l, n=128): @@ -39,6 +39,8 @@ def build_fw_dict(fw_versions, filter_brand=None): def get_brand_addrs(): brand_addrs = defaultdict(set) for brand, cars in VERSIONS.items(): + # Add ecus in database + extra ecus to match against + brand_addrs[brand] |= {(addr, sub_addr) for _, addr, sub_addr in FW_QUERY_CONFIGS[brand].extra_ecus} for fw in cars.values(): brand_addrs[brand] |= {(addr, sub_addr) for _, addr, sub_addr in fw.keys()} return brand_addrs @@ -146,38 +148,43 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True): return True, set() -def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[Tuple[int, Optional[int], int]]: - queries = list() - parallel_queries = list() +def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[EcuAddrBusType]: + params = Params() + # queries are split by OBD multiplexing mode + queries: Dict[bool, List[List[EcuAddrBusType]]] = {True: [], False: []} + parallel_queries: Dict[bool, List[EcuAddrBusType]] = {True: [], False: []} responses = set() - for brand, r in REQUESTS: + for brand, config, r in REQUESTS: # Skip query if no panda available if r.bus > num_pandas * 4 - 1: continue for brand_versions in VERSIONS[brand].values(): - for ecu_type, addr, sub_addr in brand_versions: + for ecu_type, addr, sub_addr in list(brand_versions) + config.extra_ecus: # Only query ecus in whitelist if whitelist is not empty if len(r.whitelist_ecus) == 0 or ecu_type in r.whitelist_ecus: a = (addr, sub_addr, r.bus) # Build set of queries if sub_addr is None: - if a not in parallel_queries: - parallel_queries.append(a) + if a not in parallel_queries[r.obd_multiplexing]: + parallel_queries[r.obd_multiplexing].append(a) else: # subaddresses must be queried one by one - if [a] not in queries: - queries.append([a]) + if [a] not in queries[r.obd_multiplexing]: + queries[r.obd_multiplexing].append([a]) # Build set of expected responses to filter response_addr = uds.get_rx_addr_for_tx_addr(addr, r.rx_offset) responses.add((response_addr, sub_addr, r.bus)) - queries.insert(0, parallel_queries) + for obd_multiplexing in queries: + queries[obd_multiplexing].insert(0, parallel_queries[obd_multiplexing]) ecu_responses = set() - for query in queries: - ecu_responses.update(get_ecu_addrs(logcan, sendcan, set(query), responses, timeout=0.1)) + for obd_multiplexing in queries: + set_obd_multiplexing(params, obd_multiplexing) + for query in queries[obd_multiplexing]: + ecu_responses.update(get_ecu_addrs(logcan, sendcan, set(query), responses, timeout=0.1)) return ecu_responses @@ -185,9 +192,9 @@ def get_brand_ecu_matches(ecu_rx_addrs): """Returns dictionary of brands and matches with ECUs in their FW versions""" brand_addrs = get_brand_addrs() - brand_matches = {brand: set() for brand, _ in REQUESTS} + brand_matches = {brand: set() for brand, _, _ in REQUESTS} - brand_rx_offsets = set((brand, r.rx_offset) for brand, r in REQUESTS) + brand_rx_offsets = set((brand, r.rx_offset) for brand, _, r in REQUESTS) for addr, sub_addr, _ in ecu_rx_addrs: # Since we can't know what request an ecu responded to, add matches for all possible rx offsets for brand, rx_offset in brand_rx_offsets: @@ -198,13 +205,13 @@ def get_brand_ecu_matches(ecu_rx_addrs): return brand_matches -def disable_obd_multiplexing(params): - if not params.get_bool("ObdMultiplexingDisabled"): - params.put_bool("FirmwareObdQueryDone", True) - - cloudlog.warning("Waiting for OBD multiplexing to be disabled") - params.get_bool("ObdMultiplexingDisabled", block=True) - cloudlog.warning("OBD multiplexing disabled") +def set_obd_multiplexing(params: Params, obd_multiplexing: bool): + if params.get_bool("ObdMultiplexingEnabled") != obd_multiplexing: + cloudlog.warning(f"Setting OBD multiplexing to {obd_multiplexing}") + params.remove("ObdMultiplexingChanged") + params.put_bool("ObdMultiplexingEnabled", obd_multiplexing) + params.get_bool("ObdMultiplexingChanged", block=True) + cloudlog.warning("OBD multiplexing set successfully") def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False): @@ -212,7 +219,6 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pand all_car_fw = [] brand_matches = get_brand_ecu_matches(ecu_rx_addrs) - matched_brand: Optional[str] = None for brand in sorted(brand_matches, key=lambda b: len(brand_matches[b]), reverse=True): car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, debug=debug, progress=progress) @@ -220,21 +226,14 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pand # Try to match using FW returned from this brand only matches = match_fw_to_car_exact(build_fw_dict(car_fw)) if len(matches) == 1: - matched_brand = brand break - disable_obd_multiplexing(Params()) - - # Do non-OBD queries for matched brand, or all if no match is found - for brand in FW_QUERY_CONFIGS.keys(): - if brand == matched_brand or matched_brand is None: - all_car_fw.extend(get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, obd_multiplexed=False, debug=debug, progress=progress)) - return all_car_fw -def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, obd_multiplexed=True, debug=False, progress=False): +def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, debug=False, progress=False): versions = VERSIONS.copy() + params = Params() # Each brand can define extra ECUs to query for data collection for brand, config in FW_QUERY_CONFIGS.items(): @@ -250,19 +249,15 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, # ECUs using a subaddress need be queried one by one, the rest can be done in parallel addrs = [] parallel_addrs = [] - logging_addrs = [] ecu_types = {} for brand, brand_versions in versions.items(): - for candidate, ecu in brand_versions.items(): + for ecu in brand_versions.values(): for ecu_type, addr, sub_addr in ecu.keys(): a = (brand, addr, sub_addr) if a not in ecu_types: ecu_types[a] = ecu_type - if a not in logging_addrs and candidate == "debug": - logging_addrs.append(a) - if sub_addr is None: if a not in parallel_addrs: parallel_addrs.append(a) @@ -274,16 +269,17 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, # Get versions and build capnp list to put into CarParams car_fw = [] - requests = [(brand, r) for brand, r in REQUESTS if query_brand is None or brand == query_brand] + requests = [(brand, config, r) for brand, config, r in REQUESTS if query_brand is None or brand == query_brand] for addr in tqdm(addrs, disable=not progress): for addr_chunk in chunks(addr): - for brand, r in requests: + for brand, config, r in requests: # Skip query if no panda available if r.bus > num_pandas * 4 - 1: continue - # Or if request is not designated for current multiplexing mode - elif r.non_obd == obd_multiplexed: - continue + + # Toggle OBD multiplexing for each request + if r.bus % 4 == 1: + set_obd_multiplexing(params, r.obd_multiplexing) try: addrs = [(a, s) for (b, a, s) in addr_chunk if b in (brand, 'any') and @@ -294,15 +290,15 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, for (tx_addr, sub_addr), version in query.get_data(timeout).items(): f = car.CarParams.CarFw.new_message() - ecu_key = (brand, tx_addr, sub_addr) - f.ecu = ecu_types.get(ecu_key, Ecu.unknown) + f.ecu = ecu_types.get((brand, tx_addr, sub_addr), Ecu.unknown) f.fwVersion = version f.address = tx_addr f.responseAddress = uds.get_rx_addr_for_tx_addr(tx_addr, r.rx_offset) f.request = r.request f.brand = brand f.bus = r.bus - f.logging = r.logging or ecu_key in logging_addrs + f.logging = r.logging or (f.ecu, tx_addr, sub_addr) in config.extra_ecus + f.obdMultiplexing = r.obd_multiplexing if sub_addr is not None: f.subAddress = sub_addr diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 7e6e71c642..6e2797ce24 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 from cereal import car -from math import fabs +from math import fabs, exp from panda import Panda from common.conversions import Conversions as CV from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus -from selfdrive.car.interfaces import CarInterfaceBase +from selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD +from selfdrive.controls.lib.drive_helpers import get_friction ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -44,6 +45,29 @@ class CarInterface(CarInterfaceBase): else: return CarInterfaceBase.get_steer_feedforward_default + @staticmethod + def torque_from_lateral_accel_bolt(lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning, + lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float: + friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) + + def sig(val): + return 1 / (1 + exp(-val)) - 0.5 + + # The "lat_accel vs torque" relationship is assumed to be the sum of "sigmoid + linear" curves + # An important thing to consider is that the slope at 0 should be > 0 (ideally >1) + # This has big effect on the stability about 0 (noise when going straight) + # ToDo: To generalize to other GMs, explore tanh function as the nonlinear + a, b, c, _ = [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178] # weights computed offline + + steer_torque = (sig(lateral_accel_value * a) * b) + (lateral_accel_value * c) + return float(steer_torque) + friction + + def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: + if self.CP.carFingerprint == CAR.BOLT_EUV: + return self.torque_from_lateral_accel_bolt + else: + return self.torque_from_lateral_accel_linear + @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "gm" @@ -78,7 +102,6 @@ class CarInterface(CarInterfaceBase): ret.stoppingDecelRate = 2.0 # reach brake quickly after enabling ret.vEgoStopping = 0.25 ret.vEgoStarting = 0.25 - ret.longitudinalActuatorDelayUpperBound = 0.5 if experimental_long: ret.pcmCruise = False @@ -113,6 +136,7 @@ class CarInterface(CarInterfaceBase): ret.steerLimitTimer = 0.4 ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz + ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking if candidate == CAR.VOLT: ret.mass = 1607. + STD_CARGO_KG @@ -148,7 +172,13 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 14.4 # end to end is 13.46 ret.centerToFront = ret.wheelbase * 0.4 ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_acadia() - ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking + + elif candidate == CAR.BUICK_LACROSSE: + ret.mass = 1712. + STD_CARGO_KG + ret.wheelbase = 2.91 + ret.steerRatio = 15.8 + ret.centerToFront = ret.wheelbase * 0.4 # wild guess + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.BUICK_REGAL: ret.mass = 3779. * CV.LB_TO_KG + STD_CARGO_KG # (3849+3708)/2 @@ -168,7 +198,6 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.95 # 116 inches in meters ret.steerRatio = 17.3 ret.centerToFront = ret.wheelbase * 0.5 - ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.ESCALADE_ESV: diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 2ac418b629..1b814e00b2 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -14,8 +14,8 @@ class CarControllerParams: STEER_STEP = 3 # Active control frames per command (~33hz) INACTIVE_STEER_STEP = 10 # Inactive control frames per command (10hz) STEER_DELTA_UP = 10 # Delta rates require review due to observed EPS weakness - STEER_DELTA_DOWN = 25 - STEER_DRIVER_ALLOWANCE = 50 + STEER_DELTA_DOWN = 15 + STEER_DRIVER_ALLOWANCE = 65 STEER_DRIVER_MULTIPLIER = 4 STEER_DRIVER_FACTOR = 100 NEAR_STOP_BRAKE_PHASE = 0.5 # m/s @@ -66,6 +66,7 @@ class CAR: CADILLAC_ATS = "CADILLAC ATS Premium Performance 2018" MALIBU = "CHEVROLET MALIBU PREMIER 2017" ACADIA = "GMC ACADIA DENALI 2018" + BUICK_LACROSSE = "BUICK LACROSSE 2017" BUICK_REGAL = "BUICK REGAL ESSENCE 2018" ESCALADE = "CADILLAC ESCALADE 2017" ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016" @@ -99,6 +100,7 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.CADILLAC_ATS: GMCarInfo("Cadillac ATS Premium Performance 2018"), CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"), CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), + CAR.BUICK_LACROSSE: GMCarInfo("Buick LaCrosse 2017-19", "Driver Confidence Package 2"), 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"), @@ -156,6 +158,11 @@ FINGERPRINTS = { { 170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 368: 8, 381: 2, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 5, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 4, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 761: 7, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 5, 844: 8, 853: 8, 866: 4, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7 }], + CAR.BUICK_LACROSSE: [ + # LaCrosse Premium AWD 2017 + { + 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 5, 707: 8, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 872: 1, 882: 8, 890: 1, 892: 2, 893: 1, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1916: 7, 1918: 7, 1919: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 + }], CAR.BUICK_REGAL : [ # Regal TourX Essence w/ ACC 2018 { diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index 4dc1dc8131..347c16c86f 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -1,7 +1,6 @@ from collections import namedtuple from cereal import car -from common.conversions import Conversions as CV from common.numpy_fast import clip, interp from common.realtime import DT_CTRL from opendbc.can.packer import CANPacker @@ -117,6 +116,7 @@ class CarController: self.brake_last = 0. self.apply_brake_last = 0 self.last_pump_ts = 0. + self.stopping_counter = 0 self.accel = 0.0 self.speed = 0.0 @@ -127,7 +127,8 @@ class CarController: def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl - hud_v_cruise = hud_control.setSpeed * CV.MS_TO_KPH if hud_control.speedVisible else 255 + conversion = hondacan.get_cruise_speed_conversion(self.CP.carFingerprint, CS.is_metric) + hud_v_cruise = hud_control.setSpeed / conversion if hud_control.speedVisible else 255 pcm_cancel_cmd = CC.cruiseControl.cancel if CC.longActive: @@ -161,7 +162,7 @@ class CarController: can_sends = [] # tester present - w/ no response (keeps radar disabled) - if self.CP.carFingerprint in HONDA_BOSCH and self.CP.openpilotLongitudinalControl: + if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and self.CP.openpilotLongitudinalControl: if self.frame % 10 == 0: can_sends.append((0x18DAB0F1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 1)) @@ -217,8 +218,9 @@ class CarController: self.gas = interp(accel, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V) stopping = actuators.longControlState == LongCtrlState.stopping + self.stopping_counter = self.stopping_counter + 1 if stopping else 0 can_sends.extend(hondacan.create_acc_commands(self.packer, CC.enabled, CC.longActive, self.accel, self.gas, - stopping, self.CP.carFingerprint)) + self.stopping_counter, self.CP.carFingerprint)) else: apply_brake = clip(self.brake_last - wind_brake, 0.0, 1.0) apply_brake = int(clip(apply_brake * self.params.NIDEC_BRAKE_MAX, 0, self.params.NIDEC_BRAKE_MAX - 1)) diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index 16880d1b1f..8189800368 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -5,7 +5,7 @@ from common.conversions import Conversions as CV from common.numpy_fast import interp from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser -from selfdrive.car.honda.hondacan import get_pt_bus +from selfdrive.car.honda.hondacan import get_cruise_speed_conversion, get_pt_bus from selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL, HONDA_BOSCH_RADARLESS from selfdrive.car.interfaces import CarStateBase @@ -246,8 +246,7 @@ class CarState(CarStateBase): ret.cruiseState.nonAdaptive = acc_hud["CRUISE_CONTROL_LABEL"] != 0 ret.cruiseState.standstill = acc_hud["CRUISE_SPEED"] == 252. - # on certain cars, CRUISE_SPEED changes to imperial with car's unit setting - conversion = CV.MPH_TO_MS if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS and not self.is_metric else CV.KPH_TO_MS + conversion = get_cruise_speed_conversion(self.CP.carFingerprint, self.is_metric) # On set, cruise set speed pulses between 254~255 and the set speed prev is set to avoid this. ret.cruiseState.speed = self.v_cruise_pcm_prev if acc_hud["CRUISE_SPEED"] > 160.0 else acc_hud["CRUISE_SPEED"] * conversion self.v_cruise_pcm_prev = ret.cruiseState.speed diff --git a/selfdrive/car/honda/hondacan.py b/selfdrive/car/honda/hondacan.py index 17681444af..1fe0a13767 100644 --- a/selfdrive/car/honda/hondacan.py +++ b/selfdrive/car/honda/hondacan.py @@ -21,6 +21,11 @@ def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False): return 0 +def get_cruise_speed_conversion(car_fingerprint: str, is_metric: bool) -> float: + # on certain cars, CRUISE_SPEED changes to imperial with car's unit setting + return CV.MPH_TO_MS if car_fingerprint in HONDA_BOSCH_RADARLESS and not is_metric else CV.KPH_TO_MS + + def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw, car_fingerprint, stock_brake): # TODO: do we loose pressure if we keep pump off for long? brakelights = apply_brake > 0 @@ -46,7 +51,7 @@ def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_ return packer.make_can_msg("BRAKE_COMMAND", bus, values) -def create_acc_commands(packer, enabled, active, accel, gas, stopping, car_fingerprint): +def create_acc_commands(packer, enabled, active, accel, gas, stopping_counter, car_fingerprint): commands = [] bus = get_pt_bus(car_fingerprint) min_gas_accel = CarControllerParams.BOSCH_GAS_LOOKUP_BP[0] @@ -55,30 +60,39 @@ def create_acc_commands(packer, enabled, active, accel, gas, stopping, car_finge gas_command = gas if active and accel > min_gas_accel else -30000 accel_command = accel if active else 0 braking = 1 if active and accel < min_gas_accel else 0 - standstill = 1 if active and stopping else 0 - standstill_release = 1 if active and not stopping else 0 + standstill = 1 if active and stopping_counter > 0 else 0 + standstill_release = 1 if active and stopping_counter == 0 else 0 + # common ACC_CONTROL values acc_control_values = { - # setting CONTROL_ON causes car to set POWERTRAIN_DATA->ACC_STATUS = 1 - "CONTROL_ON": control_on, - "GAS_COMMAND": gas_command, # used for gas - "ACCEL_COMMAND": accel_command, # used for brakes - "BRAKE_LIGHTS": braking, - "BRAKE_REQUEST": braking, - "STANDSTILL": standstill, - "STANDSTILL_RELEASE": standstill_release, + 'ACCEL_COMMAND': accel_command, + 'STANDSTILL': standstill, } - commands.append(packer.make_can_msg("ACC_CONTROL", bus, acc_control_values)) - acc_control_on_values = { - "SET_TO_3": 0x03, - "CONTROL_ON": enabled, - "SET_TO_FF": 0xff, - "SET_TO_75": 0x75, - "SET_TO_30": 0x30, - } - commands.append(packer.make_can_msg("ACC_CONTROL_ON", bus, acc_control_on_values)) + if car_fingerprint in HONDA_BOSCH_RADARLESS: + acc_control_values.update({ + "CONTROL_ON": enabled, + "IDLESTOP_ALLOW": stopping_counter > 200, # allow idle stop after 4 seconds (50 Hz) + }) + else: + acc_control_values.update({ + # setting CONTROL_ON causes car to set POWERTRAIN_DATA->ACC_STATUS = 1 + "CONTROL_ON": control_on, + "GAS_COMMAND": gas_command, # used for gas + "BRAKE_LIGHTS": braking, + "BRAKE_REQUEST": braking, + "STANDSTILL_RELEASE": standstill_release, + }) + acc_control_on_values = { + "SET_TO_3": 0x03, + "CONTROL_ON": enabled, + "SET_TO_FF": 0xff, + "SET_TO_75": 0x75, + "SET_TO_30": 0x30, + } + commands.append(packer.make_can_msg("ACC_CONTROL_ON", bus, acc_control_on_values)) + commands.append(packer.make_can_msg("ACC_CONTROL", bus, acc_control_values)) return commands diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index d3cf9fa891..3a40f03dd9 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -37,13 +37,11 @@ class CarInterface(CarInterfaceBase): if candidate in HONDA_BOSCH: ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaBosch)] ret.radarUnavailable = True - - if candidate not in HONDA_BOSCH_RADARLESS: - # Disable the radar and let openpilot control longitudinal - # WARNING: THIS DISABLES AEB! - ret.experimentalLongitudinalAvailable = True - ret.openpilotLongitudinalControl = experimental_long - + # Disable the radar and let openpilot control longitudinal + # WARNING: THIS DISABLES AEB! + # If Bosch radarless, this blocks ACC messages from the camera + ret.experimentalLongitudinalAvailable = True + ret.openpilotLongitudinalControl = experimental_long ret.pcmCruise = not ret.openpilotLongitudinalControl else: ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaNidec)] @@ -75,6 +73,8 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kpV = [0.25] ret.longitudinalTuning.kiV = [0.05] ret.longitudinalActuatorDelayUpperBound = 0.5 # s + if candidate in HONDA_BOSCH_RADARLESS: + ret.stopAccel = CarControllerParams.BOSCH_ACCEL_MIN # stock uses -4.0 m/s^2 once stopped but limited by safety model else: # default longitudinal tuning for all hondas ret.longitudinalTuning.kpBP = [0., 5., 35.] diff --git a/selfdrive/car/honda/tests/__init__.py b/selfdrive/car/honda/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 0737bea147..009a549afd 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -186,7 +186,7 @@ FW_QUERY_CONFIG = FwQueryConfig( [StdQueries.UDS_VERSION_RESPONSE], bus=1, logging=True, - non_obd=True, + obd_multiplexing=False, ), ], extra_ecus=[ diff --git a/selfdrive/car/hyundai/tests/__init__.py b/selfdrive/car/hyundai/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index cf0143543a..7fcc5b9140 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -224,7 +224,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", "https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e), ], - CAR.KIA_SORENTO_4TH_GEN: HyundaiCarInfo("Kia Sorento 2022-23", harness=Harness.hyundai_k), + CAR.KIA_SORENTO_4TH_GEN: HyundaiCarInfo("Kia Sorento 2021-23", harness=Harness.hyundai_k), CAR.KIA_SORENTO_PHEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", harness=Harness.hyundai_a), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", harness=Harness.hyundai_n), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), @@ -335,10 +335,15 @@ FINGERPRINTS = { HYUNDAI_VERSION_REQUEST_LONG = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(0xf100) # Long description + +HYUNDAI_VERSION_REQUEST_ALT = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(0xf110) # Alt long description + HYUNDAI_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) + \ p16(0xf100) + HYUNDAI_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) FW_QUERY_CONFIG = FwQueryConfig( @@ -355,22 +360,46 @@ FW_QUERY_CONFIG = FwQueryConfig( [HYUNDAI_VERSION_RESPONSE], whitelist_ecus=[Ecu.engine, Ecu.transmission, Ecu.eps, Ecu.abs, Ecu.fwdRadar], ), - # CAN-FD queries (camera) + + # CAN-FD queries (from camera) + # TODO: combine shared whitelists with CAN requests Request( [HYUNDAI_VERSION_REQUEST_LONG], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.cornerRadar], - bus=4, + whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.cornerRadar, Ecu.hvac], + bus=0, + auxiliary=True, ), Request( [HYUNDAI_VERSION_REQUEST_LONG], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.fwdCamera, Ecu.adas, Ecu.cornerRadar], - bus=5, + whitelist_ecus=[Ecu.fwdCamera, Ecu.adas, Ecu.cornerRadar, Ecu.hvac], + bus=1, + auxiliary=True, + obd_multiplexing=False, + ), + + # CAN-FD debugging queries + Request( + [HYUNDAI_VERSION_REQUEST_ALT], + [HYUNDAI_VERSION_RESPONSE], + whitelist_ecus=[Ecu.parkingAdas], + bus=0, + auxiliary=True, + ), + Request( + [HYUNDAI_VERSION_REQUEST_ALT], + [HYUNDAI_VERSION_RESPONSE], + whitelist_ecus=[Ecu.parkingAdas], + bus=1, + auxiliary=True, + obd_multiplexing=False, ), ], extra_ecus=[ (Ecu.adas, 0x730, None), # ADAS Driving ECU on HDA2 platforms + (Ecu.parkingAdas, 0x7b1, None), # ADAS Parking ECU (may exist on all platforms) + (Ecu.hvac, 0x7b3, None), # HVAC Control Assembly (Ecu.cornerRadar, 0x7b7, None), ], ) @@ -1025,11 +1054,23 @@ FW_VERSIONS = { ], }, CAR.GENESIS_G70: { - (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', ], - (Ecu.engine, 0x7e0, None): [b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.eps, 0x7d4, None): [b'\xf1\x00IK MDPS R 1.00 1.06 57700-G9420 4I4VL106', ], - (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920', ], - (Ecu.transmission, 0x7e1, None): [b'\xf1\x87VDJLT17895112DN4\x88fVf\x99\x88\x88\x88\x87fVe\x88vhwwUFU\x97eFex\x99\xff\xb7\x82\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', + b'\xf1\x00IK__ SCC F-CUP 1.00 1.01 96400-G9100 ', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00IK MDPS R 1.00 1.06 57700-G9420 4I4VL106', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', + b'\xf1\x87VDJLT17895112DN4\x88fVf\x99\x88\x88\x88\x87fVe\x88vhwwUFU\x97eFex\x99\xff\xb7\x82\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', + ], }, CAR.GENESIS_G70_2020: { (Ecu.eps, 0x7d4, None): [ @@ -1444,25 +1485,29 @@ FW_VERSIONS = { b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930', b'\xf1\000CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.07 99210-AA000 220426', + b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.08 99210-AA000 220728', ], (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\000CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', + b'\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', b'\xf1\x8799110BY000\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103', b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', b'\xf1\x8756310/BY050\xf1\000CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', + b'\xf1\x00CN7 MDPS C 1.00 1.04 56310BY050\x00 4CNHC104', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\000\000\000\000', b'\xf1\x816U3K3051\000\000\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', + b'\xf1\x006U3L0_C2\x00\x006U3K9051\x00\x00HCN0G16NS1\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6G8051\x00\x00\x00\x00\x00\x00\x00\x00', ] }, CAR.KONA_HEV: { @@ -1552,6 +1597,8 @@ FW_VERSIONS = { b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328', b'\xf1\x00CV1 MFC AT EUR RHD 1.00 1.00 99210-CV100 220630', b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV100 220630', + b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.04 99210-CV000 210823', + b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.06 99210-CV000 220328', ], }, CAR.IONIQ_5: { @@ -1562,6 +1609,7 @@ FW_VERSIONS = { b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', + b'\xf1\x00NE1 MFC AT KOR LHD 1.00 1.05 99211-GI010 220614', b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.01 99211-GI010 211007', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007', b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.02 99211-GI010 211206', @@ -1570,9 +1618,11 @@ FW_VERSIONS = { }, CAR.TUCSON_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9240 14T', ], (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', ], }, @@ -1628,6 +1678,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU100 211215', b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU000 211215', + b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.03 99211-CU000 221118', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00JW1_ RDR ----- 1.00 1.00 99110-CU000 ', @@ -1636,14 +1687,17 @@ FW_VERSIONS = { CAR.KIA_SORENTO_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.05 99210-R5000 210623', + b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.03 99210-R5000 200903', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ', + b'\xf1\x00MQ4_ SCC F-CUP 1.00 1.06 99110-P2000 ', ], }, CAR.KIA_NIRO_HEV_2ND_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.08 99211-AT000 220531', + b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.09 99211-AT000 220801', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ', diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 249818369c..cf6d7280fa 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -8,10 +8,10 @@ from cereal import car from common.basedir import BASEDIR from common.conversions import Conversions as CV from common.kalman.simple_kalman import KF1D -from common.numpy_fast import clip, interp +from common.numpy_fast import clip from common.realtime import DT_CTRL from selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness -from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_center_deadzone +from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, get_friction from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.vehicle_model import VehicleModel @@ -131,15 +131,11 @@ class CarInterfaceBase(ABC): return self.get_steer_feedforward_default @staticmethod - def torque_from_lateral_accel_linear(lateral_accel_value, torque_params, lateral_accel_error, lateral_accel_deadzone, friction_compensation): + def torque_from_lateral_accel_linear(lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning, + lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float: # The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction) - friction_interp = interp( - apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone), - [-FRICTION_THRESHOLD, FRICTION_THRESHOLD], - [-torque_params.friction, torque_params.friction] - ) - friction = friction_interp if friction_compensation else 0.0 - return (lateral_accel_value / torque_params.latAccelFactor) + friction + friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) + return (lateral_accel_value / float(torque_params.latAccelFactor)) + friction def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: return self.torque_from_lateral_accel_linear diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index d9c658a14c..70f8b5f50d 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -60,7 +60,7 @@ class IsoTpParallelQuery: return msgs def _drain_rx(self): - messaging.drain_sock(self.logcan) + messaging.drain_sock_raw(self.logcan) self.msg_buffer = defaultdict(list) def _create_isotp_msg(self, tx_addr, sub_addr, rx_addr): diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index e9af828e2b..358cf0d0cd 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -126,18 +126,22 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x707, None): [ b'5SH1BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80', b'5SK0ADB\x04\x18\x00\x00\x00\x00\x00_(5\x07\x9aQ\x00\x00\x00\x80', + b'5SH4BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80', ], (Ecu.abs, 0x740, None): [ b'476605SH1D', b'476605SK2A', + b'476605SD2E', ], (Ecu.eps, 0x742, None): [ b'5SH2A\x99A\x05\x02N123F\x15\x81\x00\x00\x00\x00\x00\x00\x00\x80', b'5SK3A\x99A\x05\x02N123F\x15u\x00\x00\x00\x00\x00\x00\x00\x80', + b'5SH2C\xb7A\x05\x02N123F\x15\xa3\x00\x00\x00\x00\x00\x00\x00\x80', ], (Ecu.gateway, 0x18dad0f1, None): [ b'284U25SH3A', b'284U25SK2D', + b'284U25SF0C', ], }, CAR.XTRAIL: { diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 5c7d3f5e4d..9c4447631d 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -273,9 +273,11 @@ FW_VERSIONS = { b'\xa3 \031\024\000', b'\xa3 \x14\x01', b'\xf1\x00\xbb\r\x05', + b'\xa3 \x18&\x00', ], (Ecu.eps, 0x746, None): [ b'\x8d\xc0\x04\x00', + b'\x8d\xc0\x00\x00', ], (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00e!\x1f@ \x11', @@ -292,6 +294,7 @@ FW_VERSIONS = { b'\xcb\"`p\a', b'\xf1\x00\xa2\x10\n', b'\xcf"`p\x07', + b'\xb6\xa2`A\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\032\xf6B0\000', @@ -299,6 +302,7 @@ FW_VERSIONS = { b'\032\xf6b`\000', b'\x1a\xf6B`\x00', b'\x1a\xf6b0\x00', + b'\x1a\xe6B1\x00', ], }, CAR.FORESTER_PREGLOBAL: { diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 4ca7c0a16c..4dd19d24e3 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -27,6 +27,8 @@ non_tested_cars = [ HYUNDAI.KIA_OPTIMA_H, HONDA.ODYSSEY_CHN, VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter + TOYOTA.RAV4_TSS2_2023, + TOYOTA.RAV4H_TSS2_2023, ] CarTestRoute = namedtuple('CarTestRoute', ['route', 'car_model', 'segment'], defaults=(None,)) @@ -50,6 +52,7 @@ routes = [ CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.ACADIA), CarTestRoute("aa20e335f61ba898|2019-02-05--16-59-04", GM.BUICK_REGAL), + 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("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT), diff --git a/selfdrive/car/tests/test_lateral_limits.py b/selfdrive/car/tests/test_lateral_limits.py index 3efdd7404e..d3e4de1798 100755 --- a/selfdrive/car/tests/test_lateral_limits.py +++ b/selfdrive/car/tests/test_lateral_limits.py @@ -10,7 +10,6 @@ from common.realtime import DT_CTRL from selfdrive.car.car_helpers import interfaces from selfdrive.car.fingerprints import all_known_cars from selfdrive.car.interfaces import get_torque_params -from selfdrive.car.hyundai.values import CAR as HYUNDAI CAR_MODELS = all_known_cars() @@ -22,14 +21,6 @@ MAX_LAT_ACCEL = 3.0 # m/s^2 # jerk is measured over half a second JERK_MEAS_FRAMES = 0.5 / DT_CTRL -# TODO: update the max measured lateral accel for these cars -ABOVE_LIMITS_CARS = [ - HYUNDAI.KONA_EV, - HYUNDAI.KONA_HEV, - HYUNDAI.KONA, - HYUNDAI.KONA_EV_2022, -] - car_model_jerks: DefaultDict[str, Dict[str, float]] = defaultdict(dict) @@ -52,9 +43,6 @@ class TestLateralLimits(unittest.TestCase): if CP.notCar: raise unittest.SkipTest - if CP.carFingerprint in ABOVE_LIMITS_CARS: - raise unittest.SkipTest - CarControllerParams = importlib.import_module(f'selfdrive.car.{CP.carName}.values').CarControllerParams cls.control_params = CarControllerParams(CP) cls.torque_params = get_torque_params(cls.car_model) diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index cc1681bce1..75051316fa 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -7,6 +7,10 @@ NISSAN LEAF 2018 Instrument Cluster: [.nan, 1.5, .nan] NISSAN LEAF 2018: [.nan, 1.5, .nan] NISSAN ROGUE 2019: [.nan, 1.5, .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] TESLA AP2 MODEL S: [.nan, 2.5, .nan] diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index 6f8cfe0ce6..cb423e1d87 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -30,7 +30,7 @@ HYUNDAI IONIQ 5 2022: [3.172929, 2.713050, 0.096019] HYUNDAI IONIQ ELECTRIC LIMITED 2019: [1.7662975472852054, 1.613755614526594, 0.17087579756306276] HYUNDAI IONIQ PHEV 2020: [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] HYUNDAI IONIQ PLUG-IN HYBRID 2019: [2.970807902012267, 1.6312321830002083, 0.1088964990357482] -HYUNDAI KONA ELECTRIC 2019: [3.078814714619148, 3.2961956260770484, 0.12359762054065548] +HYUNDAI KONA ELECTRIC 2019: [3.078814714619148, 2.307336938253934, 0.12359762054065548] HYUNDAI PALISADE 2020: [2.544642494803999, 1.8721703683337008, 0.1301424599248651] HYUNDAI SANTA FE 2019: [3.0787027729757632, 2.6173437483495565, 0.1207019341823945] HYUNDAI SANTA FE HYBRID 2022: [3.501877602644835, 2.729064118456137, 0.10384068104538963] diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index 696d9829d1..5feef12206 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -50,6 +50,7 @@ HONDA CIVIC SEDAN 1.6 DIESEL 2019: HONDA CIVIC (BOSCH) 2019 HONDA E 2020: HONDA CIVIC (BOSCH) 2019 HONDA ODYSSEY CHN 2019: HONDA ODYSSEY 2018 +BUICK LACROSSE 2017: CHEVROLET VOLT PREMIER 2017 BUICK REGAL ESSENCE 2018: CHEVROLET VOLT PREMIER 2017 CADILLAC ESCALADE ESV 2016: CHEVROLET VOLT PREMIER 2017 CADILLAC ATS Premium Performance 2018: CHEVROLET VOLT PREMIER 2017 diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 8004ea9dca..66a7b57f01 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -76,6 +76,11 @@ class CarController: apply_steer_req = 0 self.steer_rate_counter = 0 + # Never actuate with LKA on cars that only support LTA + if self.CP.steerControlType == car.CarParams.SteerControlType.angle: + apply_steer = 0 + apply_steer_req = 0 + # TODO: probably can delete this. CS.pcm_acc_status uses a different signal # than CS.cruiseState.enabled. confirm they're not meaningfully different if not CC.enabled and CS.pcm_acc_status: diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 8b3fd048d9..33a87451e9 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -2,7 +2,8 @@ from cereal import car from common.conversions import Conversions as CV from panda import Panda -from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, UNSUPPORTED_DSU_CAR, CarControllerParams, NO_STOP_TIMER_CAR +from selfdrive.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \ + MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, UNSUPPORTED_DSU_CAR, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase @@ -20,16 +21,22 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.toyota)] ret.safetyConfigs[0].safetyParam = EPS_SCALE[candidate] - if candidate in (CAR.RAV4, CAR.PRIUS_V, CAR.COROLLA, CAR.LEXUS_ESH, CAR.LEXUS_CTH): + # BRAKE_MODULE is on a different address for these cars + if DBC[candidate]["pt"] == "toyota_new_mc_pt_generated": ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_ALT_BRAKE + if candidate in ANGLE_CONTROL_CAR: + ret.dashcamOnly = True + ret.steerControlType = car.CarParams.SteerControlType.angle + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_LTA + else: + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + ret.steerActuatorDelay = 0.12 # Default delay, Prius has larger delay ret.steerLimitTimer = 0.4 ret.stoppingControl = False # Toyota starts braking more when it thinks you want to stop stop_and_go = False - steering_angle_deadzone_deg = 0.0 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) if candidate == CAR.PRIUS: stop_and_go = True @@ -40,9 +47,8 @@ class CarInterface(CarInterfaceBase): # Only give steer angle deadzone to for bad angle sensor prius for fw in car_fw: if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00': - steering_angle_deadzone_deg = 0.2 ret.steerActuatorDelay = 0.25 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg=0.2) elif candidate == CAR.PRIUS_V: stop_and_go = True @@ -102,7 +108,8 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.7983 ret.mass = 3505. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid - elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022): + elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, + CAR.RAV4_TSS2_2023, CAR.RAV4H_TSS2_2023): stop_and_go = True ret.wheelbase = 2.68986 ret.steerRatio = 14.3 diff --git a/selfdrive/car/toyota/tests/__init__.py b/selfdrive/car/toyota/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/car/toyota/tests/test_toyota.py b/selfdrive/car/toyota/tests/test_toyota.py new file mode 100755 index 0000000000..5648f75fe7 --- /dev/null +++ b/selfdrive/car/toyota/tests/test_toyota.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +import unittest + +from selfdrive.car.toyota.values import TSS2_CAR, ANGLE_CONTROL_CAR + + +class TestToyotaInterfaces(unittest.TestCase): + def test_angle_car_set(self): + self.assertTrue(len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index 7e360cc4e1..a78b70608c 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -17,7 +17,7 @@ def create_lta_steer_command(packer, steer, steer_req, raw_cnt): "SETME_X1": 1, "SETME_X3": 3, "PERCENTAGE": 100, - "SETME_X64": 0x64, + "SETME_X64": 0, "ANGLE": 0, "STEER_ANGLE_CMD": steer, "STEER_REQUEST": steer_req, diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index a8e143e80a..f0e846cc54 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -67,8 +67,10 @@ class CAR: RAV4H = "TOYOTA RAV4 HYBRID 2017" RAV4_TSS2 = "TOYOTA RAV4 2019" RAV4_TSS2_2022 = "TOYOTA RAV4 2022" + RAV4_TSS2_2023 = "TOYOTA RAV4 2023" RAV4H_TSS2 = "TOYOTA RAV4 HYBRID 2019" 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" @@ -157,14 +159,16 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ], CAR.RAV4_TSS2: ToyotaCarInfo("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"), CAR.RAV4_TSS2_2022: ToyotaCarInfo("Toyota RAV4 2022"), + CAR.RAV4_TSS2_2023: ToyotaCarInfo("Toyota RAV4 2023"), CAR.RAV4H_TSS2: ToyotaCarInfo("Toyota RAV4 Hybrid 2019-21"), CAR.RAV4H_TSS2_2022: ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"), + CAR.RAV4H_TSS2_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), # Lexus CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "Lexus Safety System+"), - CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18", "Lexus Safety System+"), + CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18"), CAR.LEXUS_ES_TSS2: ToyotaCarInfo("Lexus ES 2019-22"), CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-23", video_link="https://youtu.be/BZ29osRVJeg?t=12"), CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"), @@ -172,7 +176,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.LEXUS_NXH: ToyotaCarInfo("Lexus NX Hybrid 2018-19"), CAR.LEXUS_NX_TSS2: ToyotaCarInfo("Lexus NX 2020-21"), CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020-21"), - CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2017-20"), + CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2018-20"), CAR.LEXUS_RX: [ ToyotaCarInfo("Lexus RX 2016", "Lexus Safety System+"), ToyotaCarInfo("Lexus RX 2017-19"), @@ -323,6 +327,7 @@ FW_VERSIONS = { CAR.AVALON_TSS2: { (Ecu.abs, 0x7b0, None): [ b'\x01F152607240\x00\x00\x00\x00\x00\x00', + b'\x01F152607250\x00\x00\x00\x00\x00\x00', b'\x01F152607280\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ @@ -330,6 +335,7 @@ 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', @@ -527,14 +533,18 @@ FW_VERSIONS = { CAR.CAMRY_TSS2: { (Ecu.eps, 0x7a1, None): [ b'8965B33630\x00\x00\x00\x00\x00\x00', + b'8965B33640\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', ], (Ecu.engine, 0x700, None): [ b'\x018966306Q5000\x00\x00\x00\x00', + b'\x018966306Q9000\x00\x00\x00\x00', + b'\x018966306R3000\x00\x00\x00\x00', b'\x018966306T3100\x00\x00\x00\x00', b'\x018966306T3200\x00\x00\x00\x00', b'\x018966306T4000\x00\x00\x00\x00', @@ -542,12 +552,15 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F6201200\x00\x00\x00\x00', + b'\x018821F6201300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F0602100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F0602200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + 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: { @@ -716,22 +729,26 @@ FW_VERSIONS = { ], }, CAR.CHRH_TSS2: { - (Ecu.eps, 0x7a1, None): [ - b'8965B10092\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', - ], - (Ecu.fwdRadar, 0x750, 15): [ - b'\x018821FF410500\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 109): [ - b'\x028646FF413100\x00\x00\x00\x008646GF411100\x00\x00\x00\x00', - ], - }, + (Ecu.eps, 0x7a1, None): [ + b'8965B10092\x00\x00\x00\x00\x00\x00', + b'8965B10091\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'\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: { (Ecu.engine, 0x7e0, None): [ b'\x0230ZC2000\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -1454,6 +1471,23 @@ FW_VERSIONS = { 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'\x01896634AJ2000\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: { (Ecu.engine, 0x700, None): [ b'\x01896634A15000\x00\x00\x00\x00', @@ -1541,6 +1575,23 @@ FW_VERSIONS = { b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00', ], }, + CAR.RAV4H_TSS2_2023: { + (Ecu.abs, 0x7b0, None): [ + b'\x01F15264283200\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x01896634AE1001\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.SIENNA: { (Ecu.engine, 0x700, None): [ b'\x01896630832100\x00\x00\x00\x00', @@ -1729,6 +1780,7 @@ FW_VERSIONS = { b'\x018966378B2100\x00\x00\x00\x00', b'\x018966378B3000\x00\x00\x00\x00', b'\x018966378B4100\x00\x00\x00\x00', + b'\x018966378G2000\x00\x00\x00\x00', b'\x018966378G3000\x00\x00\x00\x00', b'\x018966378B2000\x00\x00\x00\x00', ], @@ -1751,6 +1803,7 @@ FW_VERSIONS = { CAR.LEXUS_NXH_TSS2: { (Ecu.engine, 0x7e0, None): [ b'\x0237887000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02378A0000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'F152678210\x00\x00\x00\x00\x00\x00', @@ -1760,6 +1813,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301400\x00\x00\x00\x00', + b'\x018821F3301300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F78030A0\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', @@ -2096,6 +2150,7 @@ DBC = { 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), CAR.COROLLA_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.COROLLAH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.LEXUS_ES_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), @@ -2106,6 +2161,7 @@ DBC = { CAR.LEXUS_CTH: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), CAR.RAV4H_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_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_NXH: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), 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'), @@ -2120,8 +2176,8 @@ DBC = { EPS_SCALE = defaultdict(lambda: 73, {CAR.PRIUS: 66, CAR.COROLLA: 88, CAR.LEXUS_IS: 77, CAR.LEXUS_RC: 77, CAR.LEXUS_CTH: 100, CAR.PRIUS_V: 100}) # Toyota/Lexus Safety Sense 2.0 and 2.5 -TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, - CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2, +TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, + CAR.RAV4H_TSS2_2023, CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2, CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.LEXUS_NXH_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2, CAR.CHR_TSS2, CAR.CHRH_TSS2} NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH} @@ -2130,10 +2186,13 @@ NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH} 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.CHR_TSS2, CAR.CHRH_TSS2} +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} + +# these cars use the Lane Tracing Assist (LTA) message for lateral control +ANGLE_CONTROL_CAR = {CAR.RAV4H_TSS2_2023, CAR.RAV4_TSS2_2023} EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.CHRH_TSS2, CAR.COROLLAH_TSS2, CAR.HIGHLANDERH, CAR.HIGHLANDERH_TSS2, CAR.PRIUS, - CAR.PRIUS_V, CAR.RAV4H, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, CAR.LEXUS_CTH, CAR.MIRAI, CAR.LEXUS_ESH, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_NXH, CAR.LEXUS_RXH, + CAR.PRIUS_V, CAR.RAV4H, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, CAR.RAV4H_TSS2_2023, CAR.LEXUS_CTH, CAR.MIRAI, CAR.LEXUS_ESH, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_NXH, CAR.LEXUS_RXH, CAR.LEXUS_RXH_TSS2, CAR.LEXUS_NXH_TSS2, CAR.PRIUS_TSS2, CAR.ALPHARDH_TSS2} # no resume button press required diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 694b4006f8..9873b628cd 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -293,6 +293,7 @@ FW_VERSIONS = { b'\xf1\x873G0906259N \xf1\x890004', b'\xf1\x873G0906259P \xf1\x890001', b'\xf1\x875NA907115H \xf1\x890002', + b'\xf1\x873G0906259G \xf1\x890004', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158L \xf1\x893611', @@ -304,17 +305,19 @@ FW_VERSIONS = { b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121157161111572900', b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121177161113772900', b'\xf1\x873Q0959655DL\xf1\x890732\xf1\x82\0161812141812171105141123052J00', + b'\xf1\x873Q0959655CK\xf1\x890711\xf1\x82\x0e1712141712141105121122052900', ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571B41815A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571B00817A1', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\00567B0020800', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020800', b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x002MB4092M7N', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x875Q0907572J \xf1\x890654', + b'\xf1\x875Q0907572R \xf1\x890771', ], }, CAR.ATLAS_MK1: { @@ -394,6 +397,7 @@ FW_VERSIONS = { b'\xf1\x8704L906021DT\xf1\x895520', b'\xf1\x8704L906021DT\xf1\x898127', b'\xf1\x8704L906021N \xf1\x895518', + b'\xf1\x8704L906026BN\xf1\x891197', b'\xf1\x8704L906026BP\xf1\x897608', b'\xf1\x8704L906026NF\xf1\x899528', b'\xf1\x8704L906056CL\xf1\x893823', @@ -514,6 +518,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0511A00403A0', b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00404A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00504A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00604A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A07A02A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00507A1', @@ -861,6 +866,7 @@ FW_VERSIONS = { b'\xf1\x878V0906264B \xf1\x890003', b'\xf1\x878V0907115B \xf1\x890007', b'\xf1\x878V0907404A \xf1\x890005', + b'\xf1\x875G0906259D \xf1\x890002', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870CW300044T \xf1\x895245', @@ -868,6 +874,7 @@ FW_VERSIONS = { b'\xf1\x870D9300012 \xf1\x894912', b'\xf1\x870D9300012 \xf1\x894931', b'\xf1\x870D9300012K \xf1\x894513', + b'\xf1\x870D9300013B \xf1\x894902', b'\xf1\x870D9300013B \xf1\x894931', b'\xf1\x870D9300041N \xf1\x894512', b'\xf1\x870D9300043T \xf1\x899699', @@ -905,11 +912,13 @@ FW_VERSIONS = { b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00803A0', b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0503G0G803A0', b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\00516G00804A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516G00804A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521G00807A1', ], (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x875Q0907567N \xf1\x890400\xf1\x82\00101', - b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\00101', + b'\xf1\x875Q0907567N \xf1\x890400\xf1\x82\x0101', + b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', + b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', b'\xf1\x875Q0907572G \xf1\x890571', b'\xf1\x875Q0907572H \xf1\x890620', b'\xf1\x875Q0907572P \xf1\x890682', @@ -1073,6 +1082,8 @@ FW_VERSIONS = { b'\xf1\x8704L906026DE\xf1\x895418', b'\xf1\x8704L906026EJ\xf1\x893661', b'\xf1\x8704L906026HT\xf1\x893617', + b'\xf1\x8783A907115E \xf1\x890001', + b'\xf1\x8705E906018DJ\xf1\x890915', b'\xf1\x875NA907115E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890005', ], @@ -1082,6 +1093,8 @@ FW_VERSIONS = { b'\xf1\x870DL300012M \xf1\x892107', b'\xf1\x870DL300012N \xf1\x892110', b'\xf1\x870DL300013G \xf1\x892119', + b'\xf1\x870GC300014N \xf1\x892801', + b'\xf1\x870GC300046Q \xf1\x892802', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AP\xf1\x890306\xf1\x82\r11110011110011421111314211', @@ -1089,11 +1102,13 @@ FW_VERSIONS = { b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1213001211001244212111442100', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e1213001211001205212112052100', b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111', + b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6060405', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6070405', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G500', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G600', ], (Ecu.fwdRadar, 0x757, None): [ @@ -1101,6 +1116,7 @@ FW_VERSIONS = { b'\xf1\x872Q0907572R \xf1\x890372', b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572AB\xf1\x890397', ], }, CAR.SKODA_OCTAVIA_MK3: { diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 05c3897335..cd6525949c 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -190,3 +190,13 @@ def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates): current_curvature_desired + max_curvature_rate * DT_MDL) return safe_desired_curvature, safe_desired_curvature_rate + + +def get_friction(lateral_accel_error: float, lateral_accel_deadzone: float, friction_threshold: float, torque_params: car.CarParams.LateralTorqueTuning, friction_compensation: bool) -> float: + friction_interp = interp( + apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone), + [-friction_threshold, friction_threshold], + [-torque_params.friction, torque_params.friction] + ) + friction = float(friction_interp) if friction_compensation else 0.0 + return friction diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index e4ddfb5326..67a4735e14 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -658,6 +658,11 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { ET.NO_ENTRY: NoEntryAlert("Steering Temporarily Unavailable"), }, + EventName.steerTimeLimit: { + ET.SOFT_DISABLE: soft_disable_alert("Vehicle Steering Time Limit"), + ET.NO_ENTRY: NoEntryAlert("Vehicle Steering Time Limit"), + }, + EventName.outOfSpace: { ET.PERMANENT: out_of_space_alert, ET.NO_ENTRY: NoEntryAlert("Out of Storage"), @@ -811,10 +816,6 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { ET.NO_ENTRY: NoEntryAlert("Cruise Fault: Restart the Car"), }, - EventName.accFaultedTemp: { - ET.NO_ENTRY: NoEntryAlert("Cruise Temporarily Faulted"), - }, - EventName.controlsMismatch: { ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Controls Mismatch"), ET.NO_ENTRY: NoEntryAlert("Controls Mismatch"), diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 2f56094379..6550b19227 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -61,10 +61,12 @@ class LatControlTorque(LatControl): low_speed_factor = interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2 setpoint = desired_lateral_accel + low_speed_factor * desired_curvature measurement = actual_lateral_accel + low_speed_factor * actual_curvature - error = setpoint - measurement gravity_adjusted_lateral_accel = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY - pid_log.error = self.torque_from_lateral_accel(error, self.torque_params, error, + torque_from_setpoint = self.torque_from_lateral_accel(setpoint, self.torque_params, setpoint, lateral_accel_deadzone, friction_compensation=False) + torque_from_measurement = self.torque_from_lateral_accel(measurement, self.torque_params, measurement, + lateral_accel_deadzone, friction_compensation=False) + pid_log.error = torque_from_setpoint - torque_from_measurement ff = self.torque_from_lateral_accel(gravity_adjusted_lateral_accel, self.torque_params, desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, friction_compensation=True) diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py index 92fc2468bb..18c8e79026 100755 --- a/selfdrive/controls/tests/test_startup.py +++ b/selfdrive/controls/tests/test_startup.py @@ -72,7 +72,6 @@ class TestStartup(unittest.TestCase): params.clear_all() params.put_bool("Passive", False) params.put_bool("OpenpilotEnabledToggle", True) - params.put_bool("ObdMultiplexingDisabled", True) # Build capnn version of FW array if fw_versions is not None: @@ -109,6 +108,10 @@ class TestStartup(unittest.TestCase): finger = _FINGERPRINTS[car_model][0] for _ in range(1000): + # controlsd waits for boardd to echo back that it has changed the multiplexing mode + if not params.get_bool("ObdMultiplexingChanged"): + params.put_bool("ObdMultiplexingChanged", True) + msgs = [[addr, 0, b'\x00'*length, 0] for addr, length in finger.items()] pm.send('can', can_list_to_can_capnp(msgs)) diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/hyundai_enable_radar_points.py index 07ce5ebddb..3a0ff33cb6 100755 --- a/selfdrive/debug/hyundai_enable_radar_points.py +++ b/selfdrive/debug/hyundai_enable_radar_points.py @@ -32,6 +32,9 @@ SUPPORTED_FW_VERSIONS = { b"DN8_ SCC FHCUP 1.00 1.00 99110-L0000\x19\x08)\x15T ": ConfigValues( default_config=b"\x00\x00\x00\x01\x00\x00", tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), + b"DN8_ SCC F-CUP 1.00 1.00 99110-L0000\x19\x08)\x15T ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), # 2021 SONATA HYBRID b"DNhe SCC FHCUP 1.00 1.02 99110-L5000 \x01#\x15# ": ConfigValues( default_config=b"\x00\x00\x00\x01\x00\x00", diff --git a/selfdrive/debug/internal/check_frame_frequencies.py b/selfdrive/debug/internal/check_frame_frequencies.py new file mode 100755 index 0000000000..9ac8bfc80e --- /dev/null +++ b/selfdrive/debug/internal/check_frame_frequencies.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +import time +import statistics +import cereal.messaging as messaging + +from typing import Dict + +camera_states = [ + 'roadCameraState', + 'wideRoadCameraState', + 'driverCameraState' +] + +def fmt(val): + ref = 0.05 + return f"{val:.6f} ({100 * val / ref:.2f}%)" + +if __name__ == "__main__": + sm = messaging.SubMaster(camera_states) + + prev_sof = {state: None for state in camera_states} + diffs: Dict[str, list] = {state: [] for state in camera_states} + + st = time.monotonic() + while True: + sm.update() + + for state in camera_states: + if sm.updated[state]: + if prev_sof[state] is not None: + diffs[state].append((sm[state].timestampSof - prev_sof[state]) / 1e9) + prev_sof[state] = sm[state].timestampSof + + if time.monotonic() - st > 10: + for state in camera_states: + values = diffs[state] + ref = 0.05 + print(f"{state} \tMean: {fmt(statistics.mean(values))} \t Min: {fmt(min(values))} \t Max: {fmt(max(values))} \t Std: {statistics.stdev(values):.6f} \t Num frames: {len(values)}") + diffs[state] = [] + + print() + st = time.monotonic() diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index 2b18dcf152..0c0bcbf7ef 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import json import math import os import time @@ -8,7 +7,7 @@ from collections import defaultdict from concurrent.futures import Future, ProcessPoolExecutor from datetime import datetime from enum import IntEnum -from typing import List, Optional +from typing import List, Optional, Dict, Any import numpy as np @@ -17,9 +16,9 @@ from common.params import Params, put_nonblocking from laika import AstroDog from laika.constants import SECS_IN_HR, SECS_IN_MIN from laika.downloader import DownloadFailed -from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_gps_ephem, convert_ublox_glonass_ephem, parse_qcom_ephem +from laika.ephemeris import EphemerisType, GPSEphemeris, GLONASSEphemeris, ephemeris_structs, parse_qcom_ephem from laika.gps_time import GPSTime -from laika.helpers import ConstellationId +from laika.helpers import ConstellationId, get_sv_id from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox, read_raw_qcom from laika.opt import calc_pos_fix, get_posfix_sympy_fun, calc_vel_fix, get_velfix_sympy_func from selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind @@ -28,15 +27,53 @@ from selfdrive.locationd.models.gnss_kf import States as GStates from system.swaglog import cloudlog MAX_TIME_GAP = 10 -EPHEMERIS_CACHE = 'LaikadEphemerisV2' +EPHEMERIS_CACHE = 'LaikadEphemerisV3' DOWNLOADS_CACHE_FOLDER = "/tmp/comma_download_cache/" 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=("GPS", "GLONASS"), auto_fetch_navs=True, auto_update=False, - valid_ephem_types=(EphemerisType.NAV,), + 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 @@ -51,10 +88,11 @@ class Laikad: self.auto_fetch_navs = auto_fetch_navs self.orbit_fetch_executor: Optional[ProcessPoolExecutor] = None self.orbit_fetch_future: Optional[Future] = None - - self.last_fetch_navs_t = None self.got_first_gnss_msg = False - self.last_cached_t = 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() @@ -64,36 +102,58 @@ class Laikad: self.last_fix_t = None self.gps_week = None self.use_qcom = use_qcom + self.first_log_time = None + self.ttff = -1 def load_cache(self): if not self.save_ephemeris: return - cache = Params().get(EPHEMERIS_CACHE) - if not cache: + cache_bytes = Params().get(EPHEMERIS_CACHE) + if not cache_bytes: return + nav_dict = {} try: - cache = json.loads(cache, object_hook=deserialize_hook) - if cache['version'] == CACHE_VERSION: - self.astro_dog.add_navs(cache['navs']) - self.last_fetch_navs_t = cache['last_fetch_navs_t'] - else: - cache['navs'] = {} - except json.decoder.JSONDecodeError: + ephem_cache = ephemeris_structs.EphemerisCache.from_bytes(cache_bytes) + 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") - timestamp = self.last_fetch_navs_t.as_datetime() if self.last_fetch_navs_t is not None else 'Nan' cloudlog.debug( - f"Loaded navs ({sum([len(v) for v in cache['navs']])}) cache with timestamp: {timestamp}. Unique orbit and nav sats: {list(cache['navs'].keys())} " + - f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in self.astro_dog.navs_fetched_times._ranges]}") - - def cache_ephemeris(self, t: GPSTime): - if self.save_ephemeris and (self.last_cached_t is None or t - self.last_cached_t > SECS_IN_MIN): - put_nonblocking(EPHEMERIS_CACHE, json.dumps( - {'version': CACHE_VERSION, 'last_fetch_navs_t': self.last_fetch_navs_t, 'navs': self.astro_dog.navs}, - cls=CacheSerializer)) + 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()], []) + ephem_cache = ephemeris_structs.EphemerisCache(**{'glonassEphemerides': [e.data for e in nav_list if e.prn[0]=='R'], + 'gpsEphemerides': [e.data for e in nav_list if e.prn[0]=='G']}) + + put_nonblocking(EPHEMERIS_CACHE, ephem_cache.to_bytes()) cloudlog.debug("Cache saved") - self.last_cached_t = t + self.last_cached_t = self.last_report_time + + def create_ephem_statuses(self): + ephemeris_statuses = [] + prns_to_check = list(self.astro_dog.get_all_ephem_prns()) + prns_to_check.sort() + for prn in prns_to_check: + eph = self.astro_dog.get_eph(prn, self.last_report_time) + if eph is not None: + status = log.GnssMeasurements.EphemerisStatus.new_message() + status.constellationId = ConstellationId.from_rinex_char(prn[0]).value + status.svId = get_sv_id(prn) + status.type = get_log_eph_type(eph).value + status.source = get_log_eph_source(eph).value + 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: @@ -121,8 +181,15 @@ class Laikad: def is_good_report(self, gnss_msg): if gnss_msg.which() == 'drMeasurementReport' and self.use_qcom: constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source) - # TODO support GLONASS - return constellation_id in [ConstellationId.GPS, ConstellationId.SBAS] + # TODO: Understand and use remaining unknown constellations + try: + good_constellation = constellation_id in [ConstellationId.GPS, ConstellationId.SBAS] + except NotImplementedError: + good_constellation = False + # gpsWeek 65535 is received rarely from quectel, this cannot be + # passed to GnssMeasurements's gpsWeek (Int16) + good_week = not getattr(gnss_msg, gnss_msg.which()).gpsWeek > np.iinfo(np.int16).max + return good_constellation and good_week elif gnss_msg.which() == 'measurementReport' and not self.use_qcom: return True else: @@ -139,6 +206,7 @@ class Laikad: week = report.gpsWeek tow = report.rcvTow new_meas = read_raw_ublox(report) + self.last_report_time = GPSTime(week, tow) return week, tow, new_meas def is_ephemeris(self, gnss_msg): @@ -152,29 +220,47 @@ class Laikad: # TODO this is not robust to gps week rollover if self.gps_week is None: return - ephem = parse_qcom_ephem(gnss_msg.drSvPoly, self.gps_week) + try: + ephem = parse_qcom_ephem(gnss_msg.drSvPoly, self.gps_week) + 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': - ephem = convert_ublox_gps_ephem(gnss_msg.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': - ephem = convert_ublox_glonass_ephem(gnss_msg.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(t=ephem.epoch) + 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: - corrected_measurements = correct_measurements(processed_measurements, self.last_fix_pos, self.astro_dog) - instant_fix = self.get_lsq_fix(t, corrected_measurements) - #instant_fix = self.get_lsq_fix(t, processed_measurements) + est_pos = self.last_fix_pos else: - corrected_measurements = [] - instant_fix = self.get_lsq_fix(t, processed_measurements) + est_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist() + corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog) + 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: @@ -182,60 +268,53 @@ class Laikad: self.last_fix_t = t self.last_fix_pos = position_estimate self.lat_fix_pos_std = position_std - if (t*1e9) % 10 == 0: - cloudlog.debug(f"Measurements Incoming/Processed/Corrected: {len(new_meas), len(processed_measurements), len(corrected_measurements)}") - return position_estimate, position_std, velocity_estimate, velocity_std, corrected_measurements, processed_measurements + 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} + 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) - return None elif self.is_good_report(gnss_msg): - week, tow, new_meas = self.read_report(gnss_msg) self.gps_week = week - if len(new_meas) == 0: - return None - - t = gnss_mono_time * 1e-9 if week > 0: self.got_first_gnss_msg = True latest_msg_t = GPSTime(week, tow) if self.auto_fetch_navs: self.fetch_navs(latest_msg_t, block) - output = self.process_report(new_meas, t) - if output is None: - return None - position_estimate, position_std, velocity_estimate, velocity_std, corrected_measurements, _ = output + corrected_measurements = self.process_report(new_meas, t) + msg_dict['correctedMeasurements'] = [create_measurement_msg(m) for m in corrected_measurements] - self.update_localizer(position_estimate, t, corrected_measurements) - meas_msgs = [create_measurement_msg(m) for m in corrected_measurements] - msg = messaging.new_message("gnssMeasurements") + 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.gnssMeasurements = { - "gpsWeek": week, - "gpsTimeOfWeek": tow, - "kalmanPositionECEF": measurement_msg(value=self.gnss_kf.x[GStates.ECEF_POS].tolist(), + 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), - "kalmanVelocityECEF": measurement_msg(value=self.gnss_kf.x[GStates.ECEF_VELOCITY].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), - "positionECEF": measurement_msg(value=position_estimate, std=position_std.tolist(), valid=bool(self.last_fix_t == t)), - "velocityECEF": measurement_msg(value=velocity_estimate, std=velocity_std.tolist(), valid=bool(self.last_fix_t == t)), - - "measTime": gnss_mono_time, - "correctedMeasurements": meas_msgs - } - return msg - - #elif gnss_msg.which() == 'ionoData': - # TODO: add this, Needed to better correct messages offline. First fix ublox_msg.cc to sent them. + 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 @@ -247,7 +326,7 @@ class Laikad: cloudlog.error("Time gap of over 10s detected, gnss kalman reset") elif not valid[2]: cloudlog.error("Gnss kalman filter state is nan") - if len(est_pos) > 0: + 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: @@ -272,7 +351,7 @@ class Laikad: 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 (self.last_fetch_navs_t is None or abs(t - self.last_fetch_navs_t) > SECS_IN_MIN): + 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 @@ -290,7 +369,7 @@ class Laikad: 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(t=t) + self.cache_ephemeris() def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types, cache_dir): @@ -320,31 +399,8 @@ def create_measurement_msg(meas: GNSSMeasurement): c.satPos = meas.sat_pos_final.tolist() c.satVel = meas.sat_vel.tolist() c.satVel = meas.sat_vel.tolist() - ephem = meas.sat_ephemeris - assert ephem is not None - week, time_of_week = -1, -1 - if ephem.eph_type == EphemerisType.NAV: - source_type = EphemerisSourceType.nav - elif ephem.eph_type == EphemerisType.QCOM_POLY: - source_type = EphemerisSourceType.qcom - else: - assert ephem.file_epoch is not None - week = ephem.file_epoch.week - time_of_week = ephem.file_epoch.tow - file_src = ephem.file_source - if file_src == 'igu': # example nasa: '2214/igu22144_00.sp3.Z' - source_type = EphemerisSourceType.nasaUltraRapid - elif file_src == 'Sta': # example nasa: '22166/ultra/Stark_1D_22061518.sp3' - source_type = EphemerisSourceType.glonassIacUltraRapid - else: - raise Exception(f"Didn't expect file source {file_src}") - - c.ephemerisSource.type = source_type.value - c.ephemerisSource.gpsWeek = week - c.ephemerisSource.gpsTimeOfWeek = int(time_of_week) return c - def kf_add_observations(gnss_kf: GNSSKalman, t: float, measurements: List[GNSSMeasurement]): ekf_data = defaultdict(list) for m in measurements: @@ -360,69 +416,29 @@ def kf_add_observations(gnss_kf: GNSSKalman, t: float, measurements: List[GNSSMe gnss_kf.predict_and_observe(t, kind, data) -class CacheSerializer(json.JSONEncoder): - - def default(self, o): - if isinstance(o, Ephemeris): - return o.to_json() - if isinstance(o, GPSTime): - return o.__dict__ - if isinstance(o, np.ndarray): - return o.tolist() - return json.JSONEncoder.default(self, o) - - -def deserialize_hook(dct): - if 'ephemeris' in dct: - return Ephemeris.from_json(dct) - if 'week' in dct: - return GPSTime(dct['week'], dct['tow']) - return dct - - -class EphemerisSourceType(IntEnum): - nav = 0 - nasaUltraRapid = 1 - glonassIacUltraRapid = 2 - qcom = 3 - - -def process_msg(laikad, gnss_msg, mono_time, block=False): - # TODO: Understand and use remaining unknown constellations - if gnss_msg.which() == "drMeasurementReport": - if getattr(gnss_msg, gnss_msg.which()).source not in ['glonass', 'gps', 'beidou', 'sbas']: - return None - - if getattr(gnss_msg, gnss_msg.which()).gpsWeek > np.iinfo(np.int16).max: - # gpsWeek 65535 is received rarely from quectel, this cannot be - # passed to GnssMeasurements's gpsWeek (Int16) - return None - - return laikad.process_gnss_msg(gnss_msg, mono_time, block=block) - - def clear_tmp_cache(): if os.path.exists(DOWNLOADS_CACHE_FOLDER): shutil.rmtree(DOWNLOADS_CACHE_FOLDER) os.mkdir(DOWNLOADS_CACHE_FOLDER) -def main(sm=None, pm=None, qc=None): +def main(sm=None, pm=None): #clear_tmp_cache() use_qcom = not Params().get_bool("UbloxAvailable", block=True) - if use_qcom or (qc is not None and qc): - raw_gnss_socket = "qcomGnss" + if use_qcom: + raw_name = "qcomGnss" else: - raw_gnss_socket = "ubloxGnss" + raw_name = "ubloxGnss" + raw_gnss_sock = messaging.sub_sock(raw_name, conflate=False, timeout=1000) if sm is None: - sm = messaging.SubMaster([raw_gnss_socket, 'clocks']) + sm = messaging.SubMaster(['clocks',]) if pm is None: pm = messaging.PubMaster(['gnssMeasurements']) # disable until set as main gps source, to better analyze startup time - use_internet = False #"LAIKAD_NO_INTERNET" not in os.environ + use_internet = False # "LAIKAD_NO_INTERNET" not in os.environ replay = "REPLAY" in os.environ if replay or "CI" in os.environ: @@ -431,22 +447,17 @@ def main(sm=None, pm=None, qc=None): laikad = Laikad(save_ephemeris=not replay, auto_fetch_navs=use_internet, use_qcom=use_qcom) while True: - sm.update() - - if sm.updated[raw_gnss_socket]: - gnss_msg = sm[raw_gnss_socket] - - msg = process_msg(laikad, gnss_msg, sm.logMonoTime[raw_gnss_socket], replay) - if msg is None: - # TODO: beautify this, locationd needs a valid message - msg = messaging.new_message("gnssMeasurements") - pm.send('gnssMeasurements', msg) + for in_msg in messaging.drain_sock(raw_gnss_sock): + out_msg = laikad.process_gnss_msg(getattr(in_msg, raw_name), in_msg.logMonoTime, replay) + pm.send('gnssMeasurements', out_msg) + sm.update(0) if not laikad.got_first_gnss_msg and sm.updated['clocks']: clocks_msg = sm['clocks'] t = GPSTime.from_datetime(datetime.utcfromtimestamp(clocks_msg.wallTimeNanos * 1E-9)) if laikad.auto_fetch_navs: laikad.fetch_navs(t, block=replay) + if __name__ == "__main__": main() diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 307626506a..87c5f644c5 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -27,7 +27,6 @@ const double MAX_FILTER_REWIND_TIME = 0.8; // s // They should be replaced with synced time from a real clock const double GPS_QUECTEL_SENSOR_TIME_OFFSET = 0.630; // s const double GPS_UBLOX_SENSOR_TIME_OFFSET = 0.095; // s -const float GPS_MUL_FACTOR = 10.0; const float GPS_POS_STD_THRESHOLD = 50.0; const float GPS_VEL_STD_THRESHOLD = 5.0; const float GPS_POS_ERROR_RESET_THRESHOLD = 300.0; @@ -326,8 +325,14 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R VectorXd ecef_pos = this->converter->ned2ecef({ 0.0, 0.0, 0.0 }).to_vector(); VectorXd ecef_vel = this->converter->ned2ecef({ log.getVNED()[0], log.getVNED()[1], log.getVNED()[2] }).to_vector() - ecef_pos; - MatrixXdr ecef_pos_R = Vector3d::Constant(std::pow(GPS_MUL_FACTOR * log.getAccuracy(),2) + std::pow(GPS_MUL_FACTOR * log.getVerticalAccuracy(),2)).asDiagonal(); - MatrixXdr ecef_vel_R = Vector3d::Constant(std::pow(GPS_MUL_FACTOR * log.getSpeedAccuracy(), 2)).asDiagonal(); + float ecef_pos_std; + if (ublox_available) { + ecef_pos_std = std::sqrt(std::pow(log.getAccuracy(), 2) + std::pow(log.getVerticalAccuracy(), 2)); + } else { + ecef_pos_std = std::sqrt(3 * std::pow(log.getVerticalAccuracy(), 2)); + } + MatrixXdr ecef_pos_R = Vector3d::Constant(std::pow(this->gps_std_factor * ecef_pos_std, 2)).asDiagonal(); + MatrixXdr ecef_vel_R = Vector3d::Constant(std::pow(this->gps_std_factor * log.getSpeedAccuracy(), 2)).asDiagonal(); this->unix_timestamp_millis = log.getUnixTimestampMillis(); double gps_est_error = (this->kf->get_x().segment(STATE_ECEF_POS_START) - ecef_pos).norm(); @@ -377,14 +382,14 @@ void Localizer::handle_gnss(double current_time, const cereal::GnssMeasurements: // indexed at 0 cause all std values are the same MAE auto ecef_pos_std = log.getPositionECEF().getStd()[0]; - MatrixXdr ecef_pos_R = Vector3d::Constant(pow(GPS_MUL_FACTOR*ecef_pos_std, 2)).asDiagonal(); + MatrixXdr ecef_pos_R = Vector3d::Constant(pow(this->gps_std_factor*ecef_pos_std, 2)).asDiagonal(); auto ecef_vel_v = log.getVelocityECEF().getValue(); VectorXd ecef_vel = Vector3d(ecef_vel_v[0], ecef_vel_v[1], ecef_vel_v[2]); // indexed at 0 cause all std values are the same MAE auto ecef_vel_std = log.getVelocityECEF().getStd()[0]; - MatrixXdr ecef_vel_R = Vector3d::Constant(pow(GPS_MUL_FACTOR*ecef_vel_std, 2)).asDiagonal(); + MatrixXdr ecef_vel_R = Vector3d::Constant(pow(this->gps_std_factor*ecef_vel_std, 2)).asDiagonal(); double gps_est_error = (this->kf->get_x().segment(STATE_ECEF_POS_START) - ecef_pos).norm(); @@ -661,8 +666,10 @@ int Localizer::locationd_thread() { const char* gps_location_socket; if (ublox_available) { gps_location_socket = "gpsLocationExternal"; + this->gps_std_factor = 10.0; } else { gps_location_socket = "gpsLocation"; + this->gps_std_factor = 2.0; } const std::initializer_list service_list = {gps_location_socket, "cameraOdometry", "liveCalibration", "carState", "carParams", "accelerometer", "gyroscope"}; diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index b7e42a9df8..6366b84f8e 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -84,4 +84,5 @@ private: std::map observation_values_invalid; bool standstill = true; int32_t orientation_reset_count = 0; + float gps_std_factor; }; diff --git a/selfdrive/locationd/test/test_laikad.py b/selfdrive/locationd/test/test_laikad.py index 10ac7790b6..e184dc3ec4 100755 --- a/selfdrive/locationd/test/test_laikad.py +++ b/selfdrive/locationd/test/test_laikad.py @@ -1,21 +1,51 @@ #!/usr/bin/env python3 import time import unittest -from collections import defaultdict +from cereal import log +import cereal.messaging as messaging +from common.params import Params from datetime import datetime from unittest import mock -from unittest.mock import patch +#from unittest.mock import patch +from tqdm import tqdm + -from common.params import Params from laika.constants import SECS_IN_DAY from laika.downloader import DownloadFailed -from laika.ephemeris import EphemerisType, GPSEphemeris +from laika.ephemeris import EphemerisType from laika.gps_time import GPSTime -from laika.helpers import ConstellationId, TimeRangeHolder -from laika.raw_gnss import GNSSMeasurement, read_raw_ublox -from selfdrive.locationd.laikad import EPHEMERIS_CACHE, EphemerisSourceType, Laikad, create_measurement_msg +from laika.helpers import ConstellationId +from laika.raw_gnss import GNSSMeasurement, read_raw_ublox, read_raw_qcom +from selfdrive.locationd.laikad import EPHEMERIS_CACHE, Laikad from selfdrive.test.openpilotci import get_url from tools.lib.logreader import LogReader +from selfdrive.manager.process_config import managed_processes + +from selfdrive.test.process_replay.helpers import OpenpilotPrefix + + +def get_ublox_gnss(ubloxraw): + with OpenpilotPrefix(): + managed_processes['ubloxd'].start() + timeout_ms = 30 + pm = messaging.PubMaster(['ubloxRaw']) + sock = messaging.sub_sock('ubloxGnss', timeout=timeout_ms) + + log_msgs = [] + log_t = [] + for x in tqdm(ubloxraw): + pm.send(x.which(), x.as_builder()) + ret = messaging.recv_one(sock) + if ret is not None: + msg = log.Event.new_message(ubloxGnss=ret.ubloxGnss.to_dict()) + msg.logMonoTime = x.logMonoTime + log_msgs.append(msg) + log_t.append(1e-9 * x.logMonoTime) + assert managed_processes['ubloxd'].get_process_state_msg().running + assert len(log_msgs) > 1 or len(ubloxraw) == 0 + managed_processes['ubloxd'].stop() + return log_t, log_msgs + def get_log(segs=range(0)): @@ -23,7 +53,8 @@ def get_log(segs=range(0)): for i in segs: logs.extend(LogReader(get_url("4cf7a6ad03080c90|2021-09-29--13-46-36", i))) - all_logs = [m for m in logs if m.which() == 'ubloxGnss'] + raw_logs = [m for m in logs if m.which() == 'ubloxRaw'] + all_logs = get_ublox_gnss(raw_logs)[1] low_gnss = [] for m in all_logs: if m.ubloxGnss.which() != 'measurementReport': @@ -31,21 +62,31 @@ def get_log(segs=range(0)): MAX_MEAS = 7 if m.ubloxGnss.measurementReport.numMeas > MAX_MEAS: - mb = m.as_builder() + 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(segs=range(0)): + logs = [] + for i in segs: + logs.extend(LogReader(get_url("b0b3cba7abf862d1|2023-03-11--09-40-33", i))) + 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: - msg = laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=True) + if m.which() == 'ubloxGnss': + gnss_msg = m.ubloxGnss + else: + gnss_msg = m.qcomGnss + 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: @@ -55,10 +96,16 @@ def verify_messages(lr, laikad, return_one_success=False): def get_first_gps_time(logs): for m in logs: - if m.ubloxGnss.which == 'measurementReport': - new_meas = read_raw_ublox(m.ubloxGnss.measurementReport) - if len(new_meas) > 0: - return new_meas[0].recv_time + 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 + else: + 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): @@ -79,6 +126,7 @@ class TestLaikad(unittest.TestCase): logs, low_gnss = get_log(range(1)) cls.logs = logs cls.low_gnss = low_gnss + cls.logs_qcom = get_log_qcom(range(1)) first_gps_time = get_first_gps_time(logs) cls.first_gps_time = first_gps_time @@ -90,9 +138,9 @@ class TestLaikad(unittest.TestCase): 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) @@ -105,6 +153,7 @@ class TestLaikad(unittest.TestCase): self.assertIsNotNone(ephem) self.assertNotEqual(ephem, ephem2) + def test_fetch_navs_with_wrong_clocks(self): laikad = Laikad() @@ -127,40 +176,11 @@ class TestLaikad(unittest.TestCase): check_has_navs() self.assertEqual(laikad.last_fetch_navs_t, real_current_time) - def test_ephemeris_source_in_msg(self): - data_mock = defaultdict(str) - data_mock['sv_id'] = 1 - - gpstime = GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC - laikad = Laikad() - laikad.fetch_navs(gpstime, block=True) - meas = get_measurement_mock(gpstime, laikad.astro_dog.navs['R01'][0]) - msg = create_measurement_msg(meas) - self.assertEqual(msg.ephemerisSource.type.raw, EphemerisSourceType.nav) - # Verify gps satellite returns same source - meas = get_measurement_mock(gpstime, laikad.astro_dog.navs['R01'][0]) - msg = create_measurement_msg(meas) - self.assertEqual(msg.ephemerisSource.type.raw, EphemerisSourceType.nav) - - # Test nasa source by using older date - gpstime = GPSTime.from_datetime(datetime(2021, month=3, day=1)) - laikad = Laikad() - laikad.fetch_navs(gpstime, block=True) - meas = get_measurement_mock(gpstime, laikad.astro_dog.navs['G01'][0]) - msg = create_measurement_msg(meas) - self.assertEqual(msg.ephemerisSource.type.raw, EphemerisSourceType.nav) - - # Test nav source type - ephem = GPSEphemeris(data_mock, gpstime) - meas = get_measurement_mock(gpstime, ephem) - msg = create_measurement_msg(meas) - self.assertEqual(msg.ephemerisSource.type.raw, EphemerisSourceType.nav) - 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 = 559 + 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])) @@ -177,12 +197,15 @@ class TestLaikad(unittest.TestCase): self.assertTrue(kf_valid) def test_laika_online_nav_only(self): - laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.NAV) - # Disable fetch_orbits to test NAV only - correct_msgs = verify_messages(self.logs, laikad) - correct_msgs_expected = 559 - self.assertEqual(correct_msgs_expected, len(correct_msgs)) - self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) + for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs]): + 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 = 44 if use_qcom else 560 + valid_fix_expected = 43 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): @@ -195,8 +218,9 @@ class TestLaikad(unittest.TestCase): downloader_mock.side_effect = DownloadFailed laikad = Laikad(auto_update=False) correct_msgs = verify_messages(self.logs, laikad) - self.assertEqual(255, len(correct_msgs)) - self.assertEqual(255, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) + 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) @@ -212,65 +236,87 @@ class TestLaikad(unittest.TestCase): self.assertGreater(len(laikad.astro_dog.navs[prn]), 0) prn = "R01" self.assertGreater(len(laikad.astro_dog.navs[prn]), 0) - print(min(laikad.astro_dog.navs[prn], key=lambda e: e.epoch).epoch.as_datetime()) def test_get_navs_in_process(self): - laikad = Laikad(auto_update=False) - has_navs = False - for m in self.logs: - laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=False) - if laikad.orbit_fetch_future is not None: - laikad.orbit_fetch_future.result() - vals = laikad.astro_dog.navs.values() - has_navs = len(vals) > 0 and max([len(v) for v in vals]) > 0 - if has_navs: - break - self.assertTrue(has_navs) - self.assertGreater(len(laikad.astro_dog.navs_fetched_times._ranges), 0) - self.assertEqual(None, laikad.orbit_fetch_future) + for auto_fetch_navs in [True, False]: + for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs]): + 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: + 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): - laikad = Laikad(auto_update=True, save_ephemeris=True) - - 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.cache_ephemeris(t=GPSTime(0, 0)) - wait_for_cache() - Params().remove(EPHEMERIS_CACHE) + use_qcom = True + for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs]): + 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() + wait_for_cache() + Params().remove(EPHEMERIS_CACHE) + + #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) - #laikad.astro_dog.get_navs(self.first_gps_time) - laikad.fetch_navs(self.first_gps_time, block=True) - # Wait for cache to save - wait_for_cache() - # Check both nav and orbits separate - laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.NAV, save_ephemeris=True) - # 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(self.logs, laikad, return_one_success=True) - self.assertIsNotNone(msg) - - 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() + #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 @@ -282,12 +328,10 @@ class TestLaikad(unittest.TestCase): gm = msg.gnssMeasurements if len(gm.correctedMeasurements) != 0 and gm.positionECEF.valid: cnt += 1 - self.assertEqual(cnt, 559) + 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/manager.py b/selfdrive/manager/manager.py index 1128097251..1b10cee806 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -153,7 +153,7 @@ def manager_thread() -> None: for param in ("DoUninstall", "DoShutdown", "DoReboot"): if params.get_bool(param): shutdown = True - params.put("LastManagerExitReason", param) + params.put("LastManagerExitReason", f"{param} {datetime.datetime.now()}") cloudlog.warning(f"Shutting down manager - {param} set") if shutdown: diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index a483fa4db4..13759c62f8 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:736ddc08497d7596bae4d9515a8efb996676be80e67a6d34d632bb8af2ed3fa9 -size 45962515 +oid sha256:5121deb0d5c683b0fbee4c1cad7bc625953bf127b1383fb7599a6b644efd0aea +size 46011200 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index ad09668a46..bd026f5710 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -ab64afd1abd1059c14f50c67b51e5ef89029f216 +82db08d52b155336e9a1dadd11485d5acdf2eba0 diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 971ddb3fe2..43b329e916 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -5,7 +5,8 @@ import sys import threading import time import signal -from collections import namedtuple +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Callable import capnp @@ -28,7 +29,20 @@ TIMEOUT = 15 PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__)) FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/") -ProcessConfig = namedtuple('ProcessConfig', ['proc_name', 'pub_sub', 'ignore', 'init_callback', 'should_recv_callback', 'tolerance', 'fake_pubsubmaster', 'submaster_config', 'environ', 'subtest_name', "field_tolerances"], defaults=({}, {}, "", {})) +@dataclass +class ProcessConfig: + proc_name: str + pub_sub: Dict[str, List[str]] + ignore: List[str] + init_callback: Optional[Callable] + should_recv_callback: Optional[Callable] + tolerance: Optional[float] + fake_pubsubmaster: bool + submaster_config: Dict[str, List[str]] = field(default_factory=dict) + environ: Dict[str, str] = field(default_factory=dict) + subtest_name: str = "" + field_tolerances: Dict[str, float] = field(default_factory=dict) + timeout: int = 30 def wait_for_event(evt): @@ -238,21 +252,12 @@ def torqued_rcv_callback(msg, CP, cfg, fsm): return recv_socks, fsm.frame == 0 or msg.which() == 'liveLocationKalman' -def ublox_rcv_callback(msg): +def ublox_rcv_callback(msg, CP, cfg, fsm): msg_class, msg_id = msg.ubloxRaw[2:4] if (msg_class, msg_id) in {(1, 7 * 16)}: - return ["gpsLocationExternal"] + return ["gpsLocationExternal"], True elif (msg_class, msg_id) in {(2, 1 * 16 + 5), (10, 9)}: - return ["ubloxGnss"] - else: - return [] - - -def laika_rcv_callback(msg, CP, cfg, fsm): - if msg.which() == 'ubloxGnss' and msg.ubloxGnss.which() == "measurementReport": - return ["gnssMeasurements"], True - elif msg.which() == 'qcomGnss' and msg.qcomGnss.which() == "drMeasurementReport": - return ["gnssMeasurements"], True + return ["ubloxGnss"], True else: return [], False @@ -331,7 +336,7 @@ CONFIGS = [ pub_sub={ "cameraOdometry": ["liveLocationKalman"], "accelerometer": [], "gyroscope": [], - "gpsLocationExternal": [], "liveCalibration": [], "carState": [], + "gpsLocationExternal": [], "liveCalibration": [], "carState": [], "gpsLocation": [], }, ignore=["logMonoTime", "valid"], init_callback=get_car_params, @@ -371,9 +376,10 @@ CONFIGS = [ }, ignore=["logMonoTime"], init_callback=get_car_params, - should_recv_callback=laika_rcv_callback, + should_recv_callback=None, tolerance=NUMPY_TOLERANCE, - fake_pubsubmaster=True, + fake_pubsubmaster=False, + timeout=60*10, # first messages are blocked on internet assistance ), ProcessConfig( proc_name="torqued", @@ -395,10 +401,10 @@ def replay_process(cfg, lr, fingerprint=None): if cfg.fake_pubsubmaster: return python_replay_process(cfg, lr, fingerprint) else: - return cpp_replay_process(cfg, lr, fingerprint) + return replay_process_with_sockets(cfg, lr, fingerprint) -def setup_env(simulation=False, CP=None, cfg=None, controlsState=None): +def setup_env(simulation=False, CP=None, cfg=None, controlsState=None, lr=None): params = Params() params.clear_all() params.put_bool("OpenpilotEnabledToggle", True) @@ -406,13 +412,19 @@ def setup_env(simulation=False, CP=None, cfg=None, controlsState=None): params.put_bool("DisengageOnAccelerator", True) params.put_bool("WideCameraOnly", False) params.put_bool("DisableLogging", False) - params.put_bool("UbloxAvailable", True) - params.put_bool("ObdMultiplexingDisabled", True) os.environ["NO_RADAR_SLEEP"] = "1" os.environ["REPLAY"] = "1" - os.environ['SKIP_FW_QUERY'] = "" - os.environ['FINGERPRINT'] = "" + os.environ["SKIP_FW_QUERY"] = "" + os.environ["FINGERPRINT"] = "" + + if lr is not None: + services = {m.which() for m in lr} + params.put_bool("UbloxAvailable", "ubloxGnss" in services) + + if lr is not None: + services = {m.which() for m in lr} + params.put_bool("UbloxAvailable", "ubloxGnss" in services) if cfg is not None: # Clear all custom processConfig environment variables @@ -464,12 +476,6 @@ def python_replay_process(cfg, lr, fingerprint=None): all_msgs = sorted(lr, key=lambda msg: msg.logMonoTime) pub_msgs = [msg for msg in all_msgs if msg.which() in list(cfg.pub_sub.keys())] - # laikad needs decision between submaster ubloxGnss and qcomGnss, prio given to ubloxGnss - if cfg.proc_name == "laikad": - args = (*args, not any(m.which() == "ubloxGnss" for m in pub_msgs)) - service = "qcomGnss" if args[2] else "ubloxGnss" - pub_msgs = [m for m in pub_msgs if m.which() == service or m.which() == 'clocks'] - controlsState = None initialized = False for msg in lr: @@ -485,10 +491,10 @@ def python_replay_process(cfg, lr, fingerprint=None): if fingerprint is not None: os.environ['SKIP_FW_QUERY'] = "1" os.environ['FINGERPRINT'] = fingerprint - setup_env(cfg=cfg, controlsState=controlsState) + setup_env(cfg=cfg, controlsState=controlsState, lr=lr) else: CP = [m for m in lr if m.which() == 'carParams'][0].carParams - setup_env(CP=CP, cfg=cfg, controlsState=controlsState) + setup_env(CP=CP, cfg=cfg, controlsState=controlsState, lr=lr) assert(type(managed_processes[cfg.proc_name]) is PythonProcess) managed_processes[cfg.proc_name].prepare() @@ -517,7 +523,7 @@ def python_replay_process(cfg, lr, fingerprint=None): recv_socks, should_recv = cfg.should_recv_callback(msg, CP, cfg, fsm) else: recv_socks = [s for s in cfg.pub_sub[msg.which()] if - (fsm.frame + 1) % int(service_list[msg.which()].frequency / service_list[s].frequency) == 0] + (fsm.frame + 1) % max(1, int(service_list[msg.which()].frequency / service_list[s].frequency)) == 0] should_recv = bool(len(recv_socks)) if msg.which() == 'can': @@ -540,49 +546,58 @@ def python_replay_process(cfg, lr, fingerprint=None): return log_msgs -def cpp_replay_process(cfg, lr, fingerprint=None): - sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub] # We get responses here +def replay_process_with_sockets(cfg, lr, fingerprint=None): pm = messaging.PubMaster(cfg.pub_sub.keys()) + sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub] + sockets = {s: messaging.sub_sock(s, timeout=100) for s in sub_sockets} all_msgs = sorted(lr, key=lambda msg: msg.logMonoTime) pub_msgs = [msg for msg in all_msgs if msg.which() in list(cfg.pub_sub.keys())] - log_msgs = [] # We need to fake SubMaster alive since we can't inject a fake clock - setup_env(simulation=True, cfg=cfg) + setup_env(simulation=True, cfg=cfg, lr=lr) + + if cfg.proc_name == "laikad": + ublox = Params().get_bool("UbloxAvailable") + keys = set(cfg.pub_sub.keys()) - ({"qcomGnss", } if ublox else {"ubloxGnss", }) + pub_msgs = [msg for msg in pub_msgs if msg.which() in keys] managed_processes[cfg.proc_name].prepare() managed_processes[cfg.proc_name].start() + log_msgs = [] try: - with Timeout(TIMEOUT, error_msg=f"timed out testing process {repr(cfg.proc_name)}"): - while not all(pm.all_readers_updated(s) for s in cfg.pub_sub.keys()): + # Wait for process to startup + with Timeout(10, error_msg=f"timed out waiting for process to start: {repr(cfg.proc_name)}"): + while not any(pm.all_readers_updated(s) for s in cfg.pub_sub.keys()): time.sleep(0) - # Make sure all subscribers are connected - sockets = {s: messaging.sub_sock(s, timeout=2000) for s in sub_sockets} - for s in sub_sockets: - messaging.recv_one_or_none(sockets[s]) + for s in sockets.values(): + messaging.recv_one_or_none(s) - for i, msg in enumerate(pub_msgs): - pm.send(msg.which(), msg.as_builder()) - - resp_sockets = cfg.pub_sub[msg.which()] if cfg.should_recv_callback is None else cfg.should_recv_callback(msg) - for s in resp_sockets: - response = messaging.recv_one_retry(sockets[s]) + # Do the replay + cnt = 0 + for msg in pub_msgs: + with Timeout(cfg.timeout, error_msg=f"timed out testing process {repr(cfg.proc_name)}, {cnt}/{len(pub_msgs)} msgs done"): + resp_sockets = cfg.pub_sub[msg.which()] + if cfg.should_recv_callback is not None: + resp_sockets, _ = cfg.should_recv_callback(msg, None, None, None) - if response is None: - print(f"Warning, no response received {i}") - else: + # Make sure all subscribers are connected + if len(log_msgs) == 0 and len(resp_sockets) > 0: + for s in sockets.values(): + messaging.recv_one_or_none(s) - response = response.as_builder() - response.logMonoTime = msg.logMonoTime - response = response.as_reader() - log_msgs.append(response) + pm.send(msg.which(), msg.as_builder()) + while not pm.all_readers_updated(msg.which()): + time.sleep(0) - if not len(resp_sockets): # We only need to wait if we didn't already wait for a response - while not pm.all_readers_updated(msg.which()): - time.sleep(0) + for s in resp_sockets: + m = messaging.recv_one_retry(sockets[s]) + m = m.as_builder() + m.logMonoTime = msg.logMonoTime + log_msgs.append(m.as_reader()) + cnt += 1 finally: managed_processes[cfg.proc_name].signal(signal.SIGKILL) managed_processes[cfg.proc_name].stop() diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index a8896884e1..af63dab2f1 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -3c5ebb007f76ba0de710ff7a8cf5910ad2edf22f \ No newline at end of file +1bb3f665191e1b75c1b786f60e76d51b274f98ae diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 07117c2e7f..a2dd938e2f 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -18,7 +18,7 @@ from tools.lib.logreader import LogReader source_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA - ("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.KIA_EV6 + ("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.KIA_EV6 (+ QCOM GPS) ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI) ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR) ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 @@ -70,7 +70,7 @@ def run_test_process(data): res = None if not args.upload_only: lr = LogReader.from_bytes(lr_dat) - res, log_msgs = test_process(cfg, lr, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) + res, log_msgs = test_process(cfg, lr, segment, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) # save logs so we can upload when updating refs save_log(cur_log_fn, log_msgs) @@ -88,7 +88,7 @@ def get_log_data(segment): return (segment, f.read()) -def test_process(cfg, lr, ref_log_path, new_log_path, ignore_fields=None, ignore_msgs=None): +def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=None, ignore_msgs=None): if ignore_fields is None: ignore_fields = [] if ignore_msgs is None: @@ -96,7 +96,10 @@ def test_process(cfg, lr, ref_log_path, new_log_path, ignore_fields=None, ignore ref_log_msgs = list(LogReader(ref_log_path)) - log_msgs = replay_process(cfg, lr) + try: + log_msgs = replay_process(cfg, lr) + except Exception as e: + raise Exception("failed on segment: " + segment) from e # check to make sure openpilot is engaged in the route if cfg.proc_name == "controlsd": diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 63b87149d4..fd14ed15e2 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -43,10 +43,10 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }, { "ExperimentalLongitudinalEnabled", - tr("Experimental openpilot Longitudinal Control"), - QString("%1
%2") - .arg(tr("WARNING: openpilot longitudinal control is experimental 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 using experimental openpilot longitudinal control.")), + tr("openpilot Longitudinal Control (Alpha)"), + QString("%1

%2") + .arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) + .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.")), "../assets/offroad/icon_speed_limit.png", }, { @@ -193,6 +193,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { connect(resetCalibBtn, &ButtonControl::clicked, [&]() { if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), tr("Reset"), this)) { params.remove("CalibrationParams"); + params.remove("LiveTorqueParameters"); } }); addItem(resetCalibBtn); diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 10300da55c..de3b64cffd 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -374,7 +374,6 @@ void CameraWidget::vipcThread() { clearFrames(); auto streams = VisionIpcClient::getAvailableStreams(stream_name, false); if (streams.empty()) { - qWarning() << "VisionIPC connected, but no streams available"; QThread::msleep(100); continue; } diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 5f3511d3cc..2e4e833e07 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -992,22 +992,10 @@ This may take up to a minute. Experimental Mode Experimenteller Modus - - Experimental openpilot Longitudinal Control - Experimenteller Openpilot Tempomat - - - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - WARNUNG: Der Openpilot Tempomat ist für dieses Auto experimentell und deaktiviert den Notbremsassistenten. - Disengage on Accelerator Pedal Bei Gasbetätigung ausschalten - - 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 using experimental openpilot longitudinal control. - Bei diesem auto wird standardmäßig der im Auto eingebaute adaptive Tempomat anstelle des Openpilot Tempomats benutzt. Aktiviere diesen Schalter, um zum Openpilot Tempomaten zu wechseln. Es ist empfohlen den Experimentellen Modus bei Nutzung des Openpilot Tempomats zu aktivieren. - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: Openpilot fährt standardmäßig im <b>entspannten Modus</b>. Der Experimentelle Modus aktiviert<b>Alpha-level Funktionen</b>, die noch nicht für den entspannten Modus bereit sind. Die experimentellen Funktionen sind die Folgenden: @@ -1044,6 +1032,18 @@ This may take up to a minute. Enable experimental longitudinal control to allow Experimental mode. Aktiviere den experimentellen Openpilot Tempomaten für experimentelle Funktionen. + + openpilot Longitudinal Control (Alpha) + + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + + Updater diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 1e0223606b..06435d3b41 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -962,10 +962,6 @@ This may take up to a minute. Upload data from the driver facing camera and help improve the driver monitoring algorithm. 車内カメラの映像をアップロードし、ドライバー監視システムのアルゴリズムの向上に役立てます。 - - Experimental openpilot Longitudinal Control - 実験段階のopenpilotによるアクセル制御 - Disengage on Accelerator Pedal アクセルを踏むと openpilot を中断 @@ -994,14 +990,6 @@ This may take up to a minute. Experimental Mode 実験モード - - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 警告: この車種でのopenpilotによるアクセル制御は実験段階であり、衝突被害軽減ブレーキ(AEB)を無効化します。 - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - openpilotはこの車の場合、車に内蔵されているACCを標準で利用します。この機能を有効にすることで実験段階のopenpilotによるアクセル制御を利用できます。実験モードと合わせて利用することをお勧めします。 - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 @@ -1038,6 +1026,18 @@ This may take up to a minute. Enable experimental longitudinal control to allow Experimental mode. 実験段階のopenpilotによるアクセル制御を有効にしてください。 + + openpilot Longitudinal Control (Alpha) + + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + + Updater diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index be11f2efde..b55a56a061 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -963,10 +963,6 @@ This may take up to a minute. Upload data from the driver facing camera and help improve the driver monitoring algorithm. 운전자 카메라에서 데이터를 업로드하고 운전자 모니터링 알고리즘을 개선합니다. - - Experimental openpilot Longitudinal Control - openpilot 롱컨트롤 (실험적) - Disengage on Accelerator Pedal 가속페달 조작시 해제 @@ -995,14 +991,6 @@ This may take up to a minute. Experimental Mode 실험적 모드 - - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 경고: openpilot 롱컨트롤은 실험적인 기능으로 차량의 자동긴급제동(AEB)를 비활성화합니다. - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - 이 차량은 openpilot 롱컨트롤 대신 차량의 내장 ACC로 기본 설정됩니다. openpilot 롱컨트롤을 사용하려면 이 옵션을 활성화하세요. 실험적 openpilot 롱컨트롤을 사용하는 경우 실험적 모드를 활성화 하세요. - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: openpilot은 기본적으로 <b>안정적 모드</b>로 주행합니다. 실험적 모드는 안정적 모드에 준비되지 않은 <b>알파 수준 기능</b>을 활성화 합니다. 실험적 모드의 특징은 아래에 나열되어 있습니다 @@ -1039,6 +1027,18 @@ This may take up to a minute. Enable experimental longitudinal control to allow Experimental mode. 실험적 롱컨트롤을 사용하려면 실험적 모드를 활성화 하세요. + + openpilot Longitudinal Control (Alpha) + openpilot 롱컨트롤 (알파) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + 경고: openpilot 롱컨트롤은 알파 기능으로 차량의 자동긴급제동(AEB)를 비활성화합니다. + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + 이 차량은 openpilot 롱컨트롤 대신 차량의 내장 ACC로 기본 설정됩니다. openpilot 롱컨트롤으로 전환하려면 이 기능을 활성화하세요. openpilot 롱컨트롤 알파를 활성화하는경우 실험적 모드 활성화를 권장합니다. + Updater diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 8dc3a9830d..34fbc8abf0 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -967,10 +967,6 @@ Isso pode levar até um minuto. Upload data from the driver facing camera and help improve the driver monitoring algorithm. Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor. - - Experimental openpilot Longitudinal Control - Controle longitudinal experimental openpilot - Disengage on Accelerator Pedal Desacionar com Pedal do Acelerador @@ -999,14 +995,6 @@ Isso pode levar até um minuto. Experimental Mode Modo Experimental - - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - ATENÇÃO: o controle longitudinal do openpilot é experimental para este carro e desativará a Frenagem Automática de Emergência (AEB). - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - Neste carro o penpilot por padrão utiliza o ACC nativo do veículo ao invés de controlar longitudinalmente. Ative isto para mudar para o controle longitudinal do openpilot. Ativar o Modo Experimental é recomendado quando em uso do controle longitudinal experimental do openpilot. - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-alfa</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: @@ -1043,6 +1031,18 @@ Isso pode levar até um minuto. Enable experimental longitudinal control to allow Experimental mode. Ative o controle longitudinal experimental para permitir o modo Experimental. + + openpilot Longitudinal Control (Alpha) + Controle Longitudinal openpilot (Alpha) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + AVISO: o controle longitudinal openpilot está em alfa para este carro e desativará a Frenagem Automática de Emergência (AEB). + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + Neste carro, o openpilot tem como padrão o ACC embutido do carro em vez do controle longitudinal do openpilot. Habilite isso para alternar para o controle longitudinal openpilot. Recomenda-se ativar o modo Experimental ao ativar o alfa de controle longitudinal openpilot. + Updater diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index cfc055ac98..7cacc72f1c 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -960,10 +960,6 @@ This may take up to a minute. Upload data from the driver facing camera and help improve the driver monitoring algorithm. 上传驾驶员摄像头的数据,帮助改进驾驶员监控算法。 - - Experimental openpilot Longitudinal Control - 试验性的openpilot纵向控制 - Disengage on Accelerator Pedal 踩油门时取消控制 @@ -992,14 +988,6 @@ This may take up to a minute. Experimental Mode 测试模式 - - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 警告: 此车辆的openpilot纵向控制是试验性功能,且将禁用AEB自动刹车功能。 - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - 针对此车辆,openpilot默认使用车辆自带的ACC,而非openpilot的纵向控制。启用此选项将切换到openpilot纵向控制。当使用试验性的openpilot纵向控制时,建议同时启用试验模式。 - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括: @@ -1036,6 +1024,18 @@ This may take up to a minute. Enable experimental longitudinal control to allow Experimental mode. 启用试验性的纵向控制,以便允许使用试验模式。 + + openpilot Longitudinal Control (Alpha) + + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 83db0b7301..e559ca88a6 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -962,10 +962,6 @@ This may take up to a minute. Upload data from the driver facing camera and help improve the driver monitoring algorithm. 上傳駕駛監控的錄像來協助我們提升駕駛監控的準確率。 - - Experimental openpilot Longitudinal Control - 使用 openpilot 縱向控制(實驗) - Disengage on Accelerator Pedal 油門取消控車 @@ -994,14 +990,6 @@ This may take up to a minute. Experimental Mode 實驗模式 - - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - 警告:openpilot 縱向控制在這輛車上仍屬實驗性質,啟用後會喪失自動緊急煞車 (AEB) 功能。 - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - 在本車輛中,openpilot預設將使用原車內建的ACC系統,而非openpilot縱向控制。開啟此開關來啟用openpilot縱向控制,使用此選項時建議一併啟用實驗模式。 - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: openpilot 預設以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: @@ -1038,6 +1026,18 @@ This may take up to a minute. Enable experimental longitudinal control to allow Experimental mode. 啟用實驗性縱向控制以使用實驗模式。 + + openpilot Longitudinal Control (Alpha) + + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + + Updater diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index e15d4c3433..5964eb49dc 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -19,7 +19,7 @@ def update_translations(vanish=False, plural_only=None, translations_dir=TRANSLA for file in translation_files.values(): tr_file = os.path.join(translations_dir, f"{file}.ts") - args = f"lupdate -locations none -recursive {UI_DIR} -ts {tr_file}" + args = f"lupdate -locations none -recursive {UI_DIR} -ts {tr_file} -I {BASEDIR}" if vanish: args += " -no-obsolete" if file in plural_only: diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 7ee3738057..9ef5d89a4e 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -157,9 +157,6 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setHighConversionGain(frame_data.high_conversion_gain); framed.setMeasuredGreyFraction(frame_data.measured_grey_fraction); framed.setTargetGreyFraction(frame_data.target_grey_fraction); - framed.setLensPos(frame_data.lens_pos); - framed.setLensErr(frame_data.lens_err); - framed.setLensTruePos(frame_data.lens_true_pos); framed.setProcessingTime(frame_data.processing_time); const float ev = c->cur_ev[frame_data.frame_id % 3]; diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 088b9f7939..07d1291a2a 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -66,11 +66,6 @@ typedef struct FrameMetadata { float measured_grey_fraction; float target_grey_fraction; - // Focus - unsigned int lens_pos; - float lens_err; - float lens_true_pos; - float processing_time; } FrameMetadata; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 92b3bde413..94116b11cd 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -1175,7 +1175,7 @@ void CameraState::set_camera_exposure(float grey_frac) { // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD uint32_t hcg_time = exposure_time; uint32_t lcg_time = hcg_time; - uint32_t spd_time = exposure_time_max + VS_TIME_MAX_OX03C10; + uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (exposure_time_max + VS_TIME_MAX_OX03C10) / 3), exposure_time_max + VS_TIME_MAX_OX03C10); uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; diff --git a/system/camerad/cameras/sensor2_i2c.h b/system/camerad/cameras/sensor2_i2c.h index 83fcb8f7a9..4db4bfd4d8 100644 --- a/system/camerad/cameras/sensor2_i2c.h +++ b/system/camerad/cameras/sensor2_i2c.h @@ -58,7 +58,7 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { // SC ctrl {0x3001, 0x03}, // io_pad_oen - {0x3002, 0xf8}, // io_pad_oen + {0x3002, 0xfc}, // io_pad_oen {0x3005, 0x80}, // io_pad_out {0x3007, 0x01}, // io_pad_sel {0x3008, 0x80}, // io_pad_sel @@ -85,6 +85,9 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { {0x3882, 0x8}, {0x3883, 0x0D}, {0x3836, 0x1F}, {0x3837, 0x40}, + {0x3892, 0x44}, + {0x3823, 0x48}, + {0x3012, 0x41}, // SC_PHY_CTRL = 4 lane MIPI {0x3020, 0x05}, // SC_CTRL_20 @@ -179,8 +182,8 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { {0x3820, 0x04}, {0x3821, 0x19}, - {0x3832, 0x00}, - {0x3834, 0x00}, + {0x3832, 0xF0}, + {0x3834, 0xF0}, {0x384c, 0x02}, {0x384d, 0x0d}, {0x3850, 0x00}, diff --git a/system/hardware/tici/amplifier.py b/system/hardware/tici/amplifier.py old mode 100644 new mode 100755 index 840d575fdf..9725cd689c --- a/system/hardware/tici/amplifier.py +++ b/system/hardware/tici/amplifier.py @@ -129,3 +129,12 @@ class Amplifier: self.set_config(config) self.set_global_shutdown(amp_disabled=False) + + +if __name__ == "__main__": + with open("/sys/firmware/devicetree/base/model") as f: + model = f.read().strip('\x00') + model = model.split('comma ')[-1] + + amp = Amplifier() + amp.initialize_configuration(model) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 9c1cc930c1..46db097da1 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -452,6 +452,12 @@ class Tici(HardwareBase): # Allow thermald to write engagement status to kmsg os.system("sudo chmod a+w /dev/kmsg") + # TODO: remove the if once agnos 7 ships + # Turn off fan, turned on by the ABL + if os.path.exists('/sys/class/gpio/gpio49/'): + gpio_init(GPIO.SOM_ST_IO, True) + gpio_set(GPIO.SOM_ST_IO, 0) + # *** IRQ config *** # move these off the default core diff --git a/system/hardware/tici/pins.py b/system/hardware/tici/pins.py index fe31b9311d..082a402f15 100644 --- a/system/hardware/tici/pins.py +++ b/system/hardware/tici/pins.py @@ -10,6 +10,8 @@ class GPIO: STM_RST_N = 124 STM_BOOT0 = 134 + SOM_ST_IO = 49 + LTE_RST_N = 50 LTE_PWRKEY = 116 LTE_BOOT = 52 diff --git a/system/loggerd/bootlog.cc b/system/loggerd/bootlog.cc index 4deb5b41f3..becd293c02 100644 --- a/system/loggerd/bootlog.cc +++ b/system/loggerd/bootlog.cc @@ -2,6 +2,7 @@ #include #include "cereal/messaging/messaging.h" +#include "common/params.h" #include "common/swaglog.h" #include "system/loggerd/logger.h" @@ -48,7 +49,8 @@ static kj::Array build_boot_log() { } int main(int argc, char** argv) { - const std::string path = LOG_ROOT + "/boot/" + logger_get_route_name(); + const std::string timestr = logger_get_route_name(); + const std::string path = LOG_ROOT + "/boot/" + timestr; LOGW("bootlog to %s", path.c_str()); // Open bootlog @@ -61,5 +63,8 @@ int main(int argc, char** argv) { // Write bootlog file.write(build_boot_log().asBytes()); + // Write out bootlog param to match routes with bootlog + Params().put("CurrentBootlog", timestr.c_str()); + return 0; } diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 54098d13d0..f39b8bb9d6 100755 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -86,7 +86,7 @@ class TestLoggerd(unittest.TestCase): params.clear_all() for k, _, v in fake_params: params.put(k, v) - params.put("LaikadEphemerisV2", "abc") + params.put("LaikadEphemerisV3", "abc") lr = list(LogReader(str(self._gen_bootlog()))) initData = lr[0].initData @@ -103,14 +103,14 @@ class TestLoggerd(unittest.TestCase): # check params logged_params = {entry.key: entry.value for entry in initData.params.entries} - expected_params = set(k for k, _, __ in fake_params) | {'LaikadEphemerisV2'} + expected_params = set(k for k, _, __ in fake_params) | {'LaikadEphemerisV3'} assert set(logged_params.keys()) == expected_params, set(logged_params.keys()) ^ expected_params - assert logged_params['LaikadEphemerisV2'] == b'', f"DONT_LOG param value was logged: {repr(logged_params['LaikadEphemerisV2'])}" + assert logged_params['LaikadEphemerisV3'] == b'', f"DONT_LOG param value was logged: {repr(logged_params['LaikadEphemerisV3'])}" for param_key, initData_key, v in fake_params: self.assertEqual(getattr(initData, initData_key), v) self.assertEqual(logged_params[param_key].decode(), v) - params.put("LaikadEphemerisV2", "") + params.put("LaikadEphemerisV3", "") def test_rotation(self): os.environ["LOGGERD_TEST"] = "1" diff --git a/system/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py index c6fe33129a..21b67271d6 100755 --- a/system/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import os +import glob import time import unittest import numpy as np @@ -7,7 +8,7 @@ from collections import namedtuple, defaultdict import cereal.messaging as messaging from cereal import log -from system.hardware import TICI, HARDWARE +from system.hardware import TICI from selfdrive.manager.process_config import managed_processes BMX = { @@ -70,7 +71,6 @@ ALL_SENSORS = { } } -LSM_IRQ = 336 def get_irq_count(irq: int): with open(f"/sys/kernel/irq/{irq}/per_cpu_count") as f: @@ -101,9 +101,6 @@ class TestSensord(unittest.TestCase): if not TICI: raise unittest.SkipTest - # make sure gpiochip0 is readable - HARDWARE.initialize_hardware() - # enable LSM self test os.environ["LSM_SELF_TEST"] = "1" @@ -114,6 +111,15 @@ class TestSensord(unittest.TestCase): time.sleep(3) cls.sample_secs = 10 cls.events = read_sensor_events(cls.sample_secs) + + # determine sensord's irq + cls.sensord_irq = None + for fn in glob.glob('/sys/kernel/irq/*/actions'): + with open(fn) as f: + if "sensord" in f.read(): + cls.sensord_irq = int(fn.split('/')[-2]) + break + assert cls.sensord_irq is not None finally: # teardown won't run if this doesn't succeed managed_processes["sensord"].stop() @@ -121,8 +127,6 @@ class TestSensord(unittest.TestCase): @classmethod def tearDownClass(cls): managed_processes["sensord"].stop() - if "LSM_SELF_TEST" in os.environ: - del os.environ['LSM_SELF_TEST'] def tearDown(self): managed_processes["sensord"].stop() @@ -250,9 +254,9 @@ class TestSensord(unittest.TestCase): time.sleep(3) # read /proc/interrupts to verify interrupts are received - state_one = get_irq_count(LSM_IRQ) + state_one = get_irq_count(self.sensord_irq) time.sleep(1) - state_two = get_irq_count(LSM_IRQ) + state_two = get_irq_count(self.sensord_irq) error_msg = f"no interrupts received after sensord start!\n{state_one} {state_two}" assert state_one != state_two, error_msg @@ -261,9 +265,9 @@ class TestSensord(unittest.TestCase): time.sleep(1) # read /proc/interrupts to verify no more interrupts are received - state_one = get_irq_count(LSM_IRQ) + state_one = get_irq_count(self.sensord_irq) time.sleep(1) - state_two = get_irq_count(LSM_IRQ) + state_two = get_irq_count(self.sensord_irq) assert state_one == state_two, "Interrupts received after sensord stop!" diff --git a/system/ubloxd/generated/ubx.cpp b/system/ubloxd/generated/ubx.cpp index 34fe1e52ca..81b82ccafc 100644 --- a/system/ubloxd/generated/ubx.cpp +++ b/system/ubloxd/generated/ubx.cpp @@ -40,6 +40,11 @@ void ubx_t::_read() { m_body = new rxm_sfrbx_t(m__io, this, m__root); break; } + case 309: { + n_body = false; + m_body = new nav_sat_t(m__io, this, m__root); + break; + } case 2571: { n_body = false; m_body = new mon_hw2_t(m__io, this, m__root); @@ -70,9 +75,9 @@ void ubx_t::_clean_up() { ubx_t::rxm_rawx_t::rxm_rawx_t(kaitai::kstream* p__io, ubx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { m__parent = p__parent; m__root = p__root; - m_measurements = 0; - m__raw_measurements = 0; - m__io__raw_measurements = 0; + m_meas = 0; + m__raw_meas = 0; + m__io__raw_meas = 0; try { _read(); @@ -89,15 +94,15 @@ void ubx_t::rxm_rawx_t::_read() { m_num_meas = m__io->read_u1(); m_rec_stat = m__io->read_u1(); m_reserved1 = m__io->read_bytes(3); - m__raw_measurements = new std::vector(); - m__io__raw_measurements = new std::vector(); - m_measurements = new std::vector(); - const int l_measurements = num_meas(); - for (int i = 0; i < l_measurements; i++) { - m__raw_measurements->push_back(m__io->read_bytes(32)); - kaitai::kstream* io__raw_measurements = new kaitai::kstream(m__raw_measurements->at(m__raw_measurements->size() - 1)); - m__io__raw_measurements->push_back(io__raw_measurements); - m_measurements->push_back(new meas_t(io__raw_measurements, this, m__root)); + m__raw_meas = new std::vector(); + m__io__raw_meas = new std::vector(); + m_meas = new std::vector(); + const int l_meas = num_meas(); + for (int i = 0; i < l_meas; i++) { + m__raw_meas->push_back(m__io->read_bytes(32)); + kaitai::kstream* io__raw_meas = new kaitai::kstream(m__raw_meas->at(m__raw_meas->size() - 1)); + m__io__raw_meas->push_back(io__raw_meas); + m_meas->push_back(new measurement_t(io__raw_meas, this, m__root)); } } @@ -106,24 +111,24 @@ ubx_t::rxm_rawx_t::~rxm_rawx_t() { } void ubx_t::rxm_rawx_t::_clean_up() { - if (m__raw_measurements) { - delete m__raw_measurements; m__raw_measurements = 0; + if (m__raw_meas) { + delete m__raw_meas; m__raw_meas = 0; } - if (m__io__raw_measurements) { - for (std::vector::iterator it = m__io__raw_measurements->begin(); it != m__io__raw_measurements->end(); ++it) { + if (m__io__raw_meas) { + for (std::vector::iterator it = m__io__raw_meas->begin(); it != m__io__raw_meas->end(); ++it) { delete *it; } - delete m__io__raw_measurements; m__io__raw_measurements = 0; + delete m__io__raw_meas; m__io__raw_meas = 0; } - if (m_measurements) { - for (std::vector::iterator it = m_measurements->begin(); it != m_measurements->end(); ++it) { + if (m_meas) { + for (std::vector::iterator it = m_meas->begin(); it != m_meas->end(); ++it) { delete *it; } - delete m_measurements; m_measurements = 0; + delete m_meas; m_meas = 0; } } -ubx_t::rxm_rawx_t::meas_t::meas_t(kaitai::kstream* p__io, ubx_t::rxm_rawx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { +ubx_t::rxm_rawx_t::measurement_t::measurement_t(kaitai::kstream* p__io, ubx_t::rxm_rawx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { m__parent = p__parent; m__root = p__root; @@ -135,7 +140,7 @@ ubx_t::rxm_rawx_t::meas_t::meas_t(kaitai::kstream* p__io, ubx_t::rxm_rawx_t* p__ } } -void ubx_t::rxm_rawx_t::meas_t::_read() { +void ubx_t::rxm_rawx_t::measurement_t::_read() { m_pr_mes = m__io->read_f8le(); m_cp_mes = m__io->read_f8le(); m_do_mes = m__io->read_f4le(); @@ -152,11 +157,11 @@ void ubx_t::rxm_rawx_t::meas_t::_read() { m_reserved3 = m__io->read_bytes(1); } -ubx_t::rxm_rawx_t::meas_t::~meas_t() { +ubx_t::rxm_rawx_t::measurement_t::~measurement_t() { _clean_up(); } -void ubx_t::rxm_rawx_t::meas_t::_clean_up() { +void ubx_t::rxm_rawx_t::measurement_t::_clean_up() { } ubx_t::rxm_sfrbx_t::rxm_sfrbx_t(kaitai::kstream* p__io, ubx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { @@ -198,6 +203,89 @@ void ubx_t::rxm_sfrbx_t::_clean_up() { } } +ubx_t::nav_sat_t::nav_sat_t(kaitai::kstream* p__io, ubx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + m_svs = 0; + m__raw_svs = 0; + m__io__raw_svs = 0; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void ubx_t::nav_sat_t::_read() { + m_itow = m__io->read_u4le(); + m_version = m__io->read_u1(); + m_num_svs = m__io->read_u1(); + m_reserved = m__io->read_bytes(2); + m__raw_svs = new std::vector(); + m__io__raw_svs = new std::vector(); + m_svs = new std::vector(); + const int l_svs = num_svs(); + for (int i = 0; i < l_svs; i++) { + m__raw_svs->push_back(m__io->read_bytes(12)); + kaitai::kstream* io__raw_svs = new kaitai::kstream(m__raw_svs->at(m__raw_svs->size() - 1)); + m__io__raw_svs->push_back(io__raw_svs); + m_svs->push_back(new nav_t(io__raw_svs, this, m__root)); + } +} + +ubx_t::nav_sat_t::~nav_sat_t() { + _clean_up(); +} + +void ubx_t::nav_sat_t::_clean_up() { + if (m__raw_svs) { + delete m__raw_svs; m__raw_svs = 0; + } + if (m__io__raw_svs) { + for (std::vector::iterator it = m__io__raw_svs->begin(); it != m__io__raw_svs->end(); ++it) { + delete *it; + } + delete m__io__raw_svs; m__io__raw_svs = 0; + } + if (m_svs) { + for (std::vector::iterator it = m_svs->begin(); it != m_svs->end(); ++it) { + delete *it; + } + delete m_svs; m_svs = 0; + } +} + +ubx_t::nav_sat_t::nav_t::nav_t(kaitai::kstream* p__io, ubx_t::nav_sat_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void ubx_t::nav_sat_t::nav_t::_read() { + m_gnss_id = static_cast(m__io->read_u1()); + m_sv_id = m__io->read_u1(); + m_cno = m__io->read_u1(); + m_elev = m__io->read_s1(); + m_azim = m__io->read_s2le(); + m_pr_res = m__io->read_s2le(); + m_flags = m__io->read_u4le(); +} + +ubx_t::nav_sat_t::nav_t::~nav_t() { + _clean_up(); +} + +void ubx_t::nav_sat_t::nav_t::_clean_up() { +} + ubx_t::nav_pvt_t::nav_pvt_t(kaitai::kstream* p__io, ubx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { m__parent = p__parent; m__root = p__root; diff --git a/system/ubloxd/generated/ubx.h b/system/ubloxd/generated/ubx.h index 6be4ce8c4b..022108489f 100644 --- a/system/ubloxd/generated/ubx.h +++ b/system/ubloxd/generated/ubx.h @@ -16,6 +16,7 @@ class ubx_t : public kaitai::kstruct { public: class rxm_rawx_t; class rxm_sfrbx_t; + class nav_sat_t; class nav_pvt_t; class mon_hw2_t; class mon_hw_t; @@ -42,7 +43,7 @@ public: class rxm_rawx_t : public kaitai::kstruct { public: - class meas_t; + class measurement_t; rxm_rawx_t(kaitai::kstream* p__io, ubx_t* p__parent = 0, ubx_t* p__root = 0); @@ -53,18 +54,18 @@ public: public: ~rxm_rawx_t(); - class meas_t : public kaitai::kstruct { + class measurement_t : public kaitai::kstruct { public: - meas_t(kaitai::kstream* p__io, ubx_t::rxm_rawx_t* p__parent = 0, ubx_t* p__root = 0); + measurement_t(kaitai::kstream* p__io, ubx_t::rxm_rawx_t* p__parent = 0, ubx_t* p__root = 0); private: void _read(); void _clean_up(); public: - ~meas_t(); + ~measurement_t(); private: double m_pr_mes; @@ -110,11 +111,11 @@ public: uint8_t m_num_meas; uint8_t m_rec_stat; std::string m_reserved1; - std::vector* m_measurements; + std::vector* m_meas; ubx_t* m__root; ubx_t* m__parent; - std::vector* m__raw_measurements; - std::vector* m__io__raw_measurements; + std::vector* m__raw_meas; + std::vector* m__io__raw_meas; public: double rcv_tow() const { return m_rcv_tow; } @@ -123,11 +124,11 @@ public: uint8_t num_meas() const { return m_num_meas; } uint8_t rec_stat() const { return m_rec_stat; } std::string reserved1() const { return m_reserved1; } - std::vector* measurements() const { return m_measurements; } + std::vector* meas() const { return m_meas; } ubx_t* _root() const { return m__root; } ubx_t* _parent() const { return m__parent; } - std::vector* _raw_measurements() const { return m__raw_measurements; } - std::vector* _io__raw_measurements() const { return m__io__raw_measurements; } + std::vector* _raw_meas() const { return m__raw_meas; } + std::vector* _io__raw_meas() const { return m__io__raw_meas; } }; class rxm_sfrbx_t : public kaitai::kstruct { @@ -170,6 +171,79 @@ public: ubx_t* _parent() const { return m__parent; } }; + class nav_sat_t : public kaitai::kstruct { + + public: + class nav_t; + + nav_sat_t(kaitai::kstream* p__io, ubx_t* p__parent = 0, ubx_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~nav_sat_t(); + + class nav_t : public kaitai::kstruct { + + public: + + nav_t(kaitai::kstream* p__io, ubx_t::nav_sat_t* p__parent = 0, ubx_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~nav_t(); + + private: + gnss_type_t m_gnss_id; + uint8_t m_sv_id; + uint8_t m_cno; + int8_t m_elev; + int16_t m_azim; + int16_t m_pr_res; + uint32_t m_flags; + ubx_t* m__root; + ubx_t::nav_sat_t* m__parent; + + public: + gnss_type_t gnss_id() const { return m_gnss_id; } + uint8_t sv_id() const { return m_sv_id; } + uint8_t cno() const { return m_cno; } + int8_t elev() const { return m_elev; } + int16_t azim() const { return m_azim; } + int16_t pr_res() const { return m_pr_res; } + uint32_t flags() const { return m_flags; } + ubx_t* _root() const { return m__root; } + ubx_t::nav_sat_t* _parent() const { return m__parent; } + }; + + private: + uint32_t m_itow; + uint8_t m_version; + uint8_t m_num_svs; + std::string m_reserved; + std::vector* m_svs; + ubx_t* m__root; + ubx_t* m__parent; + std::vector* m__raw_svs; + std::vector* m__io__raw_svs; + + public: + uint32_t itow() const { return m_itow; } + uint8_t version() const { return m_version; } + uint8_t num_svs() const { return m_num_svs; } + std::string reserved() const { return m_reserved; } + std::vector* svs() const { return m_svs; } + ubx_t* _root() const { return m__root; } + ubx_t* _parent() const { return m__parent; } + std::vector* _raw_svs() const { return m__raw_svs; } + std::vector* _io__raw_svs() const { return m__io__raw_svs; } + }; + class nav_pvt_t : public kaitai::kstruct { public: diff --git a/system/ubloxd/tests/ublox.py b/system/ubloxd/tests/ublox.py index 80abc64e2c..0d271ad785 100644 --- a/system/ubloxd/tests/ublox.py +++ b/system/ubloxd/tests/ublox.py @@ -48,6 +48,7 @@ MSG_NAV_TIMEGPS = 0x20 MSG_NAV_TIMEUTC = 0x21 MSG_NAV_CLOCK = 0x22 MSG_NAV_SVINFO = 0x30 +MSG_NAV_SAT = 0x35 MSG_NAV_AOPSTATUS = 0x60 MSG_NAV_DGPS = 0x31 MSG_NAV_DOP = 0x04 diff --git a/system/ubloxd/ublox_msg.cc b/system/ubloxd/ublox_msg.cc index 83e64b7ddc..a01187d560 100644 --- a/system/ubloxd/ublox_msg.cc +++ b/system/ubloxd/ublox_msg.cc @@ -65,22 +65,6 @@ inline bool UbloxMsgParser::valid_so_far() { return true; } -inline uint16_t UbloxMsgParser::get_glonass_year(uint8_t N4, uint16_t Nt) { - // convert time to year (conversion from A3.1.3) - int J = 0; - if (1 <= Nt && Nt <= 366) { - J = 1; - } else if (367 <= Nt && Nt <= 731) { - J = 2; - } else if (732 <= Nt && Nt <= 1096) { - J = 3; - } else if (1097 <= Nt && Nt <= 1461) { - J = 4; - } - uint16_t year = 1996 + 4*(N4 -1) + (J - 1); - return year; -} - bool UbloxMsgParser::add_data(float log_time, const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed) { last_log_time = log_time; int needed = needed_bytes(); @@ -203,6 +187,7 @@ kj::Array UbloxMsgParser::parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *m int iode_s2 = 0; int iode_s3 = 0; int iodc_lsb = 0; + int week; // Subframe 1 { @@ -210,7 +195,14 @@ kj::Array UbloxMsgParser::parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *m gps_t subframe(&stream); gps_t::subframe_1_t* subframe_1 = static_cast(subframe.body()); - eph.setGpsWeek(subframe_1->week_no()); + // Each message is incremented to be greater or equal than week 1877 (2015-12-27). + // To skip this use the current_time argument + week = subframe_1->week_no(); + week += 1024; + if (week < 1877) { + week += 1024; + } + //eph.setGpsWeek(subframe_1->week_no()); eph.setTgd(subframe_1->t_gd() * pow(2, -31)); eph.setToc(subframe_1->t_oc() * pow(2, 4)); eph.setAf2(subframe_1->af_2() * pow(2, -55)); @@ -227,6 +219,12 @@ kj::Array UbloxMsgParser::parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *m gps_t subframe(&stream); gps_t::subframe_2_t* subframe_2 = static_cast(subframe.body()); + // GPS week refers to current week, the ephemeris can be valid for the next + // if toe equals 0, this can be verified by the TOW count if it is within the + // last 2 hours of the week (gps ephemeris valid for 4hours) + if (subframe_2->t_oe() == 0 and subframe.how()->tow_count()*6 >= (SECS_IN_WEEK - 2*SECS_IN_HR)){ + week += 1; + } eph.setCrs(subframe_2->c_rs() * pow(2, -5)); eph.setDeltaN(subframe_2->delta_n() * pow(2, -43) * gpsPi); eph.setM0(subframe_2->m_0() * pow(2, -31) * gpsPi); @@ -256,6 +254,9 @@ kj::Array UbloxMsgParser::parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *m iode_s3 = subframe_3->iode(); } + eph.setToeWeek(week); + eph.setTocWeek(week); + gps_subframes[msg->sv_id()].clear(); if (iodc_lsb != iode_s2 || iodc_lsb != iode_s3) { // data set cutover, reject ephemeris @@ -329,7 +330,10 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ MessageBuilder msg_builder; auto eph = msg_builder.initEvent().initUbloxGnss().initGlonassEphemeris(); eph.setSvId(msg->sv_id()); + eph.setFreqNum(msg->freq_id() - 7); + uint16_t current_day = 0; + uint16_t tk = 0; // string number 1 { @@ -338,7 +342,8 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ glonass_t::string_1_t* data = static_cast(gl_stream.data()); eph.setP1(data->p1()); - eph.setTk(data->t_k()); + tk = data->t_k(); + eph.setTkDEPRECATED(tk); eph.setXVel(data->x_vel() * pow(2, -20)); eph.setXAccel(data->x_accel() * pow(2, -30)); eph.setX(data->x() * pow(2, -11)); @@ -379,6 +384,7 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ glonass_t::string_4_t* data = static_cast(gl_stream.data()); current_day = data->n_t(); + eph.setNt(current_day); eph.setTauN(data->tau_n() * pow(2, -30)); eph.setDeltaTauN(data->delta_tau_n() * pow(2, -30)); eph.setAge(data->e_n()); @@ -398,27 +404,9 @@ kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_ // string5 parsing is only needed to get the year, this can be removed and // the year can be fetched later in laika (note rollovers and leap year) - uint8_t n_4 = data->n_4(); - uint16_t year = get_glonass_year(n_4, current_day); - if (current_day > 1461) { - // impossible day within last 4 year, reject ephemeris - // TODO: check if this can be detected via hamming code - LOGE("INVALID DATA: current day out of range: %d, %d", current_day, n_4); - glonass_strings[msg->sv_id()].clear(); - return kj::Array(); - } - - uint16_t last_leap_year = 1996 + 4*(n_4-1); - uint16_t days_till_this_year = (year - last_leap_year)*365; - if (days_till_this_year != 0) { - days_till_this_year++; - } - - eph.setYear(year); - eph.setDayInYear(current_day - days_till_this_year); - eph.setHour((eph.getTk()>>7) & 0x1F); - eph.setMinute((eph.getTk()>>1) & 0x3F); - eph.setSecond((eph.getTk() & 0x1) * 30); + eph.setN4(data->n_4()); + int tk_seconds = SECS_IN_HR * ((tk>>7) & 0x1F) + SECS_IN_MIN * ((tk>>1) & 0x3F) + (tk & 0x1) * 30; + eph.setTkSeconds(tk_seconds); } glonass_strings[msg->freq_id()].clear(); @@ -446,7 +434,7 @@ kj::Array UbloxMsgParser::gen_rxm_rawx(ubx_t::rxm_rawx_t *msg) { mr.setGpsWeek(msg->week()); auto mb = mr.initMeasurements(msg->num_meas()); - auto measurements = *msg->measurements(); + auto measurements = *msg->meas(); for(int8_t i = 0; i < msg->num_meas(); i++) { mb[i].setSvId(measurements[i]->sv_id()); mb[i].setPseudorange(measurements[i]->pr_mes()); @@ -475,6 +463,22 @@ kj::Array UbloxMsgParser::gen_rxm_rawx(ubx_t::rxm_rawx_t *msg) { return capnp::messageToFlatArray(msg_builder); } +kj::Array UbloxMsgParser::gen_nav_sat(ubx_t::nav_sat_t *msg) { + MessageBuilder msg_builder; + auto sr = msg_builder.initEvent().initUbloxGnss().initSatReport(); + sr.setITow(msg->itow()); + + auto svs = sr.initSvs(msg->num_svs()); + auto svs_data = *msg->svs(); + for(int8_t i = 0; i < msg->num_svs(); i++) { + svs[i].setSvId(svs_data[i]->sv_id()); + svs[i].setGnssId(svs_data[i]->gnss_id()); + svs[i].setFlagsBitfield(svs_data[i]->flags()); + } + + return capnp::messageToFlatArray(msg_builder); +} + kj::Array UbloxMsgParser::gen_mon_hw(ubx_t::mon_hw_t *msg) { MessageBuilder msg_builder; auto hwStatus = msg_builder.initEvent().initUbloxGnss().initHwStatus(); diff --git a/system/ubloxd/ublox_msg.h b/system/ubloxd/ublox_msg.h index 06877cc50d..a52a7db3e5 100644 --- a/system/ubloxd/ublox_msg.h +++ b/system/ubloxd/ublox_msg.h @@ -15,6 +15,11 @@ using namespace std::string_literals; +const int SECS_IN_MIN = 60; +const int SECS_IN_HR = 60 * SECS_IN_MIN; +const int SECS_IN_DAY = 24 * SECS_IN_HR; +const int SECS_IN_WEEK = 7 * SECS_IN_DAY; + // protocol constants namespace ublox { const uint8_t PREAMBLE1 = 0xb5; @@ -97,12 +102,12 @@ class UbloxMsgParser { kj::Array gen_rxm_rawx(ubx_t::rxm_rawx_t *msg); kj::Array gen_mon_hw(ubx_t::mon_hw_t *msg); kj::Array gen_mon_hw2(ubx_t::mon_hw2_t *msg); + kj::Array gen_nav_sat(ubx_t::nav_sat_t *msg); private: inline bool valid_cheksum(); inline bool valid(); inline bool valid_so_far(); - inline uint16_t get_glonass_year(uint8_t N4, uint16_t Nt); kj::Array parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *msg); kj::Array parse_glonass_ephemeris(ubx_t::rxm_sfrbx_t *msg); diff --git a/system/ubloxd/ubx.ksy b/system/ubloxd/ubx.ksy index 6945680d32..02c757fe71 100644 --- a/system/ubloxd/ubx.ksy +++ b/system/ubloxd/ubx.ksy @@ -17,6 +17,7 @@ seq: 0x0215: rxm_rawx 0x0a09: mon_hw 0x0a0b: mon_hw2 + 0x0135: nav_sat instances: checksum: pos: length + 6 @@ -142,13 +143,13 @@ types: type: u1 - id: reserved1 size: 3 - - id: measurements - type: meas + - id: meas + type: measurement size: 32 repeat: expr repeat-expr: num_meas types: - meas: + measurement: seq: - id: pr_mes type: f8 @@ -179,6 +180,39 @@ types: type: u1 - id: reserved3 size: 1 + nav_sat: + seq: + - id: itow + type: u4 + - id: version + type: u1 + - id: num_svs + type: u1 + - id: reserved + size: 2 + - id: svs + type: nav + size: 12 + repeat: expr + repeat-expr: num_svs + types: + nav: + seq: + - id: gnss_id + type: u1 + enum: gnss_type + - id: sv_id + type: u1 + - id: cno + type: u1 + - id: elev + type: s1 + - id: azim + type: s2 + - id: pr_res + type: s2 + - id: flags + type: u4 nav_pvt: seq: diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index 73879ab05d..c74ab7483c 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -3,5 +3,5 @@ moc_* _cabana settings -car_fingerprint_to_dbc.json +dbc/car_fingerprint_to_dbc.json tests/_test_cabana diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 3cb5913c01..7d5129fc16 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -28,7 +28,8 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset prev_moc_path = cabana_env['QT_MOCHPREFIX'] cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' -cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbc.cc', 'dbcmanager.cc', +cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', + 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', 'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) @@ -39,5 +40,5 @@ if GetOption('test'): cabana_env.Program('tests/_test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) def generate_dbc_json(target, source, env): - env.Execute('tools/cabana/generate_dbc_json.py --out tools/cabana/car_fingerprint_to_dbc.json') + env.Execute('tools/cabana/dbc/generate_dbc_json.py --out tools/cabana/dbc/car_fingerprint_to_dbc.json') cabana_env.Command('generate_dbc_json', [], generate_dbc_json) diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index b81f7d5e79..d0576615c9 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -11,7 +11,7 @@ #include #include "tools/cabana/commands.h" -#include "tools/cabana/signaledit.h" +#include "tools/cabana/signalview.h" // BinaryView @@ -273,6 +273,10 @@ void BinaryViewModel::refresh() { row_count = can->lastMessage(msg_id).dat.size(); items.resize(row_count * column_count); } + int valid_rows = std::min(can->lastMessage(msg_id).dat.size(), row_count); + for (int i = 0; i < valid_rows * column_count; ++i) { + items[i].valid = true; + } endResetModel(); updateState(); } @@ -307,9 +311,6 @@ void BinaryViewModel::updateState() { items[i * column_count + 8].val = toHex(binary[i]); items[i * column_count + 8].bg_color = last_msg.colors[i]; } - for (int i = binary.size() * column_count; i < items.size(); ++i) { - items[i].val = "-"; - } for (int i = 0; i < items.size(); ++i) { if (i >= prev_items.size() || prev_items[i].val != items[i].val || prev_items[i].bg_color != items[i].bg_color) { @@ -376,6 +377,9 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op } } + if (!item->valid) { + painter->fillRect(option.rect, QBrush(Qt::darkGray, Qt::BDiagPattern)); + } painter->drawText(option.rect, Qt::AlignCenter, item->val); if (item->is_msb || item->is_lsb) { painter->setFont(small_font); @@ -422,7 +426,7 @@ void BinaryItemDelegate::drawBorder(QPainter* painter, const QStyleOptionViewIte painter->setClipRegion(QRegion(rc).subtracted(subtract)); if (!subtract.isEmpty()) { // fill gaps inside corners. - painter->setPen(QPen(border_color, 2)); + painter->setPen(QPen(border_color, 2, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin)); for (auto &r : subtract) { painter->drawRect(r); } diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index f677d11941..aa1f8c656b 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -6,7 +6,7 @@ #include #include -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" class BinaryItemDelegate : public QStyledItemDelegate { @@ -42,8 +42,9 @@ public: QColor bg_color = QColor(102, 86, 169, 0); bool is_msb = false; bool is_lsb = false; - QString val = "-"; + QString val; QList sigs; + bool valid = false; }; std::vector items; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 184d41ca0b..845667bad7 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -9,16 +9,20 @@ #include #include #include -#include +#include #include +#include +#include #include #include #include const int MAX_COLUMN_COUNT = 4; +static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; } + // ChartsWidget -ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { +ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), QFrame(parent) { setFrameStyle(QFrame::StyledPanel | QFrame::Plain); QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); @@ -53,6 +57,9 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { range_slider->setPageStep(60); // 1 min range_slider_action = toolbar->addWidget(range_slider); + undo_zoom_action = toolbar->addAction(utils::icon("arrow-counterclockwise"), tr("Previous zoom")); + qobject_cast(toolbar->widgetForAction(undo_zoom_action))->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + reset_zoom_action = toolbar->addAction(utils::icon("zoom-out"), tr("Reset Zoom")); qobject_cast(toolbar->widgetForAction(reset_zoom_action))->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); @@ -64,13 +71,13 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { charts_layout = new QGridLayout(); charts_layout->setSpacing(10); - QWidget *charts_container = new QWidget(this); + charts_container = new QWidget(this); QVBoxLayout *charts_main_layout = new QVBoxLayout(charts_container); charts_main_layout->setContentsMargins(0, 0, 0, 0); charts_main_layout->addLayout(charts_layout); charts_main_layout->addStretch(0); - QScrollArea *charts_scroll = new QScrollArea(this); + charts_scroll = new QScrollArea(this); charts_scroll->setFrameStyle(QFrame::NoFrame); charts_scroll->setWidgetResizable(true); charts_scroll->setWidget(charts_container); @@ -86,12 +93,15 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { range_slider->setValue(max_chart_range); updateToolBar(); + align_timer.setSingleShot(true); + QObject::connect(&align_timer, &QTimer::timeout, this, &ChartsWidget::alignCharts); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged); QObject::connect(can, &AbstractStream::updated, this, &ChartsWidget::updateState); QObject::connect(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange); QObject::connect(new_plot_btn, &QAction::triggered, this, &ChartsWidget::newChart); QObject::connect(remove_all_btn, &QAction::triggered, this, &ChartsWidget::removeAll); + QObject::connect(undo_zoom_action, &QAction::triggered, this, &ChartsWidget::zoomUndo); QObject::connect(reset_zoom_action, &QAction::triggered, this, &ChartsWidget::zoomReset); QObject::connect(&settings, &Settings::changed, this, &ChartsWidget::settingChanged); QObject::connect(dock_btn, &QAction::triggered, [this]() { @@ -108,17 +118,17 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { void ChartsWidget::eventsMerged() { { - assert(!can->liveStreaming()); QFutureSynchronizer future_synchronizer; - const auto events = can->events(); for (auto c : charts) { - future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, events, true)); + future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr)); } } - updateState(); + if (can->isPaused()) { + updateState(); + } } -void ChartsWidget::zoomIn(double min, double max) { +void ChartsWidget::setZoom(double min, double max) { zoomed_range = {min, max}; is_zoomed = zoomed_range != display_range; updateToolBar(); @@ -126,33 +136,53 @@ void ChartsWidget::zoomIn(double min, double max) { emit rangeChanged(min, max, is_zoomed); } +void ChartsWidget::zoomIn(double min, double max) { + // Save previous zoom on undo stack + if (is_zoomed) { + zoom_stack.push({zoomed_range.first, zoomed_range.second}); + } + setZoom(min, max); +} + void ChartsWidget::zoomReset() { - zoomIn(display_range.first, display_range.second); + setZoom(display_range.first, display_range.second); + zoom_stack.clear(); } -void ChartsWidget::updateState() { - if (charts.isEmpty()) return; +void ChartsWidget::zoomUndo() { + if (!zoom_stack.isEmpty()) { + auto r = zoom_stack.pop(); + setZoom(r.first, r.second); + } else { + zoomReset(); + } +} - const auto events = can->events(); - if (can->liveStreaming()) { - // appends incoming events to the end of series - for (auto c : charts) { - c->updateSeries(nullptr, events, false); +void ChartsWidget::showValueTip(double sec) { + const QRect visible_rect(-charts_container->pos(), charts_scroll->viewport()->size()); + for (auto c : charts) { + if (sec >= 0 && visible_rect.contains(QRect(c->mapTo(charts_container, QPoint(0, 0)), c->size()))) { + c->showTip(sec); + } else { + c->hideTip(); } } +} + +void ChartsWidget::updateState() { + if (charts.isEmpty()) return; const double cur_sec = can->currentSec(); if (!is_zoomed) { - double pos = (cur_sec - display_range.first) / std::max(1.0, (display_range.second - display_range.first)); + double pos = (cur_sec - display_range.first) / std::max(1.0, max_chart_range); if (pos < 0 || pos > 0.8) { display_range.first = std::max(0.0, cur_sec - max_chart_range * 0.1); } - double max_event_sec = events->empty() ? 0 : (events->back()->mono_time / 1e9 - can->routeStartTime()); - double max_sec = std::min(std::floor(display_range.first + max_chart_range), max_event_sec); + double max_sec = std::min(std::floor(display_range.first + max_chart_range), can->lastEventSecond()); 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 || cur_sec >= zoomed_range.second) { - // loop in zoommed range + // loop in zoomed range can->seekTo(zoomed_range.first); } @@ -171,9 +201,10 @@ void ChartsWidget::setMaxChartRange(int value) { void ChartsWidget::updateToolBar() { title_label->setText(tr("Charts: %1").arg(charts.size())); columns_action->setText(tr("Column: %1").arg(column_count)); - range_lb->setText(QString("Range: %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0'))); + range_lb->setText(QString("Range: %1 ").arg(utils::formatSeconds(max_chart_range))); range_lb_action->setVisible(!is_zoomed); range_slider_action->setVisible(!is_zoomed); + undo_zoom_action->setVisible(is_zoomed); reset_zoom_action->setVisible(is_zoomed); reset_zoom_action->setText(is_zoomed ? tr("Zoomin: %1-%2").arg(zoomed_range.first, 0, 'f', 1).arg(zoomed_range.second, 0, 'f', 1) : ""); remove_all_btn->setEnabled(!charts.isEmpty()); @@ -203,10 +234,11 @@ ChartView *ChartsWidget::createChart() { chart->chart()->setTheme(use_dark_theme ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight); QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); - QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); + QObject::connect(chart, &ChartView::zoomUndo, this, &ChartsWidget::zoomUndo); QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::seriesChanged); QObject::connect(chart, &ChartView::seriesAdded, this, &ChartsWidget::seriesChanged); - QObject::connect(chart, &ChartView::axisYLabelWidthChanged, this, &ChartsWidget::alignCharts); + QObject::connect(chart, &ChartView::axisYLabelWidthChanged, &align_timer, qOverload<>(&QTimer::start)); + QObject::connect(chart, &ChartView::hovered, this, &ChartsWidget::showValueTip); charts.push_back(chart); updateLayout(); return chart; @@ -308,9 +340,26 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { return false; } +bool ChartsWidget::event(QEvent *event) { + bool back_button = false; + if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent *ev = static_cast(event); + back_button = ev->button() == Qt::BackButton; + } else if (event->type() == QEvent::NativeGesture) { // MacOS emulates a back swipe on pressing the mouse back button + QNativeGestureEvent *ev = static_cast(event); + back_button = (ev->value() == 180); + } + + if (back_button) { + zoomUndo(); + return true; + } + return QFrame::event(event); +} + // ChartView -ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { +ChartView::ChartView(QWidget *parent) : tip_label(this), QChartView(nullptr, parent) { series_type = (SeriesType)settings.chart_series_type; QChart *chart = new QChart(); chart->setBackgroundVisible(false); @@ -318,7 +367,7 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { axis_y = new QValueAxis(this); chart->addAxis(axis_x, Qt::AlignBottom); chart->addAxis(axis_y, Qt::AlignLeft); - chart->legend()->layout()->setContentsMargins(16, 0, 40, 0); + chart->legend()->layout()->setContentsMargins(0, 0, 0, 0); chart->legend()->setShowToolTips(true); chart->setMargins({0, 0, 0, 0}); @@ -333,6 +382,7 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { setRenderHint(QPainter::Antialiasing); // TODO: enable zoomIn/seekTo in live streaming mode. setRubberBand(can->liveStreaming() ? QChartView::NoRubberBand : QChartView::HorizontalRubberBand); + setMouseTracking(true); QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartView::signalRemoved); QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartView::signalUpdated); @@ -443,24 +493,34 @@ void ChartView::manageSeries() { } void ChartView::resizeEvent(QResizeEvent *event) { - updatePlotArea(align_to); - int top_margin = style()->pixelMetric(QStyle::PM_LayoutTopMargin); - int spacing = style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); - int x = event->size().width() - close_btn_proxy->size().width() - style()->pixelMetric(QStyle::PM_LayoutRightMargin); - close_btn_proxy->setPos(x, top_margin); - manage_btn_proxy->setPos(x - manage_btn_proxy->size().width() - spacing, top_margin); - move_icon->setPos(style()->pixelMetric(QStyle::PM_LayoutLeftMargin), top_margin); + qreal left, top, right, bottom; + chart()->layout()->getContentsMargins(&left, &top, &right, &bottom); + move_icon->setPos(left, top); + close_btn_proxy->setPos(rect().right() - right - close_btn_proxy->size().width(), top); + int x = close_btn_proxy->pos().x() - manage_btn_proxy->size().width() - style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); + manage_btn_proxy->setPos(x, top); + chart()->legend()->setGeometry({move_icon->sceneBoundingRect().topRight(), manage_btn_proxy->sceneBoundingRect().bottomLeft()}); + if (align_to > 0) { + updatePlotArea(align_to); + } QChartView::resizeEvent(event); } -void ChartView::updatePlotArea(int left) { - QRect r = rect(); - if (align_to != left || r != background->rect()) { - align_to = left; - background->setRect(r); - chart()->legend()->setGeometry(QRect(r.left(), r.top(), r.width(), 45)); - chart()->setPlotArea(QRect(align_to, r.top() + 45, r.width() - align_to - 36, r.height() - 80)); +void ChartView::updatePlotArea(int left_pos) { + if (align_to != left_pos || rect() != background->rect()) { + align_to = left_pos; + background->setRect(rect()); + + qreal left, top, right, bottom; + chart()->layout()->getContentsMargins(&left, &top, &right, &bottom); + QSizeF x_label_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, QString::number(axis_x->max(), 'f', 2)); + x_label_size += QSizeF{5 * devicePixelRatioF(), 5 * devicePixelRatioF()}; + int adjust_top = chart()->legend()->geometry().height() + style()->pixelMetric(QStyle::PM_LayoutTopMargin); + chart()->setPlotArea(rect().adjusted(align_to + left, adjust_top + top, -x_label_size.width() / 2 - right, -x_label_size.height() - bottom)); chart()->layout()->invalidate(); + if (can->isPaused()) { + update(); + } } } @@ -481,86 +541,60 @@ void ChartView::updatePlot(double cur, double min, double max) { updateAxisY(); updateSeriesPoints(); } - scene()->invalidate({}, QGraphicsScene::ForegroundLayer); } void ChartView::updateSeriesPoints() { // Show points when zoomed in enough for (auto &s : sigs) { - auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); - auto end = std::lower_bound(begin, s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; }); - - int num_points = std::max(end - begin, 1); - int pixels_per_point = width() / num_points; - - if (series_type == SeriesType::Scatter) { - ((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 2, 8)); - } else { - s.series->setPointsVisible(pixels_per_point > 20); + auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan); + auto end = std::lower_bound(begin, s.vals.end(), axis_x->max(), xLessThan); + if (begin != end) { + int num_points = std::max((end - begin), 1); + QPointF right_pt = end == s.vals.end() ? s.vals.back() : *end; + double pixels_per_point = (chart()->mapToPosition(right_pt).x() - chart()->mapToPosition(*begin).x()) / num_points; + + if (series_type == SeriesType::Scatter) { + qreal size = std::clamp(pixels_per_point / 2.0, 2.0, 8.0); + if (s.series->useOpenGL()) { + size *= devicePixelRatioF(); + } + ((QScatterSeries *)s.series)->setMarkerSize(size); + } else { + s.series->setPointsVisible(pixels_per_point > 20); + } } } } -void ChartView::updateSeries(const cabana::Signal *sig, const std::vector *events, bool clear) { - events = events ? events : can->events(); +void ChartView::updateSeries(const cabana::Signal *sig) { for (auto &s : sigs) { if (!sig || s.sig == sig) { - if (clear) { + if (!can->liveStreaming()) { s.vals.clear(); s.step_vals.clear(); - s.vals.reserve(settings.max_cached_minutes * 60 * 100); // [n]seconds * 100hz - s.step_vals.reserve(settings.max_cached_minutes * 60 * 100 * 2); s.last_value_mono_time = 0; } s.series->setColor(getColor(s.sig)); - struct Chunk { - std::vector::const_iterator first, second; - QVector vals; - QVector step_vals; - }; - // split into one minitue chunks - QVector chunks; - Event begin_event(cereal::Event::Which::INIT_DATA, s.last_value_mono_time); - auto begin = std::upper_bound(events->begin(), events->end(), &begin_event, Event::lessThan()); - for (auto it = begin, second = begin; it != events->end(); it = second) { - second = std::lower_bound(it, events->end(), (*it)->mono_time + 1e9 * 60, [](auto &e, uint64_t ts) { return e->mono_time < ts; }); - chunks.push_back({it, second}); + const auto &msgs = can->events().at(s.msg_id); + auto first = std::upper_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = s.last_value_mono_time}); + int new_size = std::max(s.vals.size() + std::distance(first, msgs.cend()), settings.max_cached_minutes * 60 * 100); + if (s.vals.capacity() <= new_size) { + s.vals.reserve(new_size * 2); + s.step_vals.reserve(new_size * 4); } - QtConcurrent::blockingMap(chunks, [&](Chunk &chunk) { - chunk.vals.reserve(60 * 100); // 100 hz - chunk.step_vals.reserve(60 * 100 * 2); // 100 hz - double route_start_time = can->routeStartTime(); - for (auto it = chunk.first; it != chunk.second; ++it) { - if ((*it)->which == cereal::Event::Which::CAN) { - for (const auto &c : (*it)->event.getCan()) { - if (s.msg_id.address == c.getAddress() && s.msg_id.source == c.getSrc()) { - auto dat = c.getDat(); - double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig); - double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds - chunk.vals.push_back({ts, value}); - if (!chunk.step_vals.empty()) { - chunk.step_vals.push_back({ts, chunk.step_vals.back().y()}); - } - chunk.step_vals.push_back({ts,value}); - } - } - } - } - }); - for (auto &c : chunks) { - s.vals.append(c.vals); - if (!c.step_vals.empty()) { - if (!s.step_vals.empty()) { - s.step_vals.append({c.step_vals.first().x(), s.step_vals.back().y()}); - } - s.step_vals.append(c.step_vals); + const double route_start_time = can->routeStartTime(); + for (auto end = msgs.cend(); first != end; ++first) { + double value = get_raw_value(first->dat, first->size, *s.sig); + double ts = first->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()}); } - } - if (events->size()) { - s.last_value_mono_time = events->back()->mono_time; + s.step_vals.append({ts, value}); + s.last_value_mono_time = first->mono_time; } if (!can->liveStreaming()) { s.segment_tree.build(s.vals); @@ -587,25 +621,29 @@ void ChartView::updateAxisY() { unit.clear(); } - auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); - auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; }); + auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan); + auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), xLessThan); + s.min = std::numeric_limits::max(); + s.max = std::numeric_limits::lowest(); if (can->liveStreaming()) { for (auto it = first; it != last; ++it) { - if (it->y() < min) min = it->y(); - if (it->y() > max) max = it->y(); + if (it->y() < s.min) s.min = it->y(); + if (it->y() > s.max) s.max = it->y(); } } else { auto [min_y, max_y] = s.segment_tree.minmax(std::distance(s.vals.begin(), first), std::distance(s.vals.begin(), last)); - min = std::min(min, min_y); - max = std::max(max, max_y); + s.min = min_y; + s.max = max_y; } + min = std::min(min, s.min); + max = std::max(max, s.max); } if (min == std::numeric_limits::max()) min = 0; if (max == std::numeric_limits::lowest()) max = 0; if (axis_y->titleText() != unit) { axis_y->setTitleText(unit); - y_label_width = 0;// recalc width + y_label_width = 0; // recalc width } double delta = std::abs(max - min) < 1e-3 ? 1 : (max - min) * 0.05; @@ -614,10 +652,10 @@ void ChartView::updateAxisY() { axis_y->setRange(min_y, max_y); axis_y->setTickCount(tick_count); + int title_spacing = unit.isEmpty() ? 0 : QFontMetrics(axis_y->titleFont()).size(Qt::TextSingleLine, unit).height(); QFontMetrics fm(axis_y->labelsFont()); int n = qMax(int(-qFloor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1; - int title_spacing = axis_y->titleText().isEmpty() ? 0 : 20; - y_label_width = title_spacing + qMax(fm.width(QString::number(min_y, 'f', n)), fm.width(QString::number(max_y, 'f', n))) + 15; // left margin 15 + y_label_width = title_spacing + qMax(fm.width(QString::number(min_y, 'f', n)), fm.width(QString::number(max_y, 'f', n))) + 15; axis_y->setLabelFormat(QString("%.%1f").arg(n)); emit axisYLabelWidthChanged(y_label_width); } @@ -651,8 +689,9 @@ qreal ChartView::niceNumber(qreal x, bool ceiling) { } void ChartView::leaveEvent(QEvent *event) { - clearTrackPoints(); - scene()->update(); + if (tip_label.isVisible()) { + emit hovered(-1); + } QChartView::leaveEvent(event); } @@ -664,10 +703,7 @@ void ChartView::mousePressEvent(QMouseEvent *event) { drag->setMimeData(mimeData); drag->setPixmap(grab()); drag->setHotSpot(event->pos()); - Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::MoveAction); - if (dropAction == Qt::MoveAction) { - return; - } + drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::MoveAction); } else if (event->button() == Qt::LeftButton && QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) { if (!can->liveStreaming()) { // Save current playback state when scrubbing @@ -675,7 +711,6 @@ void ChartView::mousePressEvent(QMouseEvent *event) { if (resume_after_scrub) { can->pause(true); } - is_scrubbing = true; } } else { @@ -700,13 +735,14 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { if (rubber->width() <= 0) { // no rubber dragged, seek to mouse position can->seekTo(min); - } else if ((max_rounded - min_rounded) >= 0.5) { - // zoom in if selected range is greater than 0.5s + } else if (rubber->width() > 10) { emit zoomIn(min_rounded, max_rounded); + } else { + scene()->invalidate({}, QGraphicsScene::ForegroundLayer); } event->accept(); } else if (!can->liveStreaming() && event->button() == Qt::RightButton) { - emit zoomReset(); + emit zoomUndo(); event->accept(); } else { QGraphicsView::mouseReleaseEvent(event); @@ -721,50 +757,23 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { } void ChartView::mouseMoveEvent(QMouseEvent *ev) { + const auto plot_area = chart()->plotArea(); // Scrubbing if (is_scrubbing && QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) { - if (chart()->plotArea().contains(ev->pos())) { - double t = chart()->mapToValue(ev->pos()).x(); - // Prevent seeking past the end of the route - t = std::clamp(t, 0., can->totalSeconds()); - can->seekTo(t); + if (plot_area.contains(ev->pos())) { + can->seekTo(std::clamp(chart()->mapToValue(ev->pos()).x(), 0., can->totalSeconds())); } - return; } auto rubber = findChild(); bool is_zooming = rubber && rubber->isVisible(); - const auto plot_area = chart()->plotArea(); clearTrackPoints(); if (!is_zooming && plot_area.contains(ev->pos())) { - QStringList text_list; const double sec = chart()->mapToValue(ev->pos()).x(); - qreal x = -1; - for (auto &s : sigs) { - if (!s.series->isVisible()) continue; - - // use reverse iterator to find last item <= sec. - double value = 0; - auto it = std::lower_bound(s.vals.rbegin(), s.vals.rend(), sec, [](auto &p, double x) { return p.x() > x; }); - if (it != s.vals.rend() && it->x() >= axis_x->min()) { - value = it->y(); - s.track_pt = chart()->mapToPosition(*it); - x = std::max(x, s.track_pt.x()); - } - text_list.push_back(QString("%2: %3") - .arg(s.series->color().name(), s.sig->name, - s.track_pt.isNull() ? "--" : QString::number(value))); - } - if (x < 0) { - x = ev->pos().x(); - } - text_list.push_front(QString::number(chart()->mapToValue({x, 0}).x(), 'f', 3)); - QPointF tooltip_pt(x + 12, plot_area.top() - 20); - QToolTip::showText(mapToGlobal(tooltip_pt.toPoint()), text_list.join("
"), this, plot_area.toRect()); - scene()->invalidate({}, QGraphicsScene::ForegroundLayer); - } else { - QToolTip::hideText(); + emit hovered(sec); + } else if (tip_label.isVisible()) { + emit hovered(-1); } QChartView::mouseMoveEvent(ev); @@ -775,7 +784,37 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { if (rubber_rect != rubber->geometry()) { rubber->setGeometry(rubber_rect); } + scene()->invalidate({}, QGraphicsScene::ForegroundLayer); + } +} + +void ChartView::showTip(double sec) { + qreal x = chart()->mapToPosition({sec, 0}).x(); + QStringList text_list(QString::number(chart()->mapToValue({x, 0}).x(), 'f', 3)); + for (auto &s : sigs) { + if (s.series->isVisible()) { + QString value = "--"; + // use reverse iterator to find last item <= sec. + auto it = std::lower_bound(s.vals.rbegin(), s.vals.rend(), sec, [](auto &p, double x) { return p.x() > x; }); + if (it != s.vals.rend() && it->x() >= axis_x->min()) { + value = QString::number(it->y()); + s.track_pt = chart()->mapToPosition(*it); + x = std::max(x, s.track_pt.x()); + } + text_list << QString("%2: %3 (%4 - %5)") + .arg(s.series->color().name(), s.sig->name, value, QString::number(s.min), QString::number(s.max)); + } } + QPointF tooltip_pt(x, chart()->plotArea().top()); + int plot_right = mapToGlobal(chart()->plotArea().topRight().toPoint()).x(); + tip_label.showText(mapToGlobal(tooltip_pt.toPoint()), "

" + text_list.join("
") + "

", plot_right); + scene()->update(); +} + +void ChartView::hideTip() { + clearTrackPoints(); + tip_label.hide(); + scene()->update(); } void ChartView::dragMoveEvent(QDragMoveEvent *event) { @@ -833,14 +872,29 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { painter->setPen(Qt::NoPen); for (auto &s : sigs) { if (s.series->useOpenGL() && s.series->isVisible() && s.series->pointsVisible()) { - auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); - auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), [](auto &p, double x) { return p.x() < x; }); + auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan); + auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), xLessThan); + painter->setBrush(s.series->color()); for (auto it = first; it != last; ++it) { - painter->setBrush(s.series->color()); painter->drawEllipse(chart()->mapToPosition(*it), 4, 4); } } } + + // paint zoom range + auto rubber = findChild(); + if (rubber && rubber->isVisible() && rubber->width() > 1) { + painter->setPen(Qt::white); + auto rubber_rect = rubber->geometry().normalized(); + for (const auto &pt : {rubber_rect.bottomLeft(), rubber_rect.bottomRight()}) { + QString sec = QString::number(chart()->mapToValue(pt).x(), 'f', 1); + // ChartAxisElement's padding is 4 (https://codebrowser.dev/qt5/qtcharts/src/charts/axis/chartaxiselement_p.h.html) + auto r = painter->fontMetrics().boundingRect(sec).adjusted(-6, -4, 6, 4); + pt == rubber_rect.bottomLeft() ? r.moveTopRight(pt + QPoint{0, 2}) : r.moveTopLeft(pt + QPoint{0, 2}); + painter->fillRect(r, Qt::gray); + painter->drawText(r, Qt::AlignCenter, sec); + } + } } QXYSeries *ChartView::createSeries(SeriesType type, QColor color) { @@ -856,18 +910,25 @@ QXYSeries *ChartView::createSeries(SeriesType type, QColor color) { chart()->legend()->setMarkerShape(QLegend::MarkerShapeCircle); } series->setColor(color); - // TODO: Due to a bug in CameraWidget the camera frames - // are drawn instead of the graphs on MacOS. Re-enable OpenGL when fixed + // TODO: Due to a bug in CameraWidget the camera frames + // are drawn instead of the graphs on MacOS. Re-enable OpenGL when fixed #ifndef __APPLE__ series->setUseOpenGL(true); // Qt doesn't properly apply device pixel ratio in OpenGL mode QPen pen = series->pen(); - pen.setWidth(2.0 * qApp->devicePixelRatio()); + pen.setWidthF(2.0 * devicePixelRatioF()); series->setPen(pen); #endif chart()->addSeries(series); series->attachAxis(axis_x); series->attachAxis(axis_y); + + // disables the delivery of mouse events to the opengl widget. + // this enables the user to select the zoom area when the mouse press on the data point. + auto glwidget = findChild(); + if (glwidget && !glwidget->testAttribute(Qt::WA_TransparentForMouseEvents)) { + glwidget->setAttribute(Qt::WA_TransparentForMouseEvents); + } return series; } @@ -936,7 +997,7 @@ SeriesSelector::SeriesSelector(QString title, QWidget *parent) : QDialog(parent) auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(buttonBox, 3, 2); - for (auto it = can->can_msgs.cbegin(); it != can->can_msgs.cend(); ++it) { + for (auto it = can->last_msgs.cbegin(); it != can->last_msgs.cend(); ++it) { if (auto m = dbc()->msg(it.key())) { msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(it.key().toString()), QVariant::fromValue(it.key())); } @@ -950,7 +1011,7 @@ SeriesSelector::SeriesSelector(QString title, QWidget *parent) : QDialog(parent) QObject::connect(available_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::add); QObject::connect(selected_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::remove); QObject::connect(add_btn, &QPushButton::clicked, [this]() { if (auto item = available_list->currentItem()) add(item); }); - QObject::connect(remove_btn, &QPushButton::clicked, [this]() { if (auto item = selected_list->currentItem()) remove(item);}); + QObject::connect(remove_btn, &QPushButton::clicked, [this]() { if (auto item = selected_list->currentItem()) remove(item); }); QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } @@ -998,3 +1059,39 @@ QList SeriesSelector::seletedItems() { for (int i = 0; i < selected_list->count(); ++i) ret.push_back((ListItem *)selected_list->item(i)); return ret; } + +// ValueTipLabel + +ValueTipLabel::ValueTipLabel(QWidget *parent) : QLabel(parent, Qt::Tool | Qt::FramelessWindowHint) { + setForegroundRole(QPalette::ToolTipText); + setBackgroundRole(QPalette::ToolTipBase); + setPalette(QToolTip::palette()); + ensurePolished(); + setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, nullptr, this)); + setAttribute(Qt::WA_ShowWithoutActivating); + setTextFormat(Qt::RichText); + setVisible(false); +} + +void ValueTipLabel::showText(const QPoint &pt, const QString &text, int right_edge) { + setText(text); + if (!text.isEmpty()) { + QSize extra(1, 1); + resize(sizeHint() + extra); + QPoint tip_pos(pt.x() + 12, pt.y()); + if (tip_pos.x() + size().width() >= right_edge) { + tip_pos.rx() = pt.x() - size().width() - 12; + } + move(tip_pos); + } + setVisible(!text.isEmpty()); +} + +void ValueTipLabel::paintEvent(QPaintEvent *ev) { + QStylePainter p(this); + QStyleOptionFrame opt; + opt.init(this); + p.drawPrimitive(QStyle::PE_PanelTipLabel, opt); + p.end(); + QLabel::paintEvent(ev); +} diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index bdc924ff83..98ceb02674 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -1,19 +1,19 @@ #pragma once -#include #include #include #include #include #include -#include +#include +#include #include #include #include #include #include -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" using namespace QtCharts; @@ -25,6 +25,13 @@ enum class SeriesType { Scatter }; +class ValueTipLabel : public QLabel { +public: + ValueTipLabel(QWidget *parent = nullptr); + void showText(const QPoint &pt, const QString &sec, int right_edge); + void paintEvent(QPaintEvent *ev) override; +}; + class ChartView : public QChartView { Q_OBJECT @@ -32,10 +39,12 @@ public: ChartView(QWidget *parent = nullptr); void addSeries(const MessageId &msg_id, const cabana::Signal *sig); bool hasSeries(const MessageId &msg_id, const cabana::Signal *sig) const; - void updateSeries(const cabana::Signal *sig = nullptr, const std::vector *events = nullptr, bool clear = true); + void updateSeries(const cabana::Signal *sig = nullptr); void updatePlot(double cur, double min, double max); void setSeriesType(SeriesType type); void updatePlotArea(int left); + void showTip(double sec); + void hideTip(); struct SigItem { MessageId msg_id; @@ -46,15 +55,18 @@ public: uint64_t last_value_mono_time = 0; QPointF track_pt{}; SegmentTree segment_tree; + double min = 0; + double max = 0; }; signals: void seriesRemoved(const MessageId &id, const cabana::Signal *sig); void seriesAdded(const MessageId &id, const cabana::Signal *sig); void zoomIn(double min, double max); - void zoomReset(); + void zoomUndo(); void remove(); void axisYLabelWidthChanged(int w); + void hovered(double sec); private slots: void signalUpdated(const cabana::Signal *sig); @@ -92,6 +104,7 @@ private: QGraphicsProxyWidget *close_btn_proxy; QGraphicsProxyWidget *manage_btn_proxy; QGraphicsRectItem *background; + ValueTipLabel tip_label; QList sigs; double cur_sec = 0; const QString mime_type = "application/x-cabanachartview"; @@ -120,18 +133,22 @@ signals: private: void resizeEvent(QResizeEvent *event) override; + bool event(QEvent *event) override; void alignCharts(); void newChart(); - ChartView * createChart(); + ChartView *createChart(); void removeChart(ChartView *chart); void eventsMerged(); void updateState(); void zoomIn(double min, double max); void zoomReset(); + void zoomUndo(); + void setZoom(double min, double max); void updateToolBar(); void setMaxChartRange(int value); void updateLayout(); void settingChanged(); + void showValueTip(double sec); bool eventFilter(QObject *obj, QEvent *event) override; ChartView *findChart(const MessageId &id, const cabana::Signal *sig); @@ -142,18 +159,23 @@ private: QAction *range_slider_action; bool docking = true; QAction *dock_btn; + QAction *undo_zoom_action; QAction *reset_zoom_action; QAction *remove_all_btn; QGridLayout *charts_layout; QList charts; + QWidget *charts_container; + QScrollArea *charts_scroll; uint32_t max_chart_range = 0; bool is_zoomed = false; std::pair display_range; std::pair zoomed_range; + QStack> zoom_stack; bool use_dark_theme = false; QAction *columns_action; int column_count = 1; int current_column_count = 0; + QTimer align_timer; }; class SeriesSelector : public QDialog { diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h index 2a5f40d584..84d1a6e2a1 100644 --- a/tools/cabana/commands.h +++ b/tools/cabana/commands.h @@ -3,7 +3,7 @@ #include #include -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" class EditMsgCommand : public QUndoCommand { diff --git a/tools/cabana/dbc.cc b/tools/cabana/dbc/dbc.cc similarity index 95% rename from tools/cabana/dbc.cc rename to tools/cabana/dbc/dbc.cc index ff100e01f7..46302ad789 100644 --- a/tools/cabana/dbc.cc +++ b/tools/cabana/dbc/dbc.cc @@ -1,4 +1,4 @@ -#include "tools/cabana/dbc.h" +#include "tools/cabana/dbc/dbc.h" #include "tools/cabana/util.h" uint qHash(const MessageId &item) { @@ -27,7 +27,7 @@ static QVector BIG_ENDIAN_START_BITS = []() { return ret; }(); -double get_raw_value(uint8_t *data, size_t data_size, const cabana::Signal &sig) { +double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig) { int64_t val = 0; int i = sig.msb / 8; diff --git a/tools/cabana/dbc.h b/tools/cabana/dbc/dbc.h similarity index 90% rename from tools/cabana/dbc.h rename to tools/cabana/dbc/dbc.h index a2ebbc16cc..701908112f 100644 --- a/tools/cabana/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -38,6 +38,10 @@ struct MessageId { uint qHash(const MessageId &item); Q_DECLARE_METATYPE(MessageId); +template <> +struct std::hash { + std::size_t operator()(const MessageId &k) const noexcept { return qHash(k); } +}; typedef QList> ValueDescription; @@ -72,7 +76,7 @@ namespace cabana { } // Helper functions -double get_raw_value(uint8_t *data, size_t data_size, const cabana::Signal &sig); +double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig); int bigEndianStartBitsIndex(int start_bit); int bigEndianBitIndex(int index); void updateSigSizeParamsFromRange(cabana::Signal &s, int start_bit, int size); diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbc/dbcfile.cc similarity index 56% rename from tools/cabana/dbcmanager.cc rename to tools/cabana/dbc/dbcfile.cc index 651367ba57..8b4f98508c 100644 --- a/tools/cabana/dbcmanager.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -1,58 +1,200 @@ -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/dbc/dbcfile.h" + #include #include +#include #include #include #include #include #include -bool DBCManager::open(const QString &dbc_file_name, QString *error) { - QString opendbc_file_path = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, dbc_file_name); - QFile file(opendbc_file_path); + +DBCFile::DBCFile(const QString &dbc_file_name, QObject *parent) : QObject(parent) { + QFile file(dbc_file_name); if (file.open(QIODevice::ReadOnly)) { - return open(dbc_file_name, file.readAll(), error); + name_ = QFileInfo(dbc_file_name).baseName(); + + // Remove auto save file extension + if (dbc_file_name.endsWith(AUTO_SAVE_EXTENSION)) { + filename = dbc_file_name.left(dbc_file_name.length() - AUTO_SAVE_EXTENSION.length()); + } else { + filename = dbc_file_name; + } + open(file.readAll()); + } else { + throw std::runtime_error("Failed to open file."); } - return false; -} - -bool DBCManager::open(const QString &name, const QString &content, QString *error) { - try { - std::istringstream stream(content.toStdString()); - auto dbc = const_cast(dbc_parse_from_stream(name.toStdString(), stream)); - msgs.clear(); - for (auto &msg : dbc->msgs) { - auto &m = msgs[msg.address]; - m.name = msg.name.c_str(); - m.size = msg.size; - for (auto &s : msg.sigs) { - m.sigs.push_back({}); - auto &sig = m.sigs.last(); - sig.name = s.name.c_str(); - sig.start_bit = s.start_bit; - sig.msb = s.msb; - sig.lsb = s.lsb; - sig.size = s.size; - sig.is_signed = s.is_signed; - sig.factor = s.factor; - sig.offset = s.offset; - sig.is_little_endian = s.is_little_endian; - sig.updatePrecision(); - } +} + +DBCFile::DBCFile(const QString &name, const QString &content, QObject *parent) : QObject(parent), name_(name), filename("") { + // Open from clipboard + open(content); +} + +void DBCFile::open(const QString &content) { + std::istringstream stream(content.toStdString()); + auto dbc = const_cast(dbc_parse_from_stream(name_.toStdString(), stream)); + msgs.clear(); + for (auto &msg : dbc->msgs) { + auto &m = msgs[msg.address]; + m.name = msg.name.c_str(); + m.size = msg.size; + for (auto &s : msg.sigs) { + m.sigs.push_back({}); + auto &sig = m.sigs.last(); + sig.name = s.name.c_str(); + sig.start_bit = s.start_bit; + sig.msb = s.msb; + sig.lsb = s.lsb; + sig.size = s.size; + sig.is_signed = s.is_signed; + sig.factor = s.factor; + sig.offset = s.offset; + sig.is_little_endian = s.is_little_endian; + sig.updatePrecision(); } - parseExtraInfo(content); - name_ = name; - emit DBCFileChanged(); - delete dbc; - } catch (std::exception &e) { - if (error) *error = e.what(); + } + parseExtraInfo(content); + + delete dbc; +} + +bool DBCFile::save() { + assert (!filename.isEmpty()); + if (writeContents(filename)) { + cleanupAutoSaveFile(); + return true; + } else { + return false; + } +} + +bool DBCFile::saveAs(const QString &new_filename) { + filename = new_filename; + return save(); +} + +bool DBCFile::autoSave() { + if (!filename.isEmpty()) { + return writeContents(filename + AUTO_SAVE_EXTENSION); + } else { return false; } - return true; } -void DBCManager::parseExtraInfo(const QString &content) { +void DBCFile::cleanupAutoSaveFile() { + if (!filename.isEmpty()) { + QFile::remove(filename + AUTO_SAVE_EXTENSION); + } +} + +bool DBCFile::writeContents(const QString &fn) { + QFile file(fn); + if (file.open(QIODevice::WriteOnly)) { + file.write(generateDBC().toUtf8()); + return true; + } else { + return false; + } +} + +cabana::Signal *DBCFile::addSignal(const MessageId &id, const cabana::Signal &sig) { + if (auto m = const_cast(msg(id.address))) { + m->sigs.push_back(sig); + return &m->sigs.last(); + } + + return nullptr; +} + + cabana::Signal *DBCFile::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) { + if (auto m = const_cast(msg(id))) { + if (auto s = (cabana::Signal *)m->sig(sig_name)) { + *s = sig; + return s; + } + } + + return nullptr; +} + +cabana::Signal *DBCFile::getSignal(const MessageId &id, const QString &sig_name) { + if (auto m = const_cast(msg(id))) { + auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return s.name == sig_name; }); + if (it != m->sigs.end()) { + return &(*it); + } + } + return nullptr; + } + +void DBCFile::removeSignal(const MessageId &id, const QString &sig_name) { + if (auto m = const_cast(msg(id))) { + auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return s.name == sig_name; }); + if (it != m->sigs.end()) { + m->sigs.erase(it); + } + } +} + +void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size) { + auto &m = msgs[id.address]; + m.name = name; + m.size = size; +} + +void DBCFile::removeMsg(const MessageId &id) { + msgs.erase(id.address); +} + +std::map DBCFile::getMessages() { + return msgs; +} + +const cabana::Msg *DBCFile::msg(const MessageId &id) const { + return msg(id.address); +} + +const cabana::Msg *DBCFile::msg(uint32_t address) const { + auto it = msgs.find(address); + return it != msgs.end() ? &it->second : nullptr; +} + +const cabana::Msg* DBCFile::msg(const QString &name) { + for (auto &[_, msg] : msgs) { + if (msg.name == name) { + return &msg; + } + } + + return nullptr; +} + + +QStringList DBCFile::signalNames() const { + // Used for autocompletion + QStringList ret; + for (auto const& [_, msg] : msgs) { + for (auto sig: msg.getSignals()) { + ret << sig->name; + } + } + ret.sort(); + ret.removeDuplicates(); + return ret; +} + +int DBCFile::msgCount() const { + return msgs.size(); +} + +QString DBCFile::name() const { + return name_; +} + +void DBCFile::parseExtraInfo(const QString &content) { static QRegularExpression bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))"); static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); @@ -108,7 +250,7 @@ void DBCManager::parseExtraInfo(const QString &content) { } } -QString DBCManager::generateDBC() { +QString DBCFile::generateDBC() { QString dbc_string, signal_comment, val_desc; for (auto &[address, m] : msgs) { dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(address).arg(m.name).arg(m.size); @@ -139,77 +281,3 @@ QString DBCManager::generateDBC() { } return dbc_string + signal_comment + val_desc; } - -void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size) { - auto &m = msgs[id.address]; - m.name = name; - m.size = size; - - // This DBC applies to all active sources, emit for every source - for (uint8_t source : sources) { - emit msgUpdated({.source = source, .address = id.address}); - } -} - -void DBCManager::removeMsg(const MessageId &id) { - msgs.erase(id.address); - - // This DBC applies to all active sources, emit for every source - for (uint8_t source : sources) { - emit msgRemoved({.source = source, .address = id.address}); - } -} - -void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) { - if (auto m = const_cast(msg(id.address))) { - m->sigs.push_back(sig); - auto s = &m->sigs.last(); - - // This DBC applies to all active sources, emit for every source - for (uint8_t source : sources) { - emit signalAdded({.source = source, .address = id.address}, s); - } - } -} - -void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) { - if (auto m = const_cast(msg(id))) { - if (auto s = (cabana::Signal *)m->sig(sig_name)) { - *s = sig; - emit signalUpdated(s); - } - } -} - -void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { - if (auto m = const_cast(msg(id))) { - auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return s.name == sig_name; }); - if (it != m->sigs.end()) { - emit signalRemoved(&(*it)); - m->sigs.erase(it); - } - } -} - -QStringList DBCManager::signalNames() { - // Used for autocompletion - QStringList ret; - for (auto const& [_, msg] : msgs) { - for (auto sig: msg.getSignals()) { - ret << sig->name; - } - } - ret.sort(); - ret.removeDuplicates(); - return ret; -} - -void DBCManager::updateSources(const QSet &s) { - sources = s; -} - -DBCManager *dbc() { - static DBCManager dbc_manager(nullptr); - return &dbc_manager; -} - diff --git a/tools/cabana/dbc/dbcfile.h b/tools/cabana/dbc/dbcfile.h new file mode 100644 index 0000000000..082ee773b1 --- /dev/null +++ b/tools/cabana/dbc/dbcfile.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "tools/cabana/dbc/dbc.h" + +const QString AUTO_SAVE_EXTENSION = ".tmp"; + + +class DBCFile : public QObject { + Q_OBJECT + +public: + DBCFile(const QString &dbc_file_name, QObject *parent=nullptr); + DBCFile(const QString &name, const QString &content, QObject *parent=nullptr); + ~DBCFile() {} + + void open(const QString &content); + + bool save(); + bool saveAs(const QString &new_filename); + bool autoSave(); + bool writeContents(const QString &fn); + void cleanupAutoSaveFile(); + QString generateDBC(); + + cabana::Signal *addSignal(const MessageId &id, const cabana::Signal &sig); + cabana::Signal *updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig); + cabana::Signal *getSignal(const MessageId &id, const QString &sig_name); + void removeSignal(const MessageId &id, const QString &sig_name); + + void updateMsg(const MessageId &id, const QString &name, uint32_t size); + void removeMsg(const MessageId &id); + + std::map getMessages(); + const cabana::Msg *msg(const MessageId &id) const; + const cabana::Msg *msg(uint32_t address) const; + const cabana::Msg* msg(const QString &name); + QStringList signalNames() const; + int msgCount() const; + QString name() const; + + QString filename; + +private: + void parseExtraInfo(const QString &content); + std::map msgs; + QString name_; +}; diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc new file mode 100644 index 0000000000..49527765a8 --- /dev/null +++ b/tools/cabana/dbc/dbcmanager.cc @@ -0,0 +1,218 @@ +#include "tools/cabana/dbc/dbcmanager.h" + +#include +#include +#include +#include +#include +#include + + +bool DBCManager::open(SourceSet s, const QString &dbc_file_name, QString *error) { + for (int i = 0; i < dbc_files.size(); i++) { + auto [ss, dbc_file] = dbc_files[i]; + + // Check if file is already open, and merge sources + if (dbc_file->filename == dbc_file_name) { + dbc_files[i] = {ss | s, dbc_file}; + + emit DBCFileChanged(); + return true; + } + + // Check if there is already a file for this sourceset, then replace it + if (ss == s) { + try { + dbc_files[i] = {s, new DBCFile(dbc_file_name, this)}; + delete dbc_file; + + emit DBCFileChanged(); + return true; + } catch (std::exception &e) { + if (error) *error = e.what(); + return false; + } + } + } + + try { + dbc_files.push_back({s, new DBCFile(dbc_file_name, this)}); + } catch (std::exception &e) { + if (error) *error = e.what(); + return false; + } + + emit DBCFileChanged(); + return true; +} + +bool DBCManager::open(SourceSet s, const QString &name, const QString &content, QString *error) { + try { + dbc_files.push_back({s, new DBCFile(name, content, this)}); + } catch (std::exception &e) { + if (error) *error = e.what(); + return false; + } + + emit DBCFileChanged(); + return true; +} + +void DBCManager::closeAll() { + while (dbc_files.size()) { + DBCFile *dbc_file = dbc_files.back().second; + dbc_files.pop_back(); + delete dbc_file; + } + emit DBCFileChanged(); +} + + +void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) { + auto sources_dbc_file = findDBCFile(id); + assert(sources_dbc_file); // Create new DBC? + auto [dbc_sources, dbc_file] = *sources_dbc_file; + + cabana::Signal *s = dbc_file->addSignal(id, sig); + + if (s != nullptr) { + for (uint8_t source : dbc_sources) { + emit signalAdded({.source = source, .address = id.address}, s); + } + } +} + +void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) { + auto sources_dbc_file = findDBCFile(id); + assert(sources_dbc_file); // This should be impossible + auto [_, dbc_file] = *sources_dbc_file; + + cabana::Signal *s = dbc_file->updateSignal(id, sig_name, sig); + + if (s != nullptr) { + emit signalUpdated(s); + } +} + +void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { + auto sources_dbc_file = findDBCFile(id); + assert(sources_dbc_file); // This should be impossible + auto [_, dbc_file] = *sources_dbc_file; + + cabana::Signal *s = dbc_file->getSignal(id, sig_name); + + if (s != nullptr) { + emit signalRemoved(s); + dbc_file->removeSignal(id, sig_name); + } +} + +void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size) { + auto sources_dbc_file = findDBCFile(id); + assert(sources_dbc_file); // This should be impossible + auto [dbc_sources, dbc_file] = *sources_dbc_file; + + dbc_file->updateMsg(id, name, size); + + for (uint8_t source : dbc_sources) { + emit msgUpdated({.source = source, .address = id.address}); + } +} + +void DBCManager::removeMsg(const MessageId &id) { + auto sources_dbc_file = findDBCFile(id); + assert(sources_dbc_file); // This should be impossible + auto [dbc_sources, dbc_file] = *sources_dbc_file; + + dbc_file->removeMsg(id); + + for (uint8_t source : dbc_sources) { + emit msgRemoved({.source = source, .address = id.address}); + } +} + +std::map DBCManager::getMessages(uint8_t source) { + std::map ret; + + auto sources_dbc_file = findDBCFile({.source = source, .address = 0}); + if (!sources_dbc_file) { + return ret; + } + + auto [_, dbc_file] = *sources_dbc_file; + + for (auto &[address, msg] : dbc_file->getMessages()) { + MessageId id = {.source = source, .address = address}; + ret[id] = msg; + } + return ret; +} + +const cabana::Msg *DBCManager::msg(const MessageId &id) const { + auto sources_dbc_file = findDBCFile(id); + if (!sources_dbc_file) { + return nullptr; + } + auto [_, dbc_file] = *sources_dbc_file; + return dbc_file->msg(id); +} + +const cabana::Msg* DBCManager::msg(uint8_t source, const QString &name) { + auto sources_dbc_file = findDBCFile({.source = source, .address = 0}); + if (!sources_dbc_file) { + return nullptr; + } + auto [_, dbc_file] = *sources_dbc_file; + return dbc_file->msg(name); +} + +QStringList DBCManager::signalNames() const { + QStringList ret; + + for (auto &[_, dbc_file] : dbc_files) { + ret << dbc_file->signalNames(); + } + + ret.sort(); + ret.removeDuplicates(); + return ret; +} + +int DBCManager::msgCount() const { + int ret = 0; + + for (auto &[_, dbc_file] : dbc_files) { + ret += dbc_file->msgCount(); + } + + return ret; +} + +int DBCManager::dbcCount() const { + return dbc_files.size(); +} + +void DBCManager::updateSources(const SourceSet &s) { + sources = s; +} + +std::optional> DBCManager::findDBCFile(const uint8_t source) const { + // Find DBC file that matches id.source, fall back to SOURCE_ALL if no specific DBC is found + + for (auto &[source_set, dbc_file] : dbc_files) { + if (source_set.contains(source)) return {{source_set, dbc_file}}; + } + for (auto &[source_set, dbc_file] : dbc_files) { + if (source_set == SOURCE_ALL) return {{sources, dbc_file}}; + } + return {}; +} + +std::optional> DBCManager::findDBCFile(const MessageId &id) const { + return findDBCFile(id.source); +} + +DBCManager *dbc() { + static DBCManager dbc_manager(nullptr); + return &dbc_manager; +} diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h new file mode 100644 index 0000000000..f9af96516c --- /dev/null +++ b/tools/cabana/dbc/dbcmanager.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "tools/cabana/dbc/dbc.h" +#include "tools/cabana/dbc/dbcfile.h" + +typedef QSet SourceSet; +const SourceSet SOURCE_ALL = {}; + +class DBCManager : public QObject { + Q_OBJECT + +public: + DBCManager(QObject *parent) {} + ~DBCManager() {} + bool open(SourceSet s, const QString &dbc_file_name, QString *error = nullptr); + bool open(SourceSet s, const QString &name, const QString &content, QString *error = nullptr); + void closeAll(); + + void addSignal(const MessageId &id, const cabana::Signal &sig); + void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig); + void removeSignal(const MessageId &id, const QString &sig_name); + + void updateMsg(const MessageId &id, const QString &name, uint32_t size); + void removeMsg(const MessageId &id); + + std::map getMessages(uint8_t source); + const cabana::Msg *msg(const MessageId &id) const; + const cabana::Msg* msg(uint8_t source, const QString &name); + + QStringList signalNames() const; + int msgCount() const; + int dbcCount() const; + + std::optional> findDBCFile(const uint8_t source) const; + std::optional> findDBCFile(const MessageId &id) const; + + QList> dbc_files; + +private: + SourceSet sources; + +public slots: + void updateSources(const SourceSet &s); + +signals: + void signalAdded(MessageId id, const cabana::Signal *sig); + void signalRemoved(const cabana::Signal *sig); + void signalUpdated(const cabana::Signal *sig); + void msgUpdated(MessageId id); + void msgRemoved(MessageId id); + void DBCFileChanged(); +}; + +DBCManager *dbc(); + +inline QString msgName(const MessageId &id) { + auto msg = dbc()->msg(id); + return msg ? msg->name : UNTITLED; +} diff --git a/tools/cabana/generate_dbc_json.py b/tools/cabana/dbc/generate_dbc_json.py similarity index 100% rename from tools/cabana/generate_dbc_json.py rename to tools/cabana/dbc/generate_dbc_json.py diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h deleted file mode 100644 index dd3d9811c7..0000000000 --- a/tools/cabana/dbcmanager.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "tools/cabana/dbc.h" - -class DBCManager : public QObject { - Q_OBJECT - -public: - DBCManager(QObject *parent) {} - ~DBCManager() {} - bool open(const QString &dbc_file_name, QString *error = nullptr); - bool open(const QString &name, const QString &content, QString *error = nullptr); - QString generateDBC(); - void addSignal(const MessageId &id, const cabana::Signal &sig); - void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig); - void removeSignal(const MessageId &id, const QString &sig_name); - inline int msgCount() const { return msgs.size(); } - - inline QString name() const { return name_; } - void updateMsg(const MessageId &id, const QString &name, uint32_t size); - void removeMsg(const MessageId &id); - inline std::map getMessages(uint8_t source) { - std::map ret; - for (auto &[address, msg] : msgs) { - MessageId id = {.source = source, .address = address}; - ret[id] = msg; - } - return ret; - } - inline const cabana::Msg *msg(const MessageId &id) const { return msg(id.address); } - inline const cabana::Msg* msg(uint8_t source, const QString &name) { - for (auto &[_, msg] : msgs) { - if (msg.name == name) { - return &msg; - } - } - - return nullptr; - } - QStringList signalNames(); - -public slots: - void updateSources(const QSet &s); - -signals: - void signalAdded(MessageId id, const cabana::Signal *sig); - void signalRemoved(const cabana::Signal *sig); - void signalUpdated(const cabana::Signal *sig); - void msgUpdated(MessageId id); - void msgRemoved(MessageId id); - void DBCFileChanged(); - -private: - void parseExtraInfo(const QString &content); - std::map msgs; - QString name_; - QSet sources; - - inline const cabana::Msg *msg(uint32_t address) const { - auto it = msgs.find(address); - return it != msgs.end() ? &it->second : nullptr; - } -}; - -DBCManager *dbc(); - -inline QString msgName(const MessageId &id) { - auto msg = dbc()->msg(id); - return msg ? msg->name : UNTITLED; -} diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index ab723fa0e2..543946b19a 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -48,7 +48,6 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart // msg widget splitter = new QSplitter(Qt::Vertical, this); - splitter->setAutoFillBackground(true); splitter->addWidget(binary_view = new BinaryView(this)); splitter->addWidget(signal_view = new SignalView(charts, this)); binary_view->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index cc5bffd534..b7cfed376c 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -8,7 +8,7 @@ #include "tools/cabana/binaryview.h" #include "tools/cabana/chartswidget.h" #include "tools/cabana/historylog.h" -#include "tools/cabana/signaledit.h" +#include "tools/cabana/signalview.h" class EditMessageDialog : public QDialog { public: @@ -30,7 +30,6 @@ public: DetailWidget(ChartsWidget *charts, QWidget *parent); void setMessage(const MessageId &message_id); void refresh(); - QSize minimumSizeHint() const override { return binary_view->minimumSizeHint(); } private: void showTabBarContextMenu(const QPoint &pt); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 830fd30d49..4ebe8e0473 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -21,7 +21,7 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { } else if (role == BytesRole) { return m.data; } else if (role == Qt::TextAlignmentRole) { - return Qt::AlignRight; + return (uint32_t)(Qt::AlignRight | Qt::AlignVCenter); } return {}; } @@ -119,40 +119,31 @@ template std::deque HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) { std::deque msgs; QVector values(sigs.size()); - for (auto it = first; it != last && (*it)->mono_time > min_time; ++it) { - if ((*it)->which == cereal::Event::Which::CAN) { - for (const auto &c : (*it)->event.getCan()) { - if (msg_id.address == c.getAddress() && msg_id.source == c.getSrc()) { - const auto dat = c.getDat(); - for (int i = 0; i < sigs.size(); ++i) { - values[i] = get_raw_value((uint8_t *)dat.begin(), dat.size(), *sigs[i]); - } - if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) { - auto &m = msgs.emplace_back(); - m.mono_time = (*it)->mono_time; - m.data = QByteArray((char *)dat.begin(), dat.size()); - m.sig_values = values; - if (msgs.size() >= batch_size && min_time == 0) - return msgs; - } - } + for (; first != last && first->mono_time > min_time; ++first) { + for (int i = 0; i < sigs.size(); ++i) { + values[i] = get_raw_value(first->dat, first->size, *sigs[i]); + } + if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) { + auto &m = msgs.emplace_back(); + m.mono_time = first->mono_time; + m.data = QByteArray((const char *)first->dat, first->size); + m.sig_values = values; + if (msgs.size() >= batch_size && min_time == 0) { + return msgs; } } } return msgs; } -template std::deque HistoryLogModel::fetchData<>(std::vector::iterator first, std::vector::iterator last, uint64_t min_time); -template std::deque HistoryLogModel::fetchData<>(std::vector::reverse_iterator first, std::vector::reverse_iterator last, uint64_t min_time); - std::deque HistoryLogModel::fetchData(uint64_t from_time, uint64_t min_time) { - auto events = can->events(); + const auto &events = can->events().at(msg_id); const auto freq = can->lastMessage(msg_id).freq; const bool update_colors = !display_signals_mode || sigs.empty(); if (dynamic_mode) { - auto first = std::upper_bound(events->rbegin(), events->rend(), from_time, [=](uint64_t ts, auto &e) { return e->mono_time < ts; }); - auto msgs = fetchData(first, events->rend(), min_time); + auto first = std::upper_bound(events.rbegin(), events.rend(), CanEvent{.mono_time=from_time}, std::greater()); + auto msgs = fetchData(first, events.rend(), min_time); if (update_colors && (min_time > 0 || messages.empty())) { for (auto it = msgs.rbegin(); it != msgs.rend(); ++it) { hex_colors.compute(it->data, it->mono_time / (double)1e9, freq); @@ -162,10 +153,10 @@ std::deque HistoryLogModel::fetchData(uint64_t from_ti return msgs; } else { assert(min_time == 0); - auto first = std::upper_bound(events->begin(), events->end(), from_time, [=](uint64_t ts, auto &e) { return ts < e->mono_time; }); - auto msgs = fetchData(first, events->end(), 0); + auto first = std::upper_bound(events.begin(), events.end(), CanEvent{.mono_time=from_time}); + auto msgs = fetchData(first, events.end(), 0); if (update_colors) { - for (auto it = msgs.rbegin(); it != msgs.rend(); ++it) { + for (auto it = msgs.begin(); it != msgs.end(); ++it) { hex_colors.compute(it->data, it->mono_time / (double)1e9, freq); it->colors = hex_colors.colors; } diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index 119b61a0e8..8b8d1b06d2 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -7,7 +7,7 @@ #include #include -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" #include "tools/cabana/util.h" diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 56ffe872d6..c979a20f16 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -42,7 +42,7 @@ MainWindow::MainWindow() : QMainWindow() { messages_widget->restoreHeaderState(settings.message_header_state); qRegisterMetaType("uint64_t"); - qRegisterMetaType>("QSet"); + qRegisterMetaType("SourceSet"); qRegisterMetaType("ReplyMsgType"); installMessageHandler([this](ReplyMsgType type, const std::string msg) { // use queued connection to recv the log messages from replay. @@ -54,9 +54,13 @@ MainWindow::MainWindow() : QMainWindow() { main_win = this; qInstallMessageHandler(qLogMessageHandler); - QFile json_file("./car_fingerprint_to_dbc.json"); - if (json_file.open(QIODevice::ReadOnly)) { - fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); + + for (const QString &fn : {"./dbc/car_fingerprint_to_dbc.json", "./tools/cabana/dbc/car_fingerprint_to_dbc.json"}) { + QFile json_file(fn); + if (json_file.open(QIODevice::ReadOnly)) { + fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); + break; + } } setStyleSheet(QString(R"(QMainWindow::separator { @@ -72,6 +76,7 @@ MainWindow::MainWindow() : QMainWindow() { QObject::connect(can, &AbstractStream::eventsMerged, this, &MainWindow::updateStatus); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged); QObject::connect(can, &AbstractStream::sourcesUpdated, dbc(), &DBCManager::updateSources); + QObject::connect(can, &AbstractStream::sourcesUpdated, this, &MainWindow::updateSources); QObject::connect(UndoStack::instance(), &QUndoStack::cleanChanged, this, &MainWindow::undoStackCleanChanged); QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &MainWindow::undoStackIndexChanged); QObject::connect(&settings, &Settings::changed, this, &MainWindow::updateStatus); @@ -87,6 +92,9 @@ void MainWindow::createActions() { file_menu->addAction(tr("New DBC File"), this, &MainWindow::newFile)->setShortcuts(QKeySequence::New); file_menu->addAction(tr("Open DBC File..."), this, &MainWindow::openFile)->setShortcuts(QKeySequence::Open); + open_dbc_for_source = file_menu->addMenu(tr("Open &DBC File for Bus")); + open_dbc_for_source->setEnabled(false); + open_recent_menu = file_menu->addMenu(tr("Open &Recent")); for (int i = 0; i < MAX_RECENT_FILES; ++i) { recent_files_acts[i] = new QAction(this); @@ -108,9 +116,14 @@ void MainWindow::createActions() { file_menu->addAction(tr("Load DBC From Clipboard"), this, &MainWindow::loadDBCFromClipboard); file_menu->addSeparator(); - file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save)->setShortcuts(QKeySequence::Save); - file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs)->setShortcuts(QKeySequence::SaveAs); - file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard); + save_dbc = file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save); + save_dbc->setShortcuts(QKeySequence::Save); + + save_dbc_as = file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs); + save_dbc_as->setShortcuts(QKeySequence::SaveAs); + + copy_dbc_to_clipboard = file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard); + file_menu->addSeparator(); file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption)->setShortcuts(QKeySequence::Preferences); @@ -133,8 +146,10 @@ void MainWindow::createActions() { commands_act->setDefaultWidget(undo_view); commands_menu->addAction(commands_act); - QMenu *tools_menu = menuBar()->addMenu(tr("&Tools")); - tools_menu->addAction(tr("Find &Similar Bits"), this, &MainWindow::findSimilarBits); + if (!can->liveStreaming()) { + QMenu *tools_menu = menuBar()->addMenu(tr("&Tools")); + tools_menu->addAction(tr("Find &Similar Bits"), this, &MainWindow::findSimilarBits); + } QMenu *help_menu = menuBar()->addMenu(tr("&Help")); help_menu->addAction(tr("Help"), this, &MainWindow::onlineHelp)->setShortcuts(QKeySequence::HelpContents); @@ -229,7 +244,7 @@ void MainWindow::undoStackCleanChanged(bool clean) { void MainWindow::DBCFileChanged() { UndoStack::instance()->clear(); - setWindowFilePath(QString("%1").arg(dbc()->name())); + updateLoadSaveMenus(); } void MainWindow::openRoute() { @@ -245,7 +260,9 @@ void MainWindow::openRoute() { void MainWindow::newFile() { remindSaveChanges(); - dbc()->open("untitled.dbc", ""); + dbc()->closeAll(); + dbc()->open(SOURCE_ALL, "", ""); + updateLoadSaveMenus(); } void MainWindow::openFile() { @@ -256,7 +273,19 @@ void MainWindow::openFile() { } } -void MainWindow::loadFile(const QString &fn) { +void MainWindow::openFileForSource() { + if (auto action = qobject_cast(sender())) { + uint8_t source = action->data().value(); + assert(source < 64); + + QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)"); + if (!fn.isEmpty()) { + loadFile(fn, {source, uint8_t(source + 128), uint8_t(source + 192)}, false); + } + } +} + +void MainWindow::loadFile(const QString &fn, SourceSet s, bool close_all) { if (!fn.isEmpty()) { QString dbc_fn = fn; @@ -269,20 +298,24 @@ void MainWindow::loadFile(const QString &fn) { } } - QFile file(dbc_fn); - if (file.open(QIODevice::ReadOnly)) { - auto dbc_name = QFileInfo(fn).baseName(); - QString error; - bool ret = dbc()->open(dbc_name, file.readAll(), &error); - if (ret) { - setCurrentFile(fn); - statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000); - } else { - QMessageBox msg_box(QMessageBox::Warning, tr("Failed to load DBC file"), tr("Failed to parse DBC file %1").arg(fn)); - msg_box.setDetailedText(error); - msg_box.exec(); - } + auto dbc_name = QFileInfo(fn).baseName(); + QString error; + + if (close_all) { + dbc()->closeAll(); + } + + bool ret = dbc()->open(s, dbc_fn, &error); + if (ret) { + updateRecentFiles(fn); + statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000); + } else { + QMessageBox msg_box(QMessageBox::Warning, tr("Failed to load DBC file"), tr("Failed to parse DBC file %1").arg(fn)); + msg_box.setDetailedText(error); + msg_box.exec(); } + + updateLoadSaveMenus(); } } @@ -301,17 +334,23 @@ void MainWindow::openRecentFile() { } void MainWindow::loadDBCFromOpendbc(const QString &name) { - if (name != dbc()->name()) { - remindSaveChanges(); - dbc()->open(name); - } + remindSaveChanges(); + + QString opendbc_file_path = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, name); + + dbc()->closeAll(); + dbc()->open(SOURCE_ALL, opendbc_file_path); + + updateLoadSaveMenus(); } void MainWindow::loadDBCFromClipboard() { remindSaveChanges(); QString dbc_str = QGuiApplication::clipboard()->text(); QString error; - bool ret = dbc()->open("clipboard", dbc_str, &error); + + dbc()->closeAll(); + bool ret = dbc()->open(SOURCE_ALL, "", dbc_str, &error); if (ret && dbc()->msgCount() > 0) { QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!")); } else { @@ -325,7 +364,7 @@ void MainWindow::loadDBCFromClipboard() { void MainWindow::loadDBCFromFingerprint() { // Don't overwrite already loaded DBC - if (!dbc()->name().isEmpty()) { + if (dbc()->msgCount()) { return; } @@ -334,7 +373,7 @@ void MainWindow::loadDBCFromFingerprint() { if (can->liveStreaming()) { video_dock->setWindowTitle(can->routeName()); } else { - video_dock->setWindowTitle(tr("ROUTE: %1 FINGERPINT: %2").arg(can->routeName()).arg(fingerprint.isEmpty() ? tr("Unknown Car") : fingerprint)); + video_dock->setWindowTitle(tr("ROUTE: %1 FINGERPRINT: %2").arg(can->routeName()).arg(fingerprint.isEmpty() ? tr("Unknown Car") : fingerprint)); } if (!fingerprint.isEmpty()) { auto dbc_name = fingerprint_to_dbc[fingerprint]; @@ -347,54 +386,110 @@ void MainWindow::loadDBCFromFingerprint() { } void MainWindow::save() { - if (current_file.isEmpty()) { - saveAs(); - } else { - saveFile(current_file); - } + saveFile(); } void MainWindow::autoSave() { - if (!current_file.isEmpty() && !UndoStack::instance()->isClean()) { - QFile file(current_file + AUTO_SAVE_EXTENSION); - if (file.open(QIODevice::WriteOnly)) { - file.write(dbc()->generateDBC().toUtf8()); + if (!UndoStack::instance()->isClean()) { + for (auto &[_, dbc_file] : dbc()->dbc_files) { + if (!dbc_file->filename.isEmpty()) { + dbc_file->autoSave(); + } } } } void MainWindow::cleanupAutoSaveFile() { - if (!current_file.isEmpty()) { - QFile::remove(current_file + AUTO_SAVE_EXTENSION); + for (auto &[_, dbc_file] : dbc()->dbc_files) { + dbc_file->cleanupAutoSaveFile(); } } -void MainWindow::saveFile(const QString &fn) { - cleanupAutoSaveFile(); - QFile file(fn); - if (file.open(QIODevice::WriteOnly)) { - file.write(dbc()->generateDBC().toUtf8()); - UndoStack::instance()->setClean(); - setCurrentFile(fn); - statusBar()->showMessage(tr("File saved"), 2000); +void MainWindow::saveFile() { + // Save all open DBC files + for (auto &[s, dbc_file] : dbc()->dbc_files) { + if (!dbc_file->filename.isEmpty()) { + dbc_file->save(); + updateRecentFiles(dbc_file->filename); + } else { + QString fn = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)")); + if (!fn.isEmpty()) { + dbc_file->saveAs(fn); + updateRecentFiles(fn); + } + } } + + UndoStack::instance()->setClean(); + statusBar()->showMessage(tr("File saved"), 2000); } void MainWindow::saveAs() { + // Assume only one file is open + assert(dbc()->dbcCount() > 0); + auto &[_, dbc_file] = dbc()->dbc_files.first(); + QString fn = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)")); if (!fn.isEmpty()) { - saveFile(fn); + dbc_file->saveAs(fn); } } void MainWindow::saveDBCToClipboard() { - QGuiApplication::clipboard()->setText(dbc()->generateDBC()); + // Assume only one file is open + assert(dbc()->dbcCount() > 0); + + auto &[_, dbc_file] = dbc()->dbc_files.first(); + QGuiApplication::clipboard()->setText(dbc_file->generateDBC()); QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!")); } -void MainWindow::setCurrentFile(const QString &fn) { - current_file = fn; - setWindowFilePath(QString("%1").arg(fn)); +void MainWindow::updateSources(const SourceSet &s) { + sources = s; + updateLoadSaveMenus(); +} + +void MainWindow::updateLoadSaveMenus() { + if (dbc()->dbcCount() > 1) { + save_dbc->setText(tr("Save %1 DBCs...").arg(dbc()->dbcCount())); + } else { + save_dbc->setText(tr("Save DBC...")); + } + + // TODO: Support save as for multiple files + save_dbc_as->setEnabled(dbc()->dbcCount() == 1); + + // TODO: Support clipboard for multiple files + copy_dbc_to_clipboard->setEnabled(dbc()->dbcCount() == 1); + + + QList sources_sorted = sources.toList(); + std::sort(sources_sorted.begin(), sources_sorted.end()); + + open_dbc_for_source->setEnabled(sources.size() > 0); + open_dbc_for_source->clear(); + + for (uint8_t source : sources_sorted) { + if (source >= 64) continue; // Sent and blocked buses are handled implicitly + QAction *action = new QAction(this); + + auto d = dbc()->findDBCFile(source); + QString name = tr("no DBC"); + if (d && !d->second->name().isEmpty()) { + name = tr("%1").arg(d->second->name()); + } else if (d) { + name = "untitled"; + } + + action->setText(tr("Bus %1 (current: %2)").arg(source).arg(name)); + action->setData(source); + + QObject::connect(action, &QAction::triggered, this, &MainWindow::openFileForSource); + open_dbc_for_source->addAction(action); + } +} + +void MainWindow::updateRecentFiles(const QString &fn) { settings.recent_files.removeAll(fn); settings.recent_files.prepend(fn); while (settings.recent_files.size() > MAX_RECENT_FILES) { @@ -432,7 +527,6 @@ void MainWindow::remindSaveChanges() { } } UndoStack::instance()->clear(); - current_file = ""; } void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool success) { @@ -446,15 +540,7 @@ void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool succe } void MainWindow::updateStatus() { - float cached_minutes = 0; - if (!can->liveStreaming()) { - if (auto events = can->events(); !events->empty()) { - cached_minutes = (events->back()->mono_time - events->front()->mono_time) / (1e9 * 60); - } - } else { - settings.max_cached_minutes = settings.max_cached_minutes; - } - status_label->setText(tr("Cached Minutes:%1 FPS:%2").arg(cached_minutes, 0, 'f', 1).arg(settings.fps)); + status_label->setText(tr("Cached Minutes:%1 FPS:%2").arg(settings.max_cached_minutes).arg(settings.fps)); } void MainWindow::dockCharts(bool dock) { diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 3258b95cde..991a34931d 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -8,13 +8,12 @@ #include #include "tools/cabana/chartswidget.h" +#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/detailwidget.h" #include "tools/cabana/messageswidget.h" #include "tools/cabana/videowidget.h" #include "tools/cabana/tools/findsimilarbits.h" -const QString AUTO_SAVE_EXTENSION = ".tmp"; - class MainWindow : public QMainWindow { Q_OBJECT @@ -22,12 +21,13 @@ public: MainWindow(); void dockCharts(bool dock); void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); } - void loadFile(const QString &fn); + void loadFile(const QString &fn, SourceSet s = SOURCE_ALL, bool close_all = true); public slots: void openRoute(); void newFile(); void openFile(); + void openFileForSource(); void openRecentFile(); void openOpendbcFile(); void loadDBCFromOpendbc(const QString &name); @@ -36,6 +36,7 @@ public slots: void save(); void saveAs(); void saveDBCToClipboard(); + void updateSources(const SourceSet &s); signals: void showMessage(const QString &msg, int timeout); @@ -43,10 +44,10 @@ signals: protected: void remindSaveChanges(); - void saveFile(const QString &fn); + void saveFile(); void autoSave(); void cleanupAutoSaveFile(); - void setCurrentFile(const QString &fn); + void updateRecentFiles(const QString &fn); void updateRecentFileActions(); void createActions(); void createDockWindows(); @@ -62,6 +63,7 @@ protected: void onlineHelp(); void toggleFullScreen(); void updateStatus(); + void updateLoadSaveMenus(); VideoWidget *video_widget = nullptr; QDockWidget *video_dock; @@ -74,12 +76,16 @@ protected: QLabel *status_label; QJsonDocument fingerprint_to_dbc; QSplitter *video_splitter;; - QString current_file = ""; enum { MAX_RECENT_FILES = 15 }; QAction *recent_files_acts[MAX_RECENT_FILES] = {}; QMenu *open_recent_menu = nullptr; + QMenu *open_dbc_for_source = nullptr; + QAction *save_dbc = nullptr; + QAction *save_dbc_as = nullptr; + QAction *copy_dbc_to_clipboard = nullptr; int prev_undostack_index = 0; int prev_undostack_count = 0; + SourceSet sources; friend class OnlineHelp; }; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index b4a7ed7b34..8dd0bac820 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -160,7 +160,7 @@ void MessageListModel::setFilterString(const QString &string) { filter_str = string; msgs.clear(); - for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) { + for (auto it = can->last_msgs.begin(); it != can->last_msgs.end(); ++it) { if (filter_str.isEmpty() || contains(it.key(), filter_str)) { msgs.push_back(it.key()); } @@ -206,8 +206,8 @@ void MessageListModel::sortMessages() { void MessageListModel::msgsReceived(const QHash *new_msgs) { int prev_row_count = msgs.size(); - if (filter_str.isEmpty() && msgs.size() != can->can_msgs.size()) { - msgs = can->can_msgs.keys(); + if (filter_str.isEmpty() && msgs.size() != can->last_msgs.size()) { + msgs = can->last_msgs.keys(); } if (msgs.size() != prev_row_count) { sortMessages(); diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 08dac0d69a..d4ef896519 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -6,7 +6,7 @@ #include #include -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" class MessageListModel : public QAbstractTableModel { diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index 00bbb94de5..9e829708b1 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -27,6 +27,7 @@ void Settings::save() { s.setValue("recent_files", recent_files); s.setValue("message_header_state", message_header_state); s.setValue("chart_series_type", chart_series_type); + s.setValue("sparkline_range", sparkline_range); } void Settings::load() { @@ -44,6 +45,7 @@ void Settings::load() { recent_files = s.value("recent_files").toStringList(); message_header_state = s.value("message_header_state").toByteArray(); chart_series_type = s.value("chart_series_type", 0).toInt(); + sparkline_range = s.value("sparkline_range", 15).toInt(); } // SettingsDlg diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index c3ba906576..a8b6d189a5 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -17,8 +17,9 @@ public: int max_cached_minutes = 30; int chart_height = 200; int chart_column_count = 1; - int chart_range = 3 * 60; // e minutes + int chart_range = 3 * 60; // 3 minutes int chart_series_type = 0; + int sparkline_range = 15; // 15 seconds QString last_dir; QString last_route_dir; QByteArray geometry; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signalview.cc similarity index 78% rename from tools/cabana/signaledit.cc rename to tools/cabana/signalview.cc index 673a68a50b..4f841a2866 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signalview.cc @@ -1,4 +1,4 @@ -#include "tools/cabana/signaledit.h" +#include "tools/cabana/signalview.h" #include #include @@ -62,14 +62,22 @@ void SignalModel::updateState(const QHash *msgs) { auto &dat = can->lastMessage(msg_id).dat; int row = 0; for (auto item : root->children) { - QString value = QString::number(get_raw_value((uint8_t *)dat.begin(), dat.size(), *item->sig), 'f', item->sig->precision); - if (!item->sig->unit.isEmpty()){ - value += " " + item->sig->unit; + double value = get_raw_value((uint8_t *)dat.constData(), dat.size(), *item->sig); + item->sig_val = QString::number(value, 'f', item->sig->precision); + + // Show unit + if (!item->sig->unit.isEmpty()) { + item->sig_val += " " + item->sig->unit; } - if (value != item->sig_val) { - item->sig_val = value; - emit dataChanged(index(row, 1), index(row, 1), {Qt::DisplayRole}); + + // Show enum string + for (auto &[val, desc] : item->sig->val_desc) { + if (std::abs(value - val.toInt()) < 1e-6) { + item->sig_val = desc; + } } + + emit dataChanged(index(row, 1), index(row, 1), {Qt::DisplayRole}); ++row; } } @@ -160,6 +168,8 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const { if (item->type == Item::Signed) return item->sig->is_signed ? Qt::Checked : Qt::Unchecked; } else if (role == Qt::DecorationRole && index.column() == 0 && item->type == Item::ExtraInfo) { return utils::icon(item->parent->extra_expanded ? "chevron-compact-down" : "chevron-compact-up"); + } else if (role == Qt::ToolTipRole && item->type == Item::Sig) { + return (index.column() == 0) ? item->sig->name : item->sig_val; } } return {}; @@ -245,7 +255,7 @@ void SignalModel::addSignal(int start_bit, int size, bool little_endian) { } } - cabana::Signal sig = {.is_little_endian = little_endian, .factor = 1}; + cabana::Signal sig = {.is_little_endian = little_endian, .factor = 1, .min = "0", .max = QString::number(std::pow(2, size) - 1)}; for (int i = 1; /**/; ++i) { sig.name = QString("NEW_SIGNAL_%1").arg(i); if (msg->sig(sig.name) == nullptr) break; @@ -279,6 +289,7 @@ void SignalModel::handleSignalAdded(MessageId id, const cabana::Signal *sig) { beginInsertRows({}, i, i); insertItem(root.get(), i, sig); endInsertRows(); + updateState(nullptr); } } @@ -314,12 +325,39 @@ QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo int spacing = option.widget->style()->pixelMetric(QStyle::PM_TreeViewIndentation) + color_label_width + 8; it = width_cache.insert(text, option.fontMetrics.width(text) + spacing); } - width = std::min(width, it.value()); + width = std::min(option.widget->size().width() / 3.0, it.value()); } return {width, QApplication::fontMetrics().height()}; } +bool SignalItemDelegate::helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) { + if (event && event->type() == QEvent::ToolTip && index.isValid()) { + auto item = (SignalModel::Item *)index.internalPointer(); + if (item->type == SignalModel::Item::Sig && index.column() == 1) { + QRect rc = option.rect.adjusted(0, 0, -option.rect.width() * 0.4, 0); + if (rc.contains(event->pos())) { + event->setAccepted(false); + return false; + } + } + } + return QStyledItemDelegate::helpEvent(event, view, option, index); +} + +void SignalItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { + auto item = (SignalModel::Item *)index.internalPointer(); + if (editor && item->type == SignalModel::Item::Sig && index.column() == 1) { + QRect geom = option.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &option); + geom.setLeft(geom.right() - editor->sizeHint().width()); + editor->setGeometry(geom); + return; + } + QStyledItemDelegate::updateEditorGeometry(editor, option, index); +} + void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; + int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin); auto item = (SignalModel::Item *)index.internalPointer(); if (index.column() == 0 && item && item->type == SignalModel::Item::Sig) { painter->save(); @@ -330,8 +368,6 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op // color label auto bg_color = getColor(item->sig); - int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; - int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin); QRect rc{option.rect.left() + h_margin, option.rect.top(), color_label_width, option.rect.height()}; painter->setPen(Qt::NoPen); painter->setBrush(item->highlight ? bg_color.darker(125) : bg_color); @@ -350,19 +386,83 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op painter->drawText(text_rect, option.displayAlignment, text); painter->restore(); } else if (index.column() == 1 && item && item->type == SignalModel::Item::Sig) { - // draw signal value + painter->save(); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); } + + int adjust_right = ((SignalView *)parent())->tree->indexWidget(index)->sizeHint().width() + 2 * h_margin; + QRect r = option.rect.adjusted(h_margin, v_margin, -adjust_right, -v_margin); + // draw signal value + QRect value_rect = r.adjusted(r.width() * 0.6 + h_margin, 0, 0, 0); + auto text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, value_rect.width()); painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); - QRect rc = option.rect.adjusted(0, 0, -70, 0); - auto text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, rc.width()); - painter->drawText(rc, Qt::AlignRight | Qt::AlignVCenter, text); + painter->drawText(value_rect, Qt::AlignRight | Qt::AlignVCenter, text); + + QRect sparkline_rect = r.adjusted(0, 0, -r.width() * 0.4 - h_margin, 0); + drawSparkline(painter, sparkline_rect, index); + painter->restore(); } else { QStyledItemDelegate::paint(painter, option, index); } } +void SignalItemDelegate::drawSparkline(QPainter *painter, const QRect &rect, const QModelIndex &index) const { + static std::vector points; + const auto &msg_id = ((SignalView *)parent())->msg_id; + const auto &msgs = can->events().at(msg_id); + + uint64_t ts = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9; + auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = (uint64_t)std::max(ts - settings.sparkline_range * 1e9, 0)}); + auto last = std::upper_bound(first, msgs.cend(), CanEvent{.mono_time = ts}); + + if (first != last) { + double min = std::numeric_limits::max(); + double max = std::numeric_limits::lowest(); + const auto item = (const SignalModel::Item *)index.internalPointer(); + const auto sig = item->sig; + points.clear(); + for (auto it = first; it != last; ++it) { + double value = get_raw_value(it->dat, it->size, *sig); + points.emplace_back((it->mono_time - first->mono_time) / 1e9, value); + min = std::min(min, value); + max = std::max(max, value); + } + if (min == max) { + min -= 1; + max += 1; + } + + const double xscale = rect.width() / (double)settings.sparkline_range; + const double yscale = rect.height() / (max - min); + for (auto &pt : points) { + pt.rx() = rect.left() + pt.x() * xscale; + pt.ry() = rect.top() + std::abs(pt.y() - max) * yscale; + } + + painter->setPen(getColor(sig)); + painter->drawPolyline(points.data(), points.size()); + if ((points.back().x() - points.front().x()) / points.size() > 10) { + painter->setPen(Qt::NoPen); + painter->setBrush(getColor(sig)); + for (const auto &pt : points) { + painter->drawEllipse(pt, 2, 2); + } + } + + if (item->highlight) { + QFont font; + font.setPixelSize(10); + painter->setFont(font); + painter->setPen(Qt::darkGray); + painter->drawLine(rect.topRight(), rect.bottomRight()); + QRect minmax_rect{rect.right() + 3, rect.top(), 1000, rect.height()}; + painter->drawText(minmax_rect, Qt::AlignLeft | Qt::AlignTop, QString::number(max)); + painter->drawText(minmax_rect, Qt::AlignLeft | Qt::AlignBottom, QString::number(min)); + } + } +} + QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto item = (SignalModel::Item *)index.internalPointer(); if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Offset || @@ -410,12 +510,23 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), filter_edit->setPlaceholderText(tr("filter signals")); hl->addWidget(filter_edit); hl->addStretch(1); + + // WARNING: increasing the maximum range can result in severe performance degradation. + // 30s is a reasonable value at present. + const int max_range = 30; // 30s + settings.sparkline_range = std::clamp(settings.sparkline_range, 1, max_range); + hl->addWidget(sparkline_label = new QLabel()); + hl->addWidget(sparkline_range_slider = new QSlider(Qt::Horizontal, this)); + sparkline_range_slider->setRange(1, max_range); + sparkline_range_slider->setValue(settings.sparkline_range); + sparkline_range_slider->setToolTip(tr("Sparkline time range")); + auto collapse_btn = toolButton("dash-square", tr("Collapse All")); collapse_btn->setIconSize({12, 12}); hl->addWidget(collapse_btn); // tree view - tree = new QTreeView(this); + tree = new TreeView(this); tree->setModel(model = new SignalModel(this)); tree->setItemDelegate(new SignalItemDelegate(this)); tree->setFrameShape(QFrame::NoFrame); @@ -432,14 +543,15 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), main_layout->setSpacing(0); main_layout->addWidget(title_bar); main_layout->addWidget(tree); + updateToolBar(); QObject::connect(filter_edit, &QLineEdit::textEdited, model, &SignalModel::setFilter); + QObject::connect(sparkline_range_slider, &QSlider::valueChanged, this, &SignalView::setSparklineRange); QObject::connect(collapse_btn, &QPushButton::clicked, tree, &QTreeView::collapseAll); QObject::connect(tree, &QAbstractItemView::clicked, this, &SignalView::rowClicked); QObject::connect(tree, &QTreeView::viewportEntered, [this]() { emit highlight(nullptr); }); QObject::connect(tree, &QTreeView::entered, [this](const QModelIndex &index) { emit highlight(model->getItem(index)->sig); }); QObject::connect(model, &QAbstractItemModel::modelReset, this, &SignalView::rowsChanged); - QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &SignalView::rowsChanged); QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged); QObject::connect(dbc(), &DBCManager::signalAdded, [this](MessageId id, const cabana::Signal *sig) { selectSignal(sig); }); @@ -465,7 +577,6 @@ void SignalView::rowsChanged() { int h_margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin); h->setContentsMargins(0, v_margin, -h_margin, v_margin); h->setSpacing(style()->pixelMetric(QStyle::PM_ToolBarItemSpacing)); - h->addStretch(0); auto remove_btn = toolButton("x", tr("Remove signal")); auto plot_btn = toolButton("graph-up", ""); @@ -481,7 +592,7 @@ void SignalView::rowsChanged() { }); } } - signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount())); + updateToolBar(); updateChartState(); } @@ -525,10 +636,22 @@ void SignalView::signalHovered(const cabana::Signal *sig) { bool highlight = children[i]->sig == sig; if (std::exchange(children[i]->highlight, highlight) != highlight) { emit model->dataChanged(model->index(i, 0), model->index(i, 0), {Qt::DecorationRole}); + emit model->dataChanged(model->index(i, 1), model->index(i, 1), {Qt::DisplayRole}); } } } +void SignalView::updateToolBar() { + signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount())); + sparkline_label->setText(QString("Range: %1 ").arg(utils::formatSeconds(settings.sparkline_range))); +} + +void SignalView::setSparklineRange(int value) { + settings.sparkline_range = value; + updateToolBar(); + model->updateState(nullptr); +} + void SignalView::leaveEvent(QEvent *event) { emit highlight(nullptr); QWidget::leaveEvent(event); diff --git a/tools/cabana/signaledit.h b/tools/cabana/signalview.h similarity index 81% rename from tools/cabana/signaledit.h rename to tools/cabana/signalview.h index ae73a7b3bc..711748937a 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signalview.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,9 @@ public: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override; + void drawSparkline(QPainter *painter, const QRect &rect, const QModelIndex &index) const; + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QValidator *name_validator, *double_validator; QFont small_font; const int color_label_width = 18; @@ -99,6 +103,7 @@ public: void selectSignal(const cabana::Signal *sig, bool expand = false); void rowClicked(const QModelIndex &index); SignalModel *model = nullptr; + MessageId msg_id; signals: void highlight(const cabana::Signal *sig); @@ -107,10 +112,23 @@ signals: private: void rowsChanged(); void leaveEvent(QEvent *event); + void updateToolBar(); + void setSparklineRange(int value); + + struct TreeView : public QTreeView { + TreeView(QWidget *parent) : QTreeView(parent) {} + void rowsInserted(const QModelIndex &parent, int start, int end) override { + ((SignalView *)parentWidget())->rowsChanged(); + // update widget geometries in QTreeView::rowsInserted + QTreeView::rowsInserted(parent, start, end); + } + }; - MessageId msg_id; - QTreeView *tree; + TreeView *tree; + QLabel *sparkline_label; + QSlider *sparkline_range_slider; QLineEdit *filter_edit; ChartsWidget *charts; QLabel *signal_count_lb; + friend SignalItemDelegate; }; diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index c8ff449098..d60beed0c8 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -1,6 +1,5 @@ #include "tools/cabana/streams/abstractstream.h" #include -#include AbstractStream *can = nullptr; @@ -13,7 +12,7 @@ AbstractStream::AbstractStream(QObject *parent, bool is_live_streaming) : is_liv void AbstractStream::process(QHash *messages) { for (auto it = messages->begin(); it != messages->end(); ++it) { - can_msgs[it.key()] = it.value(); + last_msgs[it.key()] = it.value(); } emit updated(); emit msgsReceived(messages); @@ -62,66 +61,68 @@ bool AbstractStream::updateEvent(const Event *event) { const CanData &AbstractStream::lastMessage(const MessageId &id) { static CanData empty_data; - auto it = can_msgs.find(id); - return it != can_msgs.end() ? it.value() : empty_data; + auto it = last_msgs.find(id); + return it != last_msgs.end() ? it.value() : empty_data; } -static QHash parseEvents(std::vector::const_reverse_iterator first, - std::vector::const_reverse_iterator last, double route_start_time) { - QHash msgs; - msgs.reserve(500); - for (auto it = first; it != last; ++it) { - if ((*it)->which == cereal::Event::Which::CAN) { - for (const auto &c : (*it)->event.getCan()) { - auto &m = msgs[{.source = c.getSrc(), .address = c.getAddress()}]; - if (++m.count == 1) { - m.ts = ((*it)->mono_time / 1e9) - route_start_time; - m.dat = QByteArray((char *)c.getDat().begin(), c.getDat().size()); - m.colors = QVector(m.dat.size(), QColor(0, 0, 0, 0)); - m.last_change_t = QVector(m.dat.size(), m.ts); - m.bit_change_counts.resize(m.dat.size()); - } - } - } - } - return msgs; -}; - // it is thread safe to update data in updateLastMsgsTo. // updateEvent will not be called before replayStream::seekedTo return. void AbstractStream::updateLastMsgsTo(double sec) { - uint64_t ts = (sec + routeStartTime()) * 1e9; - const uint64_t delta = std::max(std::ceil(sec / std::thread::hardware_concurrency()), 30.0) * 1e9; - const auto evs = events(); - auto first = std::upper_bound(evs->crbegin(), evs->crend(), ts, [](uint64_t ts, auto &e) { return ts > e->mono_time; }); - QFutureSynchronizer> synchronizer; - while(first != evs->crend()) { - ts = (*first)->mono_time > delta ? (*first)->mono_time - delta : 0; - auto last = std::lower_bound(first, evs->crend(), ts, [](auto &e, uint64_t ts) { return e->mono_time > ts; }); - synchronizer.addFuture(QtConcurrent::run(parseEvents, first, last, routeStartTime())); - first = last; - } - synchronizer.waitForFinished(); - new_msgs->clear(); change_trackers.clear(); - can_msgs.clear(); + last_msgs.clear(); counters.clear(); - for (const auto &f : synchronizer.futures()) { - auto msgs = f.result(); - for (auto it = msgs.cbegin(); it != msgs.cend(); ++it) { - counters[it.key()] += it.value().count; - auto m = can_msgs.find(it.key()); - if (m == can_msgs.end()) { - m = can_msgs.insert(it.key(), it.value()); - } else { - m.value().count += it.value().count; - } - m.value().freq = m.value().count / std::max(1.0, m.value().ts); + + CanEvent last_event = {.mono_time = uint64_t((sec + routeStartTime()) * 1e9)}; + for (auto &[id, e] : events_) { + auto it = std::lower_bound(e.crbegin(), e.crend(), last_event, std::greater()); + if (it != e.crend()) { + auto &m = last_msgs[id]; + m.dat = QByteArray((const char *)it->dat, it->size); + m.ts = it->mono_time / 1e9 - routeStartTime(); + m.count = std::distance(it, e.crend()); + m.freq = m.count / std::max(1.0, m.ts); + m.last_change_t = QVector(m.dat.size(), m.ts); + m.colors = QVector(m.dat.size(), QColor(0, 0, 0, 0)); + m.bit_change_counts = QVector>(m.dat.size()); + counters[id] = m.count; } } QTimer::singleShot(0, [this]() { emit updated(); - emit msgsReceived(&can_msgs); + emit msgsReceived(&last_msgs); }); } + +void AbstractStream::parseEvents(std::unordered_map> &msgs, + std::vector::const_iterator first, std::vector::const_iterator last) { + for (; first != last; ++first) { + if ((*first)->which == cereal::Event::Which::CAN) { + for (const auto &c : (*first)->event.getCan()) { + auto dat = c.getDat(); + auto &m = msgs[{.source = c.getSrc(), .address = c.getAddress()}].emplace_back(); + m.size = std::min(dat.size(), std::size(m.dat)); + memcpy(m.dat, (uint8_t *)dat.begin(), m.size); + m.mono_time = (*first)->mono_time; + } + last_event_ts = std::max(last_event_ts, (*first)->mono_time); + } + } +} + +void AbstractStream::mergeEvents(std::vector::const_iterator first, std::vector::const_iterator last, bool append) { + if (first == last) return; + + if (append) { + parseEvents(events_, first, last); + } else { + std::unordered_map> new_events; + parseEvents(new_events, first, last); + for (auto &[id, new_e] : new_events) { + auto &e = events_[id]; + auto it = std::upper_bound(e.cbegin(), e.cend(), new_e.front()); + e.insert(it, new_e.cbegin(), new_e.cend()); + } + } + emit eventsMerged(); +} diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index de7c394303..0515f7c7e8 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -1,11 +1,12 @@ #pragma once #include - +#include +#include #include #include -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/settings.h" #include "tools/cabana/util.h" #include "tools/replay/replay.h" @@ -20,6 +21,14 @@ struct CanData { QVector> bit_change_counts; }; +struct CanEvent { + uint64_t mono_time; + uint8_t size; + uint8_t dat[64]; + inline bool operator<(const CanEvent &r) const { return mono_time < r.mono_time; } + inline bool operator>(const CanEvent &r) const { return mono_time > r.mono_time; } +}; + class AbstractStream : public QObject { Q_OBJECT @@ -27,6 +36,7 @@ public: AbstractStream(QObject *parent, bool is_live_streaming); virtual ~AbstractStream() {}; inline bool liveStreaming() const { return is_live_streaming; } + inline double lastEventSecond() const { return last_event_ts / 1e9 - routeStartTime(); } virtual void seekTo(double ts) {} virtual QString routeName() const = 0; virtual QString carFingerprint() const { return ""; } @@ -37,11 +47,13 @@ public: virtual const CanData &lastMessage(const MessageId &id); virtual VisionStreamType visionStreamType() const { return VISION_STREAM_ROAD; } virtual const Route *route() const { return nullptr; } - virtual const std::vector *events() const = 0; virtual void setSpeed(float speed) {} virtual bool isPaused() const { return false; } virtual void pause(bool pause) {} + virtual const std::vector *rawEvents() const { return nullptr; } + const std::unordered_map> &events() const { return events_; } virtual const std::vector> getTimeline() { return {}; } + void mergeEvents(std::vector::const_iterator first, std::vector::const_iterator last, bool append); signals: void paused(); @@ -52,22 +64,25 @@ signals: void updated(); void msgsReceived(const QHash *); void received(QHash *); - void sourcesUpdated(const QSet &s); + void sourcesUpdated(const SourceSet &s); public: - QHash can_msgs; - QSet sources; + QHash last_msgs; + SourceSet sources; protected: - void process(QHash *); + virtual void process(QHash *); bool updateEvent(const Event *event); void updateLastMsgsTo(double sec); + void parseEvents(std::unordered_map> &msgs, std::vector::const_iterator first, std::vector::const_iterator last); bool is_live_streaming = false; std::atomic processing = false; QHash counters; std::unique_ptr> new_msgs; QHash change_trackers; + std::unordered_map> events_; + uint64_t last_event_ts = 0; }; // A global pointer referring to the unique AbstractStream object diff --git a/tools/cabana/streams/livestream.cc b/tools/cabana/streams/livestream.cc index 592d883c4c..62ad635152 100644 --- a/tools/cabana/streams/livestream.cc +++ b/tools/cabana/streams/livestream.cc @@ -1,10 +1,8 @@ #include "tools/cabana/streams/livestream.h" -LiveStream::LiveStream(QObject *parent, QString address) : zmq_address(address), AbstractStream(parent, true) { - timer = new QTimer(this); - timer->callOnTimeout(this, &LiveStream::removeExpiredEvents); - timer->start(3 * 1000); +#include +LiveStream::LiveStream(QObject *parent, QString address) : zmq_address(address), AbstractStream(parent, true) { stream_thread = new QThread(this); QObject::connect(stream_thread, &QThread::started, [=]() { streamThread(); }); QObject::connect(stream_thread, &QThread::finished, stream_thread, &QThread::deleteLater); @@ -15,8 +13,6 @@ LiveStream::~LiveStream() { stream_thread->requestInterruption(); stream_thread->quit(); stream_thread->wait(); - for (Event *e : can_events) ::delete e; - for (auto m : messages) delete m; } void LiveStream::streamThread() { @@ -35,11 +31,8 @@ void LiveStream::streamThread() { QThread::msleep(50); continue; } - AlignedBuffer *buf = messages.emplace_back(new AlignedBuffer()); - Event *evt = ::new Event(buf->align(msg)); - delete msg; - - handleEvent(evt); + std::lock_guard lk(lock); + handleEvent(messages.emplace_back(msg).event); // TODO: write stream to log file to replay it with cabana --data_dir flag. } } @@ -53,14 +46,13 @@ void LiveStream::handleEvent(Event *evt) { emit streamStarted(); } - std::lock_guard lk(lock); - can_events.push_back(evt); + received.push_back(evt); if (!pause_) { if (speed_ < 1 && last_update_ts > 0) { - auto it = std::upper_bound(can_events.cbegin(), can_events.cend(), current_ts, [](uint64_t ts, auto &e) { + auto it = std::upper_bound(received.cbegin(), received.cend(), current_ts, [](uint64_t ts, auto &e) { return ts < e->mono_time; }); - if (it != can_events.cend()) { + if (it != received.cend()) { bool skip = (nanos_since_boot() - last_update_ts) < ((*it)->mono_time - current_ts) / speed_; if (skip) return; @@ -73,27 +65,19 @@ void LiveStream::handleEvent(Event *evt) { } } -void LiveStream::removeExpiredEvents() { - std::lock_guard lk(lock); - if (can_events.size() > 0) { - const uint64_t max_ns = settings.max_cached_minutes * 60 * 1e9; - const uint64_t last_ns = can_events.back()->mono_time; - while (!can_events.empty() && (last_ns - can_events.front()->mono_time) > max_ns) { - ::delete can_events.front(); - delete messages.front(); - can_events.pop_front(); - messages.pop_front(); +void LiveStream::process(QHash *last_messages) { + { + std::lock_guard lk(lock); + auto first = std::upper_bound(received.cbegin(), received.cend(), last_event_ts, [](uint64_t ts, auto &e) { + return ts < e->mono_time; + }); + mergeEvents(first, received.cend(), true); + if (speed_ == 1) { + received.clear(); + messages.clear(); } } -} - -const std::vector *LiveStream::events() const { - std::lock_guard lk(lock); - if (events_vector.capacity() <= can_events.size()) { - events_vector.reserve(can_events.size() * 2); - } - events_vector.assign(can_events.begin(), can_events.end()); - return &events_vector; + AbstractStream::process(last_messages); } void LiveStream::pause(bool pause) { diff --git a/tools/cabana/streams/livestream.h b/tools/cabana/streams/livestream.h index 43f67ae95f..598c0b9365 100644 --- a/tools/cabana/streams/livestream.h +++ b/tools/cabana/streams/livestream.h @@ -1,6 +1,5 @@ #pragma once -#include #include "tools/cabana/streams/abstractstream.h" class LiveStream : public AbstractStream { @@ -17,17 +16,25 @@ public: void setSpeed(float speed) override { speed_ = std::min(1.0, speed); } bool isPaused() const override { return pause_; } void pause(bool pause) override; - const std::vector *events() const override; protected: virtual void handleEvent(Event *evt); virtual void streamThread(); - virtual void removeExpiredEvents(); + void process(QHash *) override; + + struct Msg { + Msg(Message *m) { + event = ::new Event(aligned_buf.align(m)); + delete m; + } + ~Msg() { ::delete event; } + Event *event; + AlignedBuffer aligned_buf; + }; mutable std::mutex lock; - mutable std::vector events_vector; - std::deque can_events; - std::deque messages; + std::vector received; + std::deque messages; std::atomic start_ts = 0; std::atomic current_ts = 0; std::atomic speed_ = 1; @@ -36,5 +43,4 @@ protected: const QString zmq_address; QThread *stream_thread; - QTimer *timer; }; diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index ebd969be61..8bc9af8ab2 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -14,19 +14,25 @@ static bool event_filter(const Event *e, void *opaque) { return ((ReplayStream *)opaque)->eventFilter(e); } +void ReplayStream::mergeSegments() { + for (auto &[n, seg] : replay->segments()) { + if (seg && seg->isLoaded() && !processed_segments.count(n)) { + const auto &events = seg->log->events; + bool append = processed_segments.empty() || *processed_segments.rbegin() < n; + processed_segments.insert(n); + mergeEvents(events.cbegin(), events.cend(), append); + } + } +} + bool ReplayStream::loadRoute(const QString &route, const QString &data_dir) { replay.reset(new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, this)); replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->installEventFilter(event_filter, this); QObject::connect(replay.get(), &Replay::seekedTo, this, &AbstractStream::seekedTo); - QObject::connect(replay.get(), &Replay::segmentsMerged, this, &AbstractStream::eventsMerged); QObject::connect(replay.get(), &Replay::streamStarted, this, &AbstractStream::streamStarted); + QObject::connect(replay.get(), &Replay::segmentsMerged, this, &ReplayStream::mergeSegments); if (replay->load()) { - const auto &segments = replay->route()->segments(); - if (std::none_of(segments.begin(), segments.end(), [](auto &s) { return s.second.rlog.length() > 0; })) { - qWarning() << "no rlogs in route" << route; - return false; - } replay->start(); return true; } diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index 3505f6abf4..10dc804716 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -20,13 +20,15 @@ public: inline double currentSec() const override { return replay->currentSeconds(); } inline QDateTime currentDateTime() const override { return replay->currentDateTime(); } inline const Route *route() const override { return replay->route(); } - inline const std::vector *events() const override { return replay->events(); } inline void setSpeed(float speed) override { replay->setSpeed(speed); } inline bool isPaused() const override { return replay->isPaused(); } void pause(bool pause) override; + const std::vector *rawEvents() const override { return replay->events(); } inline const std::vector> getTimeline() override { return replay->getTimeline(); } private: + void mergeSegments(); std::unique_ptr replay = nullptr; uint32_t replay_flags = REPLAY_FLAG_NONE; + std::set processed_segments; }; diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index 8445867acb..4bc01a6a81 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -3,21 +3,20 @@ #undef INFO #include "catch2/catch.hpp" #include "tools/replay/logreader.h" -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" // demo route, first segment const std::string TEST_RLOG_URL = "https://commadata2.blob.core.windows.net/commadata2/4cf7a6ad03080c90/2021-09-29--13-46-36/0/rlog.bz2"; -TEST_CASE("DBCManager::generateDBC") { - DBCManager dbc_origin(nullptr); - dbc_origin.open("toyota_new_mc_pt_generated"); - DBCManager dbc_from_generated(nullptr); - dbc_from_generated.open("", dbc_origin.generateDBC()); +TEST_CASE("DBCFile::generateDBC") { + QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "toyota_new_mc_pt_generated"); + DBCFile dbc_origin(fn); + DBCFile dbc_from_generated("", dbc_origin.generateDBC()); REQUIRE(dbc_origin.msgCount() == dbc_from_generated.msgCount()); - auto msgs = dbc_origin.getMessages(0); - auto new_msgs = dbc_from_generated.getMessages(0); + auto msgs = dbc_origin.getMessages(); + auto new_msgs = dbc_from_generated.getMessages(); for (auto &[id, m] : msgs) { auto &new_m = new_msgs.at(id); REQUIRE(m.name == new_m.name); @@ -33,7 +32,7 @@ TEST_CASE("DBCManager::generateDBC") { TEST_CASE("Parse can messages") { DBCManager dbc(nullptr); - dbc.open("toyota_new_mc_pt_generated"); + dbc.open({0}, "toyota_new_mc_pt_generated"); CANParser can_parser(0, "toyota_new_mc_pt_generated", {}, {}); LogReader log; diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc index 7768359df4..7fe5b26671 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -1,5 +1,6 @@ #include "tools/cabana/tools/findsimilarbits.h" +#include #include #include #include @@ -7,7 +8,7 @@ #include #include -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::WindowFlags() | Qt::Window) { @@ -16,20 +17,23 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi QVBoxLayout *main_layout = new QVBoxLayout(this); - QHBoxLayout *form_layout = new QHBoxLayout(); - bus_combo = new QComboBox(this); - QSet bus_set; - for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) { + QHBoxLayout *src_layout = new QHBoxLayout(); + src_bus_combo = new QComboBox(this); + find_bus_combo = new QComboBox(this); + SourceSet bus_set; + for (auto it = can->last_msgs.begin(); it != can->last_msgs.end(); ++it) { bus_set << it.key().source; } - for (uint8_t bus : bus_set) { - bus_combo->addItem(QString::number(bus), bus); + for (auto cb : {src_bus_combo, find_bus_combo}) { + for (uint8_t bus : bus_set) { + cb->addItem(QString::number(bus), bus); + } + cb->model()->sort(0); + cb->setCurrentIndex(0); } - bus_combo->model()->sort(0); - bus_combo->setCurrentIndex(0); msg_cb = new QComboBox(this); - // TODO: update when bus_combo changes + // TODO: update when src_bus_combo changes for (auto &[id, msg] : dbc()->getMessages(0)) { msg_cb->addItem(msg.name, id.address); } @@ -37,29 +41,44 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi msg_cb->setCurrentIndex(0); byte_idx_sb = new QSpinBox(this); + byte_idx_sb->setFixedWidth(50); byte_idx_sb->setRange(0, 63); bit_idx_sb = new QSpinBox(this); + bit_idx_sb->setFixedWidth(50); bit_idx_sb->setRange(0, 7); - form_layout->addWidget(new QLabel("Bus")); - form_layout->addWidget(bus_combo); - form_layout->addWidget(msg_cb); - form_layout->addWidget(new QLabel("Byte Index")); - form_layout->addWidget(byte_idx_sb); - form_layout->addWidget(new QLabel("Bit Index")); - form_layout->addWidget(bit_idx_sb); - - + src_layout->addWidget(new QLabel(tr("Bus"))); + src_layout->addWidget(src_bus_combo); + src_layout->addWidget(msg_cb); + src_layout->addWidget(new QLabel(tr("Byte Index"))); + src_layout->addWidget(byte_idx_sb); + src_layout->addWidget(new QLabel(tr("Bit Index"))); + src_layout->addWidget(bit_idx_sb); + src_layout->addStretch(0); + + QHBoxLayout *find_layout = new QHBoxLayout(); + find_layout->addWidget(new QLabel(tr("Bus"))); + find_layout->addWidget(find_bus_combo); + find_layout->addWidget(new QLabel(tr("Equal"))); + equal_combo = new QComboBox(this); + equal_combo->addItems({"Yes", "No"}); + find_layout->addWidget(equal_combo); min_msgs = new QLineEdit(this); min_msgs->setValidator(new QIntValidator(this)); min_msgs->setText("100"); - form_layout->addWidget(new QLabel("Min msg count")); - form_layout->addWidget(min_msgs); + find_layout->addWidget(new QLabel(tr("Min msg count"))); + find_layout->addWidget(min_msgs); search_btn = new QPushButton(tr("&Find"), this); - form_layout->addWidget(search_btn); - form_layout->addStretch(1); - main_layout->addLayout(form_layout); + find_layout->addWidget(search_btn); + find_layout->addStretch(0); + + QGridLayout *grid_layout = new QGridLayout(); + grid_layout->addWidget(new QLabel("Find From:"), 0, 0); + grid_layout->addLayout(src_layout, 0, 1); + grid_layout->addWidget(new QLabel("Find In:"), 1, 0); + grid_layout->addLayout(find_layout, 1, 1); + main_layout->addLayout(grid_layout); table = new QTableWidget(this); table->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -70,10 +89,9 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi setMinimumSize({700, 500}); QObject::connect(search_btn, &QPushButton::clicked, this, &FindSimilarBitsDlg::find); - QObject::connect(table, &QTableWidget::doubleClicked, [this](const QModelIndex &index) { if (index.isValid()) { - MessageId msg_id = {.source = (uint8_t)bus_combo->currentData().toUInt(), .address = table->item(index.row(), 0)->text().toUInt(0, 16)}; + MessageId msg_id = {.source = (uint8_t)find_bus_combo->currentData().toUInt(), .address = table->item(index.row(), 0)->text().toUInt(0, 16)}; emit openMessage(msg_id); } }); @@ -83,7 +101,8 @@ void FindSimilarBitsDlg::find() { search_btn->setEnabled(false); table->clear(); uint32_t selected_address = msg_cb->currentData().toUInt(); - auto msg_mismatched = calcBits(bus_combo->currentText().toUInt(), selected_address, byte_idx_sb->value(), bit_idx_sb->value(), min_msgs->text().toInt()); + auto msg_mismatched = calcBits(src_bus_combo->currentText().toUInt(), selected_address, byte_idx_sb->value(), bit_idx_sb->value(), + find_bus_combo->currentText().toUInt(), equal_combo->currentIndex() == 0, min_msgs->text().toInt()); table->setRowCount(msg_mismatched.size()); table->setColumnCount(6); table->setHorizontalHeaderLabels({"address", "byte idx", "bit idx", "mismatches", "total msgs", "% mismatched"}); @@ -94,25 +113,29 @@ void FindSimilarBitsDlg::find() { table->setItem(i, 2, new QTableWidgetItem(QString::number(m.bit_idx))); table->setItem(i, 3, new QTableWidgetItem(QString::number(m.mismatches))); table->setItem(i, 4, new QTableWidgetItem(QString::number(m.total))); - table->setItem(i, 5, new QTableWidgetItem(QString::number(m.perc))); + table->setItem(i, 5, new QTableWidgetItem(QString::number(m.perc, 'f', 2))); } search_btn->setEnabled(true); } -QList FindSimilarBitsDlg::calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, int min_msgs_cnt) { +QList FindSimilarBitsDlg::calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, + int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt) { QHash> mismatches; QHash msg_count; - auto events = can->events(); + auto events = can->rawEvents(); int bit_to_find = -1; for (auto e : *events) { if (e->which == cereal::Event::Which::CAN) { for (const auto &c : e->event.getCan()) { - if (c.getSrc() == bus) { - const auto dat = c.getDat(); - uint32_t address = c.getAddress(); + uint8_t src = c.getSrc(); + uint32_t address = c.getAddress(); + const auto dat = c.getDat(); + if (src == bus) { if (address == selected_address && dat.size() > byte_idx) { bit_to_find = ((dat[byte_idx] >> (7 - bit_idx)) & 1) != 0; } + } + if (src == find_bus) { ++msg_count[address]; if (bit_to_find == -1) continue; @@ -123,7 +146,7 @@ QList FindSimilarBitsDlg::calcBits(uint8_ for (int i = 0; i < dat.size(); ++i) { for (int j = 0; j < 8; ++j) { int bit = ((dat[i] >> (7 - j)) & 1) != 0; - mismatched[i * 8 + j] += (bit != bit_to_find); + mismatched[i * 8 + j] += equal ? (bit != bit_to_find) : (bit == bit_to_find); } } } diff --git a/tools/cabana/tools/findsimilarbits.h b/tools/cabana/tools/findsimilarbits.h index 53d7806a8f..77bfac19ca 100644 --- a/tools/cabana/tools/findsimilarbits.h +++ b/tools/cabana/tools/findsimilarbits.h @@ -6,7 +6,7 @@ #include #include -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/dbc/dbcmanager.h" class FindSimilarBitsDlg : public QDialog { Q_OBJECT @@ -22,11 +22,12 @@ private: uint32_t address, byte_idx, bit_idx, mismatches, total; float perc; }; - QList calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, int min_msgs_cnt); + QList calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, uint8_t find_bus, + bool equal, int min_msgs_cnt); void find(); QTableWidget *table; - QComboBox *bus_combo, *msg_cb; + QComboBox *src_bus_combo, *find_bus_combo, *msg_cb, *equal_combo; QSpinBox *byte_idx_sb, *bit_idx_sb; QPushButton *search_btn; QLineEdit *min_msgs; diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index b37b7911ed..e5a9749103 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -4,10 +4,8 @@ #include #include #include -#include - -#include #include +#include #include "selfdrive/ui/qt/util.h" @@ -42,7 +40,7 @@ void ChangeTracker::compute(const QByteArray &dat, double ts, uint32_t freq) { } // Track bit level changes - for (int bit = 0; bit < 8; bit++){ + for (int bit = 0; bit < 8; bit++) { if ((cur ^ last) & (1 << bit)) { bit_change_counts[i][bit] += 1; } @@ -67,7 +65,6 @@ void ChangeTracker::clear() { colors.clear(); } - // SegmentTree void SegmentTree::build(const QVector &arr) { @@ -116,7 +113,7 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem & int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); QRect rc{option.rect.left() + h_margin, option.rect.top() + v_margin, byte_width, option.rect.height() - 2 * v_margin}; - auto color_role = option.state & QStyle::State_Selected ? QPalette::HighlightedText: QPalette::Text; + auto color_role = option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text; painter->setPen(option.palette.color(color_role)); painter->setFont(fixed_font); for (int i = 0; i < byte_list.size(); ++i) { @@ -139,7 +136,7 @@ QColor getColor(const cabana::Signal *sig) { return QColor::fromHsvF(h, s, v); } -NameValidator::NameValidator(QObject *parent) : QRegExpValidator(QRegExp("^(\\w+)"), parent) { } +NameValidator::NameValidator(QObject *parent) : QRegExpValidator(QRegExp("^(\\w+)"), parent) {} QValidator::State NameValidator::validate(QString &input, int &pos) const { input.replace(' ', '_'); @@ -175,7 +172,6 @@ QToolButton *toolButton(const QString &icon, const QString &tooltip) { return btn; }; - QString toHex(uint8_t byte) { static std::array hex = []() { std::array ret; @@ -186,11 +182,11 @@ QString toHex(uint8_t byte) { } int num_decimals(double num) { - const QString string = QString::number(num); - const QStringList split = string.split('.'); - if (split.size() == 1) { - return 0; - } else { - return split[1].size(); - } - } + const QString string = QString::number(num); + const QStringList split = string.split('.'); + if (split.size() == 1) { + return 0; + } else { + return split[1].size(); + } +} diff --git a/tools/cabana/util.h b/tools/cabana/util.h index 3cf14982a9..e90a838af8 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -12,7 +13,7 @@ #include #include -#include "tools/cabana/dbc.h" +#include "tools/cabana/dbc/dbc.h" class ChangeTracker { public: @@ -36,17 +37,26 @@ class LogSlider : public QSlider { public: LogSlider(double factor, Qt::Orientation orientation, QWidget *parent = nullptr) : factor(factor), QSlider(orientation, parent) {}; - void setRange(double min, double max) { QSlider::setRange(logScale(min), logScale(max)); } - int value() const { return invLogScale(QSlider::value()); } - void setValue(int value) { QSlider::setValue(logScale(value)); } + void setRange(double min, double max) { + log_min = factor * std::log10(min); + log_max = factor * std::log10(max); + QSlider::setRange(min, max); + setValue(QSlider::value()); + } + int value() const { + double v = log_min + (log_max - log_min) * ((QSlider::value() - minimum()) / double(maximum() - minimum())); + return std::lround(std::pow(10, v / factor)); + } + void setValue(int v) { + double log_v = std::clamp(factor * std::log10(v), log_min, log_max); + v = minimum() + (maximum() - minimum()) * ((log_v - log_min) / (log_max - log_min)); + QSlider::setValue(v); + } private: - double factor; - int logScale(int value) const { return factor * std::log10(value); } - int invLogScale(int value) const { return std::pow(10, value / factor); } + double factor, log_min = 0, log_max = 1; }; - enum { ColorsRole = Qt::UserRole + 1, BytesRole = Qt::UserRole + 2 @@ -88,6 +98,9 @@ public: namespace utils { QPixmap icon(const QString &id); +inline QString formatSeconds(int seconds) { + return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); +} } QToolButton *toolButton(const QString &icon, const QString &tooltip); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 536ae7ba97..fe9e00bc45 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -1,18 +1,15 @@ #include "tools/cabana/videowidget.h" -#include #include -#include #include #include -#include #include -#include -#include -#include #include #include +const int MIN_VIDEO_HEIGHT = 100; +const int THUMBNAIL_MARGIN = 3; + static const QColor timeline_colors[] = { [(int)TimelineType::None] = QColor(111, 143, 175), [(int)TimelineType::Engaged] = QColor(0, 163, 108), @@ -22,20 +19,11 @@ static const QColor timeline_colors[] = { [(int)TimelineType::AlertCritical] = QColor(199, 0, 57), }; -inline QString formatTime(int seconds) { - return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); -} - -VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(0, 0, 0, 0); - QFrame *frame = new QFrame(this); - frame->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); - main_layout->addWidget(frame); - - QVBoxLayout *frame_layout = new QVBoxLayout(frame); +VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) { + setFrameStyle(QFrame::StyledPanel | QFrame::Plain); + auto main_layout = new QVBoxLayout(this); if (!can->liveStreaming()) { - frame_layout->addWidget(createCameraWidget()); + main_layout->addWidget(createCameraWidget()); } // btn controls @@ -56,7 +44,8 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { group->addButton(btn); if (speed == 1.0) btn->setChecked(true); } - frame_layout->addLayout(control_layout); + main_layout->addLayout(control_layout); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); QObject::connect(play_btn, &QPushButton::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState); @@ -90,12 +79,13 @@ QWidget *VideoWidget::createCameraWidget() { QVBoxLayout *l = new QVBoxLayout(w); l->setContentsMargins(0, 0, 0, 0); cam_widget = new CameraWidget("camerad", can->visionStreamType(), false); + cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT); + cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); l->addWidget(cam_widget); // slider controls slider_layout = new QHBoxLayout(); - time_label = new ElidedLabel("00:00"); - time_label->setToolTip(tr("Click to set current time")); + time_label = new QLabel("00:00"); slider_layout->addWidget(time_label); slider = new Slider(this); @@ -105,51 +95,23 @@ QWidget *VideoWidget::createCameraWidget() { end_time_label = new QLabel(this); slider_layout->addWidget(end_time_label); l->addLayout(slider_layout); - - cam_widget->setMinimumHeight(100); - cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - - QObject::connect(time_label, &ElidedLabel::clicked, this, &VideoWidget::timeLabelClicked); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); }); - QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); }); + QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(value / 1000)); }); QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState); QObject::connect(can, &AbstractStream::streamStarted, [this]() { - end_time_label->setText(formatTime(can->totalSeconds())); + end_time_label->setText(utils::formatSeconds(can->totalSeconds())); slider->setRange(0, can->totalSeconds() * 1000); }); return w; } -void VideoWidget::timeLabelClicked() { - auto time_edit = new QTimeEdit(this); - auto init_date_time = can->currentDateTime(); - time_edit->setDateTime(init_date_time); - time_edit->setDisplayFormat("hh:mm:ss"); - time_label->setVisible(false); - slider_layout->insertWidget(0, time_edit); - QTimer::singleShot(0, [=]() { time_edit->setFocus(); }); - - QObject::connect(time_edit, &QTimeEdit::editingFinished, [=]() { - if (time_edit->dateTime() != init_date_time) { - int seconds = can->route()->datetime().secsTo(time_edit->dateTime()); - can->seekTo(seconds); - } - time_edit->setVisible(false); - time_label->setVisible(true); - time_edit->deleteLater(); - }); -} - void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) { - if (can->liveStreaming()) return; - if (!is_zoomed) { min = 0; max = can->totalSeconds(); } - end_time_label->setText(formatTime(max)); + end_time_label->setText(utils::formatSeconds(max)); slider->setRange(min * 1000, max * 1000); } @@ -164,16 +126,12 @@ void VideoWidget::updatePlayBtnState() { } // Slider -Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { - QTimer *timer = new QTimer(this); - timer->setInterval(2000); - timer->callOnTimeout([this]() { +Slider::Slider(QWidget *parent) : timer(this), thumbnail_label(this), QSlider(Qt::Horizontal, parent) { + timer.callOnTimeout([this]() { timeline = can->getTimeline(); update(); }); setMouseTracking(true); - - QObject::connect(can, SIGNAL(streamStarted()), timer, SLOT(start())); QObject::connect(can, &AbstractStream::streamStarted, this, &Slider::streamStarted); } @@ -187,11 +145,13 @@ void Slider::streamStarted() { thumnail_future.waitForFinished(); abort_load_thumbnail = false; thumbnails.clear(); + timeline.clear(); + timer.start(2000); thumnail_future = QtConcurrent::run(this, &Slider::loadThumbnails); } void Slider::loadThumbnails() { - const auto segments = can->route()->segments(); + const auto &segments = can->route()->segments(); for (auto it = segments.rbegin(); it != segments.rend() && !abort_load_thumbnail; ++it) { std::string qlog = it->second.qlog.toStdString(); if (!qlog.empty()) { @@ -199,29 +159,18 @@ void Slider::loadThumbnails() { if (log.load(qlog, &abort_load_thumbnail, {cereal::Event::Which::THUMBNAIL}, true, 0, 3)) { for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_load_thumbnail; ++ev) { auto thumb = (*ev)->event.getThumbnail(); - QString str = getThumbnailString(thumb.getThumbnail()); - std::lock_guard lk(thumbnail_lock); - thumbnails[thumb.getTimestampEof()] = str; + 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; + } } } } } } -QString Slider::getThumbnailString(const capnp::Data::Reader &data) { - QPixmap thumb; - if (thumb.loadFromData(data.begin(), data.size(), "jpeg")) { - thumb = thumb.scaled({thumb.width()/3, thumb.height()/3}, Qt::KeepAspectRatio); - thumbnail_size = thumb.size(); - QByteArray bytes; - QBuffer buffer(&bytes); - buffer.open(QIODevice::WriteOnly); - thumb.save(&buffer, "png"); - return QString("").arg(QString(bytes.toBase64())); - } - return {}; -} - void Slider::sliderChange(QAbstractSlider::SliderChange change) { if (change == QAbstractSlider::SliderValueChange) { int x = width() * ((value() - minimum()) / double(maximum() - minimum())); @@ -267,16 +216,45 @@ void Slider::mousePressEvent(QMouseEvent *e) { } void Slider::mouseMoveEvent(QMouseEvent *e) { - QString thumb; + QPixmap thumb; + double seconds = (minimum() + e->pos().x() * ((maximum() - minimum()) / (double)width())) / 1000.0; { - double seconds = (minimum() + e->pos().x() * ((maximum() - minimum()) / (double)width())) / 1000.0; std::lock_guard lk(thumbnail_lock); auto it = thumbnails.lowerBound((seconds + can->routeStartTime()) * 1e9); - if (it != thumbnails.end()) { - thumb = it.value(); - } + if (it != thumbnails.end()) thumb = it.value(); } - QPoint pt = mapToGlobal({e->pos().x() - thumbnail_size.width() / 2, -thumbnail_size.height() - 30}); - QToolTip::showText(pt, thumb, this, rect()); + int x = std::clamp(e->pos().x() - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN); + int y = -thumb.height() - THUMBNAIL_MARGIN - style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing); + thumbnail_label.showPixmap(mapToGlobal({x, y}), utils::formatSeconds(seconds), thumb); QSlider::mouseMoveEvent(e); } + +void Slider::leaveEvent(QEvent *event) { + thumbnail_label.hide(); + QSlider::leaveEvent(event); +} + +// ThumbnailLabel + +ThumbnailLabel::ThumbnailLabel(QWidget *parent) : QWidget(parent, Qt::Tool | Qt::FramelessWindowHint) { + setAttribute(Qt::WA_ShowWithoutActivating); + setVisible(false); +} + +void ThumbnailLabel::showPixmap(const QPoint &pt, const QString &sec, const QPixmap &pm) { + pixmap = pm; + second = sec; + setVisible(!pm.isNull()); + if (isVisible()) { + setGeometry({pt, pm.size()}); + update(); + } +} + +void ThumbnailLabel::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.drawPixmap(0, 0, pixmap); + p.setPen(QPen(Qt::white, 2)); + p.drawRect(rect()); + p.drawText(rect().adjusted(0, 0, 0, -THUMBNAIL_MARGIN), second, Qt::AlignHCenter | Qt::AlignBottom); +} diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index d10b09fec5..00b059428d 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -8,12 +8,20 @@ #include #include #include +#include #include "selfdrive/ui/qt/widgets/cameraview.h" -#include "selfdrive/ui/qt/widgets/controls.h" -#include "tools/cabana/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" +class ThumbnailLabel : public QWidget { +public: + ThumbnailLabel(QWidget *parent); + void showPixmap(const QPoint &pt, const QString &sec, const QPixmap &pm); + void paintEvent(QPaintEvent *event) override; + QPixmap pixmap; + QString second; +}; + class Slider : public QSlider { Q_OBJECT @@ -24,22 +32,23 @@ public: private: void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; + void leaveEvent(QEvent *event) override; void sliderChange(QAbstractSlider::SliderChange change) override; void paintEvent(QPaintEvent *ev) override; void streamStarted(); void loadThumbnails(); - QString getThumbnailString(const capnp::Data::Reader &data); int slider_x = -1; std::vector> timeline; std::mutex thumbnail_lock; std::atomic abort_load_thumbnail = false; - QMap thumbnails; + QMap thumbnails; QFuture thumnail_future; - QSize thumbnail_size = {}; + ThumbnailLabel thumbnail_label; + QTimer timer; }; -class VideoWidget : public QWidget { +class VideoWidget : public QFrame { Q_OBJECT public: @@ -49,12 +58,11 @@ public: protected: void updateState(); void updatePlayBtnState(); - void timeLabelClicked(); QWidget *createCameraWidget(); CameraWidget *cam_widget; QLabel *end_time_label; - ElidedLabel *time_label; + QLabel *time_label; QHBoxLayout *slider_layout; QPushButton *play_btn; Slider *slider; diff --git a/tools/gpstest/rpc_server.py b/tools/gpstest/rpc_server.py index a368566982..178e8b2c3c 100644 --- a/tools/gpstest/rpc_server.py +++ b/tools/gpstest/rpc_server.py @@ -17,7 +17,7 @@ ALT_DELTA = 30 MATCH_NUM = 10 REPORT_STATS = 10 -EPHEM_CACHE = "/data/params/d/LaikadEphemerisV2" +EPHEM_CACHE = "/data/params/d/LaikadEphemerisV3" DOWNLOAD_CACHE = "/tmp/comma_download_cache" SERVER_LOG_FILE = "/tmp/fuzzy_server.log" diff --git a/tools/gpstest/test_laikad.py b/tools/gpstest/test_laikad.py index 24ffa6c424..689b0f0b9f 100644 --- a/tools/gpstest/test_laikad.py +++ b/tools/gpstest/test_laikad.py @@ -39,7 +39,7 @@ class TestLaikad(unittest.TestCase): def setUp(self): # ensure laikad cold start - Params().remove("LaikadEphemerisV2") + Params().remove("LaikadEphemerisV3") os.environ["LAIKAD_NO_INTERNET"] = "1" managed_processes['laikad'].start() diff --git a/tools/plotjuggler/layouts/controls_mismatch_debug.xml b/tools/plotjuggler/layouts/controls_mismatch_debug.xml index adca3eed29..54586d6e7b 100644 --- a/tools/plotjuggler/layouts/controls_mismatch_debug.xml +++ b/tools/plotjuggler/layouts/controls_mismatch_debug.xml @@ -1,37 +1,44 @@ - + - + - - + + - - + + - - + + - + - - + + - + - - + + - - + + + + + + + + + @@ -46,8 +53,8 @@ - - + + diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 6788a97d03..2bb426361b 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -71,6 +71,7 @@ public: inline void setSpeed(float speed) { speed_ = speed; } inline float getSpeed() const { return speed_; } inline const std::vector *events() const { return events_.get(); } + inline const std::map> &segments() const { return segments_; }; inline const std::string &carFingerprint() const { return car_fingerprint_; } inline const std::vector> getTimeline() { std::lock_guard lk(timeline_lock); diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh index 71bad2e8a2..97b706de5b 100755 --- a/tools/ubuntu_setup.sh +++ b/tools/ubuntu_setup.sh @@ -53,6 +53,8 @@ function install_ubuntu_common_requirements() { libgles2-mesa-dev \ libglfw3-dev \ libglib2.0-0 \ + libncurses5-dev \ + libncursesw5-dev \ libomp-dev \ libopencv-dev \ libpng16-16 \