diff --git a/RELEASES.md b/RELEASES.md index 55b0dbad7e..55edc47c27 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,6 @@ 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! diff --git a/cereal b/cereal index b53ed5bd84..c5b9ea6464 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit b53ed5bd84cb6da6d21b04088a905dc631f031fe +Subproject commit c5b9ea64648c975db5d5a177c8a5de36ca9701c8 diff --git a/docs/CARS.md b/docs/CARS.md index 1f668dbe8b..7c8170c275 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -44,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|| @@ -126,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|| diff --git a/panda b/panda index 9cb545ddbc..ae96f8aae2 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 9cb545ddbcb93d30e564d471e673f32d2d9e63f7 +Subproject commit ae96f8aae21b92d715cb5188f60c23c07d1ad62a diff --git a/poetry.lock b/poetry.lock index 4abd6777ba..b7554ac03a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -119,6 +119,7 @@ packaging = ">20.6" [package.source] type = "url" 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" version = "1.4.4" @@ -459,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" @@ -1193,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" @@ -2608,6 +2631,7 @@ numpy = [ [package.source] type = "url" 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" version = "3.4.1" @@ -3359,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" @@ -4260,6 +4292,7 @@ opt-einsum = ["opt-einsum (>=3.3)"] [package.source] type = "url" url = "https://download.pytorch.org/whl/cu118/torch-2.0.0%2Bcu118-cp38-cp38-linux_x86_64.whl" + [[package]] name = "torchsummary" version = "1.5.1" @@ -4288,6 +4321,7 @@ scipy = ["scipy"] [package.source] type = "url" url = "https://download.pytorch.org/whl/cu118/torchvision-0.15.1%2Bcu118-cp38-cp38-linux_x86_64.whl" + [[package]] name = "tornado" version = "6.2" @@ -4343,6 +4377,25 @@ torch = "*" 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" @@ -4606,7 +4659,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "~3.8" -content-hash = "a16ec604c5251dc860a44f7133289d0b7b16120757f8c903f3917329753c52d1" +content-hash = "b7cd75dfa0dcddff224696ccc7f41d87aac64652f744ab386321c1eee920fbe9" [metadata.files] adal = [ @@ -4916,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"}, @@ -5700,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"}, @@ -6679,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"}, @@ -7348,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"}, @@ -7721,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"}, @@ -8090,6 +8378,15 @@ traitlets = [ {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"}, ] triton = [ + {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"}, @@ -8100,6 +8397,10 @@ triton = [ {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"}, {file = "types_atomicwrites-1.4.5.1-py3-none-any.whl", hash = "sha256:2f1febbdc78b55453b189fa5b136dce34bab7d1d82319163d470e404aab55c83"}, diff --git a/pyproject.toml b/pyproject.toml index 7506ee552d..47994779b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -174,6 +174,7 @@ 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/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/car/fw_versions.py b/selfdrive/car/fw_versions.py index 9bb04ee821..f85b7f6b7d 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -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 @@ -153,13 +155,13 @@ def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[EcuAddrBusType]: 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) @@ -190,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: @@ -247,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) @@ -271,10 +269,10 @@ 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 @@ -292,15 +290,14 @@ 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: diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index 4dc1dc8131..f6d328131b 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 @@ -127,7 +126,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 +161,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)) diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index 217f299a27..490a438ac9 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 @@ -247,8 +247,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..b7c448d1d3 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 @@ -58,27 +63,36 @@ def create_acc_commands(packer, enabled, active, accel, gas, stopping, car_finge standstill = 1 if active and stopping else 0 standstill_release = 1 if active and not stopping 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, } + + if car_fingerprint in HONDA_BOSCH_RADARLESS: + acc_control_values.update({ + "CONTROL_ON": enabled, + "IDLESTOP_ALLOW": 0, # disallows idle stop + }) + 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)) - - 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)) - return commands diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index d3cf9fa891..7314afd50a 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 = -4.0 else: # default longitudinal tuning for all hondas ret.longitudinalTuning.kpBP = [0., 5., 35.] diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 3e241c56d0..5c866012e3 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -390,14 +390,14 @@ FW_QUERY_CONFIG = FwQueryConfig( Request( [HYUNDAI_VERSION_REQUEST_ALT], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.parking], + whitelist_ecus=[Ecu.parkingAdas], bus=0, auxiliary=True, ), Request( [HYUNDAI_VERSION_REQUEST_ALT], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.parking], + whitelist_ecus=[Ecu.parkingAdas], bus=1, auxiliary=True, obd_multiplexing=False, @@ -405,7 +405,7 @@ FW_QUERY_CONFIG = FwQueryConfig( ], extra_ecus=[ (Ecu.adas, 0x730, None), # ADAS Driving ECU on HDA2 platforms - (Ecu.parking, 0x7b1, None), # ADAS Parking ECU (may exist on all platforms) + (Ecu.parkingAdas, 0x7b1, None), # ADAS Parking ECU (may exist on all platforms) (Ecu.hvac, 0x7b3, None), # HVAC Control Assembly (Ecu.cornerRadar, 0x7b7, None), ], @@ -1061,11 +1061,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): [ @@ -1674,9 +1686,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 ', ], }, 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/toyota/interface.py b/selfdrive/car/toyota/interface.py index 50a8f9ccae..ca8b941d21 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -2,7 +2,7 @@ from cereal import car from common.conversions import Conversions as CV from panda import Panda -from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_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 @@ -21,7 +21,8 @@ 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: diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 24e479a66e..ac231d5765 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -170,7 +170,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { # 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"), @@ -178,7 +178,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"), @@ -729,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', diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index e63975b0c0..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: { @@ -863,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', @@ -870,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', @@ -907,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', diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index 59176c586b..0c0bcbf7ef 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -220,16 +220,28 @@ 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) - self.astro_dog.add_qcom_polys({ephem.prn: [ephem]}) + 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': data_struct = ephemeris_structs.Ephemeris.new_message(**gnss_msg.ephemeris.to_dict()) - ephem = GPSEphemeris(data_struct, file_name='ublox') + try: + ephem = GPSEphemeris(data_struct, file_name='ublox') + except Exception: + cloudlog.exception("Error parsing GPS ephemeris from ublox") + return elif gnss_msg.which() == 'glonassEphemeris': data_struct = ephemeris_structs.GlonassEphemeris.new_message(**gnss_msg.glonassEphemeris.to_dict()) - ephem = GLONASSEphemeris(data_struct, file_name='ublox') + 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 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/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..d9156f229a 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', 'signaledit.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.h b/tools/cabana/binaryview.h index f677d11941..8598fe490b 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 { diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index c98ca3bc69..ef8524be37 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -9,8 +9,9 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -20,7 +21,7 @@ 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); @@ -55,6 +56,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); @@ -88,12 +92,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]() { @@ -120,7 +127,7 @@ void ChartsWidget::eventsMerged() { } } -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(); @@ -128,8 +135,26 @@ 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::zoomUndo() { + if (!zoom_stack.isEmpty()) { + auto r = zoom_stack.pop(); + setZoom(r.first, r.second); + } else { + zoomReset(); + } } void ChartsWidget::updateState() { @@ -145,7 +170,7 @@ void ChartsWidget::updateState() { 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); } @@ -167,6 +192,7 @@ void ChartsWidget::updateToolBar() { 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_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()); @@ -196,10 +222,10 @@ 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)); charts.push_back(chart); updateLayout(); return chart; @@ -301,6 +327,23 @@ 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) { @@ -311,7 +354,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}); @@ -437,24 +480,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(); + } } } @@ -475,7 +528,6 @@ void ChartView::updatePlot(double cur, double min, double max) { updateAxisY(); updateSeriesPoints(); } - scene()->invalidate({}, QGraphicsScene::ForegroundLayer); } @@ -489,7 +541,7 @@ void ChartView::updateSeriesPoints() { int pixels_per_point = width() / num_points; if (series_type == SeriesType::Scatter) { - ((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 2, 8)); + ((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 2, 8) * devicePixelRatioF()); } else { s.series->setPointsVisible(pixels_per_point > 20); } @@ -506,8 +558,8 @@ void ChartView::updateSeries(const cabana::Signal *sig) { } s.series->setColor(getColor(s.sig)); - 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}); + 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); @@ -568,7 +620,7 @@ void ChartView::updateAxisY() { 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; @@ -577,10 +629,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); } @@ -627,10 +679,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 @@ -638,7 +687,6 @@ void ChartView::mousePressEvent(QMouseEvent *event) { if (resume_after_scrub) { can->pause(true); } - is_scrubbing = true; } } else { @@ -671,7 +719,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { } event->accept(); } else if (!can->liveStreaming() && event->button() == Qt::RightButton) { - emit zoomReset(); + emit zoomUndo(); event->accept(); } else { QGraphicsView::mouseReleaseEvent(event); @@ -686,20 +734,18 @@ 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(); + is_scrubbing = false; clearTrackPoints(); if (!is_zooming && plot_area.contains(ev->pos())) { @@ -837,8 +883,8 @@ 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 @@ -849,6 +895,13 @@ QXYSeries *ChartView::createSeries(SeriesType type, QColor color) { 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; } @@ -931,7 +984,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); } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index c7470a6f12..7f757f0211 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; @@ -52,7 +52,7 @@ 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); @@ -120,14 +120,17 @@ 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(); @@ -142,6 +145,7 @@ 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; @@ -150,10 +154,12 @@ private: 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 98% rename from tools/cabana/dbc.cc rename to tools/cabana/dbc/dbc.cc index 38002af06c..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) { diff --git a/tools/cabana/dbc.h b/tools/cabana/dbc/dbc.h similarity index 100% rename from tools/cabana/dbc.h rename to tools/cabana/dbc/dbc.h 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 49aac5c18b..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; } - return true; } -void DBCManager::parseExtraInfo(const QString &content) { +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; + } +} + +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,76 +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..0cc51989cd --- /dev/null +++ b/tools/cabana/dbc/dbcmanager.cc @@ -0,0 +1,217 @@ +#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) { + ss |= s; + 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..31183e8199 --- /dev/null +++ b/tools/cabana/dbc/dbcmanager.h @@ -0,0 +1,67 @@ +#pragma once + +#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/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 b7cfd9dc2a..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); @@ -231,7 +244,7 @@ void MainWindow::undoStackCleanChanged(bool clean) { void MainWindow::DBCFileChanged() { UndoStack::instance()->clear(); - setWindowFilePath(QString("%1").arg(dbc()->name())); + updateLoadSaveMenus(); } void MainWindow::openRoute() { @@ -247,7 +260,9 @@ void MainWindow::openRoute() { void MainWindow::newFile() { remindSaveChanges(); - dbc()->open("untitled.dbc", ""); + dbc()->closeAll(); + dbc()->open(SOURCE_ALL, "", ""); + updateLoadSaveMenus(); } void MainWindow::openFile() { @@ -258,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; @@ -271,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(); } } @@ -303,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 { @@ -327,7 +364,7 @@ void MainWindow::loadDBCFromClipboard() { void MainWindow::loadDBCFromFingerprint() { // Don't overwrite already loaded DBC - if (!dbc()->name().isEmpty()) { + if (dbc()->msgCount()) { return; } @@ -336,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]; @@ -349,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) { @@ -434,7 +527,6 @@ void MainWindow::remindSaveChanges() { } } UndoStack::instance()->clear(); - current_file = ""; } void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool success) { 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.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/signaledit.cc b/tools/cabana/signaledit.cc index 4cd06e8a7d..a2455a0317 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -62,14 +62,11 @@ 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; - } - if (value != item->sig_val) { - item->sig_val = value; - emit dataChanged(index(row, 1), index(row, 1), {Qt::DisplayRole}); + item->sig_val = QString::number(get_raw_value((uint8_t *)dat.constData(), dat.size(), *item->sig), 'f', item->sig->precision); + if (!item->sig->unit.isEmpty()) { + item->sig_val += " " + item->sig->unit; } + emit dataChanged(index(row, 1), index(row, 1), {Qt::DisplayRole}); ++row; } } @@ -279,6 +276,7 @@ void SignalModel::handleSignalAdded(MessageId id, const cabana::Signal *sig) { beginInsertRows({}, i, i); insertItem(root.get(), i, sig); endInsertRows(); + updateState(nullptr); } } @@ -320,6 +318,8 @@ QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo } 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 +330,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 +348,62 @@ 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 if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); } - painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); - QRect rc = option.rect.adjusted(0, 0, -70, 0); + + drawSparkline(painter, option, index); + // draw signal value + int right_offset = ((SignalView *)parent())->tree->indexWidget(index)->sizeHint().width() + 2 * h_margin; + QRect rc = option.rect.adjusted(0, 0, -right_offset, 0); auto text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, rc.width()); + painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); painter->drawText(rc, Qt::AlignRight | Qt::AlignVCenter, text); } else { QStyledItemDelegate::paint(painter, option, index); } } +void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + static std::vector points; + // TODO: get seconds from settings. + const uint64_t chart_seconds = 15; // seconds + 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 - chart_seconds * 1e9, 0)}); + if (first != msgs.cend()) { + double min = std::numeric_limits::max(); + double max = std::numeric_limits::lowest(); + const auto sig = ((SignalModel::Item *)index.internalPointer())->sig; + auto last = std::lower_bound(first, msgs.cend(), CanEvent{.mono_time = ts}); + 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; + } + + int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); + int v_margin = std::max(option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin) + 2, 4); + const double xscale = (option.rect.width() - 175.0 * option.widget->devicePixelRatioF() - h_margin * 2) / chart_seconds; + const double yscale = (option.rect.height() - v_margin * 2) / (max - min); + const int left = option.rect.left(); + const int top = option.rect.top() + v_margin; + for (auto &pt : points) { + pt.rx() = left + pt.x() * xscale; + pt.ry() = top + std::abs(pt.y() - max) * yscale; + } + painter->setPen(getColor(sig)); + painter->drawPolyline(points.data(), points.size()); + } +} + 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 || diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index d6bad6d88c..163edcad69 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -82,6 +82,7 @@ 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; + void drawSparkline(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QValidator *name_validator, *double_validator; QFont small_font; const int color_label_width = 18; @@ -99,6 +100,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); @@ -117,9 +119,9 @@ private: } }; - MessageId msg_id; TreeView *tree; QLineEdit *filter_edit; ChartsWidget *charts; QLabel *signal_count_lb; + friend SignalItemDelegate; }; diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 8e19f69d2a..0515f7c7e8 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -6,7 +6,7 @@ #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" @@ -64,11 +64,11 @@ signals: void updated(); void msgsReceived(const QHash *); void received(QHash *); - void sourcesUpdated(const QSet &s); + void sourcesUpdated(const SourceSet &s); public: QHash last_msgs; - QSet sources; + SourceSet sources; protected: virtual void process(QHash *); diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index 140a795d3a..8bc9af8ab2 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -33,11 +33,6 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir) { 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/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 a44ac76059..7fe5b26671 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -8,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) { @@ -20,7 +20,7 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi QHBoxLayout *src_layout = new QHBoxLayout(); src_bus_combo = new QComboBox(this); find_bus_combo = new QComboBox(this); - QSet bus_set; + SourceSet bus_set; for (auto it = can->last_msgs.begin(); it != can->last_msgs.end(); ++it) { bus_set << it.key().source; } diff --git a/tools/cabana/tools/findsimilarbits.h b/tools/cabana/tools/findsimilarbits.h index a40e2cd8df..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 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..7f40c42352 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -12,7 +12,7 @@ #include #include -#include "tools/cabana/dbc.h" +#include "tools/cabana/dbc/dbc.h" class ChangeTracker { public: @@ -36,17 +36,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 diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index e36e62131f..0e9714994e 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 const int MIN_VIDEO_HEIGHT = 100; +const int THUMBNAIL_MARGIN = 3; static const QColor timeline_colors[] = { [(int)TimelineType::None] = QColor(111, 143, 175), @@ -27,16 +24,11 @@ static 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 @@ -57,7 +49,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); @@ -91,12 +84,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); @@ -106,12 +100,6 @@ QWidget *VideoWidget::createCameraWidget() { end_time_label = new QLabel(this); slider_layout->addWidget(end_time_label); l->addLayout(slider_layout); - - cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT); - 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(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); @@ -123,29 +111,7 @@ QWidget *VideoWidget::createCameraWidget() { 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(); @@ -165,7 +131,7 @@ void VideoWidget::updatePlayBtnState() { } // Slider -Slider::Slider(QWidget *parent) : timer(this), QSlider(Qt::Horizontal, parent) { +Slider::Slider(QWidget *parent) : timer(this), thumbnail_label(this), QSlider(Qt::Horizontal, parent) { timer.callOnTimeout([this]() { timeline = can->getTimeline(); update(); @@ -198,29 +164,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())); @@ -266,21 +221,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}), formatTime(seconds), thumb); QSlider::mouseMoveEvent(e); } void Slider::leaveEvent(QEvent *event) { - QToolTip::hideText(); + 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 a5ce8c345f..00b059428d 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -11,10 +11,17 @@ #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 @@ -30,19 +37,18 @@ private: 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: @@ -52,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;