diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 834b2141d0..efddde720d 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -250,6 +250,7 @@ jobs: ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ coverage xml" - name: Print diff + id: print-diff if: always() run: cat selfdrive/test/process_replay/diff.txt - uses: actions/upload-artifact@v2 @@ -259,7 +260,7 @@ jobs: name: process_replay_diff.txt path: selfdrive/test/process_replay/diff.txt - name: Upload reference logs - if: ${{ failure() && github.event_name == 'pull_request' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} + if: ${{ failure() && steps.print-diff.outcome == 'success' && github.event_name == 'pull_request' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} run: | ${{ env.RUN }} "CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" - name: "Upload coverage to Codecov" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index edbca52fed..1779947ff9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: rev: v2.2.1 hooks: - id: codespell - exclude: '^(third_party/)|(body/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)|(selfdrive/ui/translations/.*.ts)' + exclude: '^(third_party/)|(body/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)' args: # if you've got a short variable name that's getting flagged, add it here - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup diff --git a/RELEASES.md b/RELEASES.md index f3d4cfb64f..78d2e3b74b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,4 @@ -Version 0.9.1 (2023-2-23) +Version 0.9.1 (2023-02-23) ======================== * New driving model * 30% improved height estimation resulting in better driving performance for tall cars @@ -7,6 +7,7 @@ Version 0.9.1 (2023-2-23) * Adjust alert volume using ambient noise level * Driver monitoring icon shows driver's head pose * German translation thanks to Vrabetz and CzokNorris! +* Cadillac Escalade 2017 support thanks to rickygilleland! * Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler! * Genesis GV60 2023 support thanks to sunnyhaibin! * Hyundai Tucson 2022-23 support diff --git a/cereal b/cereal index fa3e77b7c8..162a26ca2d 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit fa3e77b7c8eee8752f19427b34adcb1ae5c70ec5 +Subproject commit 162a26ca2d7e5bc9a42bb5ea11e98194f722027b diff --git a/common/params.cc b/common/params.cc index db5e5e700d..5e3361a70f 100644 --- a/common/params.cc +++ b/common/params.cc @@ -155,6 +155,7 @@ std::unordered_map keys = { {"NavSettingTime24h", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT}, {"NavdRender", PERSISTENT}, + {"ObdMultiplexingDisabled", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"OpenpilotEnabledToggle", PERSISTENT}, {"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"PandaSignatures", CLEAR_ON_MANAGER_START}, diff --git a/docs/CARS.md b/docs/CARS.md index e6b6bbee7d..8860299360 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 236 Supported Cars +# 237 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -17,6 +17,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| |Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Cadillac|Escalade 2017[3](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| |Cadillac|Escalade ESV 2016[3](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|| diff --git a/panda b/panda index d15250cb14..0d2ee00921 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit d15250cb1454292c6f1217c79642b9ffd93e7595 +Subproject commit 0d2ee009218009173f26c73a16aefa12dd169de8 diff --git a/poetry.lock b/poetry.lock index 8edfbb868c..91e1b5c826 100644 --- a/poetry.lock +++ b/poetry.lock @@ -564,6 +564,34 @@ python-versions = ">=3.7" [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "click-plugins" +version = "1.1.1" +description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +click = ">=4.0" + +[package.extras] +dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] + +[[package]] +name = "cligj" +version = "0.7.2" +description = "Click params for commmand line interfaces to GeoJSON" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4" + +[package.dependencies] +click = ">=4.0" + +[package.extras] +test = ["pytest-cov"] + [[package]] name = "cloudpickle" version = "2.2.0" @@ -959,6 +987,29 @@ python-versions = ">=3.7" docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] +[[package]] +name = "fiona" +version = "1.9.1" +description = "Fiona reads and writes spatial data files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" +certifi = "*" +click = ">=8.0,<9.0" +click-plugins = ">=1.0" +cligj = ">=0.5" +munch = ">=2.3.2" +setuptools = "*" + +[package.extras] +all = ["Fiona[calc,s3,test]"] +calc = ["shapely"] +s3 = ["boto3 (>=1.3.1)"] +test = ["Fiona[s3]", "pytest (>=7)", "pytest-cov", "pytz"] + [[package]] name = "flake8" version = "4.0.1" @@ -1092,6 +1143,21 @@ python-versions = ">=3.7" packaging = "*" SQLAlchemy = ">=1.4" +[[package]] +name = "geopandas" +version = "0.12.2" +description = "Geographic pandas extensions" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +fiona = ">=1.8" +packaging = "*" +pandas = ">=1.0.0" +pyproj = ">=2.6.1.post1" +shapely = ">=1.7" + [[package]] name = "gevent" version = "22.10.1" @@ -2325,27 +2391,18 @@ python-versions = ">=3.5" [[package]] name = "networkx" -version = "2.3" +version = "2.8.8" description = "Python package for creating and manipulating graphs and networks" category = "dev" optional = false -python-versions = ">=3.5" - -[package.dependencies] -decorator = ">=4.3.0" +python-versions = ">=3.8" [package.extras] -all = ["gdal", "lxml", "matplotlib", "nose", "numpy", "pandas", "pydot", "pygraphviz", "pyyaml", "scipy"] -gdal = ["gdal"] -lxml = ["lxml"] -matplotlib = ["matplotlib"] -nose = ["nose"] -numpy = ["numpy"] -pandas = ["pandas"] -pydot = ["pydot"] -pygraphviz = ["pygraphviz"] -pyyaml = ["pyyaml"] -scipy = ["scipy"] +default = ["matplotlib (>=3.4)", "numpy (>=1.19)", "pandas (>=1.3)", "scipy (>=1.8)"] +developer = ["mypy (>=0.982)", "pre-commit (>=2.20)"] +doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.2)", "pydata-sphinx-theme (>=0.11)", "sphinx (>=5.2)", "sphinx-gallery (>=0.11)", "texext (>=0.6.6)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.9)", "sympy (>=1.10)"] +test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] name = "nodeenv" @@ -2542,6 +2599,31 @@ python-versions = ">=3.6" [package.dependencies] requests = "*" +[[package]] +name = "osmnx" +version = "1.2.2" +description = "Retrieve, model, analyze, and visualize OpenStreetMap street networks and other spatial data" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +geopandas = ">=0.11" +matplotlib = ">=3.5" +networkx = ">=2.8" +numpy = ">=1.22" +pandas = ">=1.4" +pyproj = ">=3.3" +requests = ">=2.28" +Rtree = ">=1.0" +Shapely = ">=1.8,<2.0" + +[package.extras] +entropy = ["scipy"] +nearest-neighbor = ["scikit-learn", "scipy"] +raster = ["gdal", "rasterio"] +web-map = ["folium"] + [[package]] name = "packaging" version = "21.3" @@ -3486,6 +3568,14 @@ python-versions = "*" numpy = ">=1.11.0" scipy = ">=0.17.1" +[[package]] +name = "rtree" +version = "1.0.1" +description = "R-Tree spatial index for Python GIS" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "s2sphere" version = "0.2.5" @@ -3688,6 +3778,19 @@ typing-extensions = "*" test = ["pytest (>=6.2)", "virtualenv (>20)"] toml = ["setuptools (>=42)"] +[[package]] +name = "shapely" +version = "1.8.5.post1" +description = "Geometric objects, predicates, and operations" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +all = ["numpy", "pytest", "pytest-cov"] +test = ["pytest", "pytest-cov"] +vectorized = ["numpy"] + [[package]] name = "shellingham" version = "1.5.0" @@ -4464,7 +4567,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "~3.8" -content-hash = "3c2597d2199a29ef79dd4d5fbc5e2350d86c82a3fe6716d13b93ebc268053eb9" +content-hash = "9e9495c896e6fd0855803aeaf46513c6c22424b86be820759a8baf27d44e73ee" [metadata.files] adal = [ @@ -4917,6 +5020,14 @@ click = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] +click-plugins = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] +cligj = [ + {file = "cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df"}, + {file = "cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27"}, +] cloudpickle = [ {file = "cloudpickle-2.2.0-py3-none-any.whl", hash = "sha256:7428798d5926d8fcbfd092d18d01a2a03daf8237d8fcdc8095d256b8490796f0"}, {file = "cloudpickle-2.2.0.tar.gz", hash = "sha256:3f4219469c55453cfe4737e564b67c2a149109dabf7f242478948b895f61106f"}, @@ -5339,6 +5450,28 @@ filelock = [ {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, ] +fiona = [ + {file = "Fiona-1.9.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:902b67b2d012c5797b5d7d3cb3b46dcf9a342cf90a7f7e53fb12c83738d19926"}, + {file = "Fiona-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2f4535ae2c8446e6b328745a44567478d5a077ed63c888b8c212dddb1e11925"}, + {file = "Fiona-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3d7f3286fb59b93cefefb89014b6fa8413126e180e15c576db859ba936cf334"}, + {file = "Fiona-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:692fe64c0f3a39742d6f7a8e420a8387f6aec3b6818b727d2dfc98a0c40e992d"}, + {file = "Fiona-1.9.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:a017a39650df0cc541c57cf7de450bb4cee6fd9760eb716323b594c1074634a2"}, + {file = "Fiona-1.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3b52d49bc379fdcfd1250b38e7e00ab24ee14eb765376c793bbe251ffd09d6a"}, + {file = "Fiona-1.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbb0ae851cd4417104c469335a01f938251a8639317f93d422c5c808150bd27"}, + {file = "Fiona-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:4a6d8fcbbdafa8af8ac1904628b0267382ed9f9921933d061d7bfc5d3f3daf99"}, + {file = "Fiona-1.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:74f056a84dc52a0b21a2cf024601a69105596f06f28d40b45049948be17b4df2"}, + {file = "Fiona-1.9.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5b9f3f6c782bb4ae2c924eefd373cabdeaaa99f86477b9c7c71eb20c052ee7c5"}, + {file = "Fiona-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:5d6183189a7e05e2498d38a1df3ab07e1353fa48e977cbc3a31203927bd06bca"}, + {file = "Fiona-1.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:5f2c14beec98a330aee1fd81fa0447a6aa1d5e0a75d000c0052dbe1f23dd6cfd"}, + {file = "Fiona-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:585a47ec21f2d198abc112158eaf12a6587a272beb7f001162d8c3b262676666"}, + {file = "Fiona-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4368b4054f21518a19dea54ce9ac445c40418c6331c0c99d1531c3ddff05da"}, + {file = "Fiona-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:839ed0db23198bb754f0d655d4eeaf5f9c141bef734557e77e95e4dc83e42e7f"}, + {file = "Fiona-1.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:decca3956032ae2d2c19d2f7fa8a4553c43a6e66eb5abe9a05f6ddadcb1bfe5c"}, + {file = "Fiona-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb827fa0030d03d8080723137c74b865ec18dbade87c02ed60215491a315c6be"}, + {file = "Fiona-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7e0f36484757baa8cd1c0602941e029ff992282776f9afae4c5b90f501ff005"}, + {file = "Fiona-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:f98933b552adf0506799097934a6950de412288e7a50466144d04874d6f63fbc"}, + {file = "Fiona-1.9.1.tar.gz", hash = "sha256:3a3725e94840a387fef48726d60db6a6791563f366939d22378a4661f8941be7"}, +] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, @@ -5463,6 +5596,10 @@ geoalchemy2 = [ {file = "GeoAlchemy2-0.12.5-py2.py3-none-any.whl", hash = "sha256:3a59eb651df95b3dfee8e1d82f4d18c80b75f712860a0a3080defc6b0435070d"}, {file = "GeoAlchemy2-0.12.5.tar.gz", hash = "sha256:31c2502dce317b57b335e4eb87562d501fa39e46c728be514d9b86091e08dd67"}, ] +geopandas = [ + {file = "geopandas-0.12.2-py3-none-any.whl", hash = "sha256:0a470e4bf6f5367e6fd83ab6b40405e0b805c8174665bbcb7c4077ed90202912"}, + {file = "geopandas-0.12.2.tar.gz", hash = "sha256:0acdacddefa176525e4da6d9aeeece225da26055c4becdc6e97cf40fa97c27f4"}, +] gevent = [ {file = "gevent-22.10.1-cp27-cp27m-win32.whl", hash = "sha256:702a51b8f21bad1976b0893f90ade466e8c27039b846b611ad2beb8c6e6ac701"}, {file = "gevent-22.10.1-cp27-cp27m-win_amd64.whl", hash = "sha256:af7baec79a5f8ad1cc132d3b14edd12661c628d8094e501b089b1fe2d3df7f6e"}, @@ -6393,7 +6530,8 @@ nest-asyncio = [ {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, ] networkx = [ - {file = "networkx-2.3.zip", hash = "sha256:8311ddef63cf5c5c5e7c1d0212dd141d9a1fe3f474915281b73597ed5f1d4e3d"}, + {file = "networkx-2.8.8-py3-none-any.whl", hash = "sha256:e435dfa75b1d7195c7b8378c3859f0445cd88c6b0375c181ed66823a9ceb7524"}, + {file = "networkx-2.8.8.tar.gz", hash = "sha256:230d388117af870fce5647a3c52401fcf753e94720e6ea6b4197a5355648885e"}, ] nodeenv = [ {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, @@ -6521,6 +6659,10 @@ osmium = [ {file = "osmium-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e45b7c54ac756e9cb40e2ba68691df635804eb6aa2023088af66936a9c8e3782"}, {file = "osmium-3.4.1.tar.gz", hash = "sha256:575dad72ab169cf585b9aeefb4f5f99ac250bf7da1986992afcbf169dc70c381"}, ] +osmnx = [ + {file = "osmnx-1.2.2-py2.py3-none-any.whl", hash = "sha256:94f2a3929e857d8c0da39ae552c6da3b1a3f4bcfea6de108696bda5ee3a7689d"}, + {file = "osmnx-1.2.2.tar.gz", hash = "sha256:30924452ca02758ece3301f9fcfb1b80edf31e2be7abe7fa7e0fefddb5050408"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -7362,6 +7504,53 @@ requests-toolbelt = [ reverse-geocoder = [ {file = "reverse_geocoder-1.5.1.tar.gz", hash = "sha256:2a2e781b5f69376d922b78fe8978f1350c84fce0ddb07e02c834ecf98b57c75c"}, ] +rtree = [ + {file = "Rtree-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9855b8f11cdad99c56eb361b7b632a4fbd3d8cbe3f2081426b445f0cfb7fdca9"}, + {file = "Rtree-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:18ce7e4d04b85c48f2d364835620b3b20e38e199639746e7b12f07a2303e18ff"}, + {file = "Rtree-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:784efa6b7be9e99b33613ae8495931032689441eabb6120c9b3eb91188c33794"}, + {file = "Rtree-1.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:157207191aebdacbbdbb369e698cfbfebce53bc97114e96c8af5bed3126475f1"}, + {file = "Rtree-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5fb3671a8d440c24b1dd29ec621d4345ced7185e26f02abe98e85a6629fcb50"}, + {file = "Rtree-1.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:11d16f51cf9205cd6995af36e24efe8f184270f667fb49bb69b09fc46b97e7d4"}, + {file = "Rtree-1.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6db6a0a93e41594ffc14b053f386dd414ab5a82535bbd9aedafa6ac8dc0650d8"}, + {file = "Rtree-1.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6e29e5eb3083ad12ac5c1ce6e37465ea3428d894d3466cc9c9e2ee4bf768e53"}, + {file = "Rtree-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:656b148589c0b5bab4a7db4d033634329f42a5feaac10ca40aceeca109d83c1f"}, + {file = "Rtree-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b2c15f9373ba314c83a8df5cb6d99b4e3af23c376c6b1317add995432dd0970"}, + {file = "Rtree-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93c5e0bf31e76b4f92a6eec3d2891e938408774c75a8ed6ac3d2c8db04a2be33"}, + {file = "Rtree-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6792de0e3c2fd3ad7e069445027603bec7a47000432f49c80246886311f4f152"}, + {file = "Rtree-1.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:004e131b570dc360a49e7f3b60e7bc6517943a54df056587964d1cb903889e7e"}, + {file = "Rtree-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:becd711fe97c2e09b1b7969e83080a3c8012bce2d30f6db879aade255fcba5c1"}, + {file = "Rtree-1.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:015df09e1bc55ddf7c88799bf1515d058cd0ee78eacf4cd443a32876d3b3a863"}, + {file = "Rtree-1.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c2973b76f61669a85e160b4ad09879c4089fc0e3f20fd99adf161ca298fe8374"}, + {file = "Rtree-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e4335e131a58952635560a003458011d97f9ea6f3c010dc24906050b42ee2c03"}, + {file = "Rtree-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:e7ca5d743f6a1dc62653dfac8ee7ce2e1ba91be7cf97916a7f60b7cbe48fb48d"}, + {file = "Rtree-1.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2ee7165e9872a026ccb868c021711eba39cedf7d1820763c9de52d5324691a92"}, + {file = "Rtree-1.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8de99f28af0f1783eefb80918959903b4b18112f6a12b48f296ecb162804e69d"}, + {file = "Rtree-1.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a94e2f4bf74bd202ea8b67ea3d7c71e763ad41f79be1d6b72aa2c8d5a8e92c4"}, + {file = "Rtree-1.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5120da3a1b96f3a7a17dd6af0afdd4e6f3cc9baa87e9ee0a272882f01f980bb"}, + {file = "Rtree-1.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7e3d5f0e7b28250afbb290ab88b49aa0f121c9714d0da2080581783690347507"}, + {file = "Rtree-1.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:296203e933b6ec0dd07f6a7456c4f1492def95b6993f20cc61c92b0fee0aecc5"}, + {file = "Rtree-1.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:77908cd7acdd519a731979ebf5baff8afd102109c2f52864c1e6ee75d3ea2d87"}, + {file = "Rtree-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1a213e5d385278ca7668bc5b27083f8d6e39996a9bd59b6528f3a30009dae4ed"}, + {file = "Rtree-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cfa8cffec5cb9fed494c4bb335ebdb69b3c26178b0b685f67f79296c6b3d800c"}, + {file = "Rtree-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b31fd22d214160859d038da7cb2aaa27acb71efc24a7bcc75c84b5e502721549"}, + {file = "Rtree-1.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d68a81ad419d5c2ea5fecc677e6c178666c057e2c7b24100a6c48392196f1e9"}, + {file = "Rtree-1.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62f38020af47b765adc6b0bc7c4e810c6c3d1eab44ba339b592ff25a4c0dc0a7"}, + {file = "Rtree-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50b658a6707f215a0056d52e9f83a97148c0af62dea07cf29b3789a2c429e78a"}, + {file = "Rtree-1.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3573cbb0de872f54d0a0c29596a84e8ac3939c47ca3bece4a82e92775730a0d0"}, + {file = "Rtree-1.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5abe5a19d943a88bea14901970e4c53e4579fc2662404cdea6163bf4c04d49a"}, + {file = "Rtree-1.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e894112cef4de6c518bdea0b43eada65f12888c3645cc437c3a677aa023039f"}, + {file = "Rtree-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:582854252b8fd5c8472478af060635434931fb55edd269bac128cbf2eef43620"}, + {file = "Rtree-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b54057e8a8ad92c1d8e9eaa5cf32aad70dde454abbf9b638e9d6024520a52c02"}, + {file = "Rtree-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:698de8ce6c62e159d93b35bacf64bcf3619077b5367bc88cd2cff5e0bc36169b"}, + {file = "Rtree-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:273ee61783de3a1664e5f868feebf5eea4629447137751bfa4087b0f82093082"}, + {file = "Rtree-1.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16900ee02cf5c198a42b03635268a80f606aa102f3f7618b89f75023d406da1c"}, + {file = "Rtree-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ce4a6fdb63254a4c1efebe7a4f7a59b1c333c703bde4ae715d9ad88c833e10b"}, + {file = "Rtree-1.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5b20f69e040a05503b22297af223f336fe7047909b57e4b207b98292f33a229f"}, + {file = "Rtree-1.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:57128293dd625cb1f07726f32208097953e8854d70ab1fc55d6858733618b9ed"}, + {file = "Rtree-1.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e898d7409ab645c25e06d4e058f99271182601d70b2887aba3351bf08e09a0c6"}, + {file = "Rtree-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ad9912faeddb1ddcec5e26b33089166d58a107af6862d8b7f1bb2b7c0002ab39"}, + {file = "Rtree-1.0.1.tar.gz", hash = "sha256:222121699c303a64065d849bf7038b1ecabc37b65c7fa340bedb38ef0e805429"}, +] s2sphere = [ {file = "s2sphere-0.2.5-py2.py3-none-any.whl", hash = "sha256:d2340c9cf458ddc9a89afd1d8048a4195ce6fa6b0095ab900d4be5271e537401"}, {file = "s2sphere-0.2.5.tar.gz", hash = "sha256:c2478c1ff7c601a59a7151a57b605435897514578fa6bdb8730721c182adbbaf"}, @@ -7539,6 +7728,50 @@ setuptools-scm = [ {file = "setuptools_scm-7.0.5-py3-none-any.whl", hash = "sha256:7930f720905e03ccd1e1d821db521bff7ec2ac9cf0ceb6552dd73d24a45d3b02"}, {file = "setuptools_scm-7.0.5.tar.gz", hash = "sha256:031e13af771d6f892b941adb6ea04545bbf91ebc5ce68c78aaf3fff6e1fb4844"}, ] +shapely = [ + {file = "Shapely-1.8.5.post1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d048f93e42ba578b82758c15d8ae037d08e69d91d9872bca5a1895b118f4e2b0"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99ab0ddc05e44acabdbe657c599fdb9b2d82e86c5493bdae216c0c4018a82dee"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a2f0da0109e81e0c101a2b4cd8412f73f5f299e7b5b2deaf64cd2a100ac118"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6fe855e7d45685926b6ba00aaeb5eba5862611f7465775dacd527e081a8ced6d"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec14ceca36f67cb48b34d02d7f65a9acae15cd72b48e303531893ba4a960f3ea"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a2b2a65fa7f97115c1cd989fe9d6f39281ca2a8a014f1d4904c1a6e34d7f25"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-win32.whl", hash = "sha256:21776184516a16bf82a0c3d6d6a312b3cd15a4cabafc61ee01cf2714a82e8396"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-win_amd64.whl", hash = "sha256:a354199219c8d836f280b88f2c5102c81bb044ccea45bd361dc38a79f3873714"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:783bad5f48e2708a0e2f695a34ed382e4162c795cb2f0368b39528ac1d6db7ed"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a23ef3882d6aa203dd3623a3d55d698f59bfbd9f8a3bfed52c2da05a7f0f8640"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab38f7b5196ace05725e407cb8cab9ff66edb8e6f7bb36a398e8f73f52a7aaa2"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d086591f744be483b34628b391d741e46f2645fe37594319e0a673cc2c26bcf"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4728666fff8cccc65a07448cae72c75a8773fea061c3f4f139c44adc429b18c3"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-win32.whl", hash = "sha256:84010db15eb364a52b74ea8804ef92a6a930dfc1981d17a369444b6ddec66efd"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-win_amd64.whl", hash = "sha256:48dcfffb9e225c0481120f4bdf622131c8c95f342b00b158cdbe220edbbe20b6"}, + {file = "Shapely-1.8.5.post1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2fd15397638df291c427a53d641d3e6fd60458128029c8c4f487190473a69a91"}, + {file = "Shapely-1.8.5.post1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a74631e511153366c6dbe3229fa93f877e3c87ea8369cd00f1d38c76b0ed9ace"}, + {file = "Shapely-1.8.5.post1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:66bdac74fbd1d3458fa787191a90fa0ae610f09e2a5ec398c36f968cc0ed743f"}, + {file = "Shapely-1.8.5.post1-cp36-cp36m-win32.whl", hash = "sha256:6d388c0c1bd878ed1af4583695690aa52234b02ed35f93a1c8486ff52a555838"}, + {file = "Shapely-1.8.5.post1-cp36-cp36m-win_amd64.whl", hash = "sha256:be9423d5a3577ac2e92c7e758bd8a2b205f5e51a012177a590bc46fc51eb4834"}, + {file = "Shapely-1.8.5.post1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5d7f85c2d35d39ff53c9216bc76b7641c52326f7e09aaad1789a3611a0f812f2"}, + {file = "Shapely-1.8.5.post1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:adcf8a11b98af9375e32bff91de184f33a68dc48b9cb9becad4f132fa25cfa3c"}, + {file = "Shapely-1.8.5.post1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:753ed0e21ab108bd4282405b9b659f2e985e8502b1a72b978eaa51d3496dee19"}, + {file = "Shapely-1.8.5.post1-cp37-cp37m-win32.whl", hash = "sha256:65b21243d8f6bcd421210daf1fabb9de84de2c04353c5b026173b88d17c1a581"}, + {file = "Shapely-1.8.5.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:370b574c78dc5af3a198a6da5d9b3d7c04654bd2ef7e80e80a3a0992dfb2d9cd"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:532a55ee2a6c52d23d6f7d1567c8f0473635f3b270262c44e1b0c88096827e22"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3480657460e939f45a7d359ef0e172a081f249312557fe9aa78c4fd3a362d993"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b65f5d530ba91e49ffc7c589255e878d2506a8b96ffce69d3b7c4500a9a9eaf8"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:147066da0be41b147a61f8eb805dea3b13709dbc873a431ccd7306e24d712bc0"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c2822111ddc5bcfb116e6c663e403579d0fe3f147d2a97426011a191c43a7458"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b47bb6f9369e8bf3e6dbd33e6a25a47ee02b2874792a529fe04a49bf8bc0df6"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-win32.whl", hash = "sha256:2e0a8c2e55f1be1312b51c92b06462ea89e6bb703fab4b114e7a846d941cfc40"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-win_amd64.whl", hash = "sha256:0d885cb0cf670c1c834df3f371de8726efdf711f18e2a75da5cfa82843a7ab65"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0b4ee3132ee90f07d63db3aea316c4c065ed7a26231458dda0874414a09d6ba3"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:02dd5d7dc6e46515d88874134dc8fcdc65826bca93c3eecee59d1910c42c1b17"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c6a9a4a31cd6e86d0fbe8473ceed83d4fe760b19d949fb557ef668defafea0f6"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:38f0fbbcb8ca20c16451c966c1f527cc43968e121c8a048af19ed3e339a921cd"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78fb9d929b8ee15cfd424b6c10879ce1907f24e05fb83310fc47d2cd27088e40"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89164e7a9776a19e29f01369a98529321994e2e4d852b92b7e01d4d9804c55bf"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-win32.whl", hash = "sha256:8e59817b0fe63d34baedaabba8c393c0090f061917d18fc0bcc2f621937a8f73"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-win_amd64.whl", hash = "sha256:e9c30b311de2513555ab02464ebb76115d242842b29c412f5a9aa0cac57be9f6"}, + {file = "Shapely-1.8.5.post1.tar.gz", hash = "sha256:ef3be705c3eac282a28058e6c6e5503419b250f482320df2172abcbea642c831"}, +] shellingham = [ {file = "shellingham-1.5.0-py2.py3-none-any.whl", hash = "sha256:a8f02ba61b69baaa13facdba62908ca8690a94b8119b69f5ec5873ea85f7391b"}, {file = "shellingham-1.5.0.tar.gz", hash = "sha256:72fb7f5c63103ca2cb91b23dee0c71fe8ad6fbfd46418ef17dbe40db51592dad"}, diff --git a/pyproject.toml b/pyproject.toml index ed3a821e29..6b4b0bb5fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -145,7 +145,7 @@ jupyterlab = "^3.4.4" jupyterlab-vim = "^0.15.1" Markdown = "^3.4.1" msgpack-python = "^0.5.6" -networkx = "~2.3" +networkx = "~2.8" nvidia-ml-py3 = "^7.352.0" onnx2torch = "^1.5.4" onnxoptimizer = "^0.3.1" @@ -172,6 +172,7 @@ triton = "^1.1.1" Werkzeug = "^2.1.2" zerorpc = { git = "https://github.com/commaai/zerorpc-python.git", branch = "master" } omegaconf = "^2.3.0" +osmnx = "==1.2.2" [build-system] diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 0473d3488c..5d885c2c79 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -137,6 +137,8 @@ bool safety_setter_thread(std::vector pandas) { panda->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); } + p.putBool("ObdMultiplexingDisabled", true); + std::string params; LOGW("waiting for params to set safety model"); while (true) { diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 4ccce979d3..370772c902 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -8,7 +8,7 @@ from system.version import is_comma_remote, is_tested_branch from selfdrive.car.interfaces import get_interface_attr from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN -from selfdrive.car.fw_versions import get_fw_versions_ordered, match_fw_to_car, get_present_ecus +from selfdrive.car.fw_versions import disable_obd_multiplexing, get_fw_versions_ordered, match_fw_to_car, get_present_ecus from system.swaglog import cloudlog import cereal.messaging as messaging from selfdrive.car import gen_empty_fingerprint @@ -116,7 +116,7 @@ def fingerprint(logcan, sendcan, num_pandas): params = Params() params.put("CarVin", vin) - params.put_bool("FirmwareObdQueryDone", True) + disable_obd_multiplexing(params) finger = gen_empty_fingerprint() candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1 diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 4b22c478a7..961684f398 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -2,7 +2,7 @@ from cereal import car from panda import Panda from selfdrive.car import STD_CARGO_KG, get_safety_config -from selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, CHRYSLER_OLD_TUNING_BLACKLIST, ChryslerFlags +from selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags from selfdrive.car.interfaces import CarInterfaceBase @@ -23,9 +23,6 @@ class CarInterface(CarInterfaceBase): elif candidate in RAM_DT: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_RAM_DT - if candidate in CHRYSLER_OLD_TUNING_BLACKLIST: - ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_LOWER_RATE - ret.minSteerSpeed = 3.8 # m/s CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) if candidate not in RAM_CARS: @@ -41,11 +38,10 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 3.089 ret.steerRatio = 16.2 # Pacifica Hybrid 2017 - if candidate in CHRYSLER_OLD_TUNING_BLACKLIST: - ret.lateralTuning.init('pid') - ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] - ret.lateralTuning.pid.kf = 0.00006 + ret.lateralTuning.init('pid') + ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] + ret.lateralTuning.pid.kf = 0.00006 # Jeep elif candidate in (CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019): @@ -54,11 +50,10 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 16.7 ret.steerActuatorDelay = 0.2 - if candidate in CHRYSLER_OLD_TUNING_BLACKLIST: - ret.lateralTuning.init('pid') - ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] - ret.lateralTuning.pid.kf = 0.00006 + ret.lateralTuning.init('pid') + ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] + ret.lateralTuning.pid.kf = 0.00006 # Ram elif candidate == CAR.RAM_1500: diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index c6b8bcadb0..16ebb4fa11 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -46,12 +46,8 @@ class CarControllerParams: self.STEER_DELTA_DOWN = 6 self.STEER_MAX = 261 # EPS allows more, up to 350? else: - if CP.carFingerprint in CHRYSLER_OLD_TUNING_BLACKLIST: - self.STEER_DELTA_UP = 3 - self.STEER_DELTA_DOWN = 3 - else: - self.STEER_DELTA_UP = 6 - self.STEER_DELTA_DOWN = 6 + self.STEER_DELTA_UP = 3 + self.STEER_DELTA_DOWN = 3 self.STEER_MAX = 261 # higher than this faults the EPS STEER_THRESHOLD = 120 @@ -60,9 +56,6 @@ RAM_DT = {CAR.RAM_1500, } RAM_HD = {CAR.RAM_HD, } RAM_CARS = RAM_DT | RAM_HD -# the increased steer rate hasn't been verified on these cars. -# remove from this list once it's been tested and confirmed to not fault -CHRYSLER_OLD_TUNING_BLACKLIST = {CAR.PACIFICA_2017_HYBRID, CAR.PACIFICA_2018_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE} @dataclass class ChryslerCarInfo(CarInfo): diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index 2b161b2f8d..c5d81eec5c 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -57,6 +57,10 @@ class Request: whitelist_ecus: List[int] = field(default_factory=list) rx_offset: int = 0x8 bus: int = 1 + # FW responses from these queries will not be used for fingerprinting + logging: bool = False + # These requests are done once OBD multiplexing is disabled, after all others + non_obd: bool = False @dataclass diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index acf74da47b..8f4339ddf7 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -5,6 +5,7 @@ from tqdm import tqdm import panda.python.uds as uds from cereal import car +from common.params import Params from selfdrive.car.ecu_addrs import get_ecu_addrs from selfdrive.car.interfaces import get_interface_attr from selfdrive.car.fingerprints import FW_VERSIONS @@ -29,10 +30,9 @@ def chunks(l, n=128): def build_fw_dict(fw_versions, filter_brand=None): fw_versions_dict = defaultdict(set) for fw in fw_versions: - if filter_brand is None or fw.brand == filter_brand: - addr = fw.address + if (filter_brand is None or fw.brand == filter_brand) and not fw.logging: sub_addr = fw.subAddress if fw.subAddress != 0 else None - fw_versions_dict[(addr, sub_addr)].add(fw.fwVersion) + fw_versions_dict[(fw.address, sub_addr)].add(fw.fwVersion) return dict(fw_versions_dict) @@ -94,7 +94,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, config, log=True, exclude=None): return set() -def match_fw_to_car_exact(fw_versions_dict, config): +def match_fw_to_car_exact(fw_versions_dict, config) -> Set[str]: """Do an exact FW match. Returns all cars that match the given FW versions for a list of "essential" ECUs. If an ECU is not considered essential the FW version can be missing to get a fingerprint, but if it's present it @@ -202,11 +202,21 @@ def get_brand_ecu_matches(ecu_rx_addrs): return brand_matches +def disable_obd_multiplexing(params): + if not params.get_bool("ObdMultiplexingDisabled"): + params.put_bool("FirmwareObdQueryDone", True) + + cloudlog.warning("Waiting for OBD multiplexing to be disabled") + params.get_bool("ObdMultiplexingDisabled", block=True) + cloudlog.warning("OBD multiplexing disabled") + + def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False): """Queries for FW versions ordering brands by likelihood, breaks when exact match is found""" all_car_fw = [] brand_matches = get_brand_ecu_matches(ecu_rx_addrs) + matched_brand: Optional[str] = None for brand in sorted(brand_matches, key=lambda b: len(brand_matches[b]), reverse=True): car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, debug=debug, progress=progress) @@ -214,12 +224,20 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pand # Try to match using FW returned from this brand only matches = match_fw_to_car_exact(build_fw_dict(car_fw)) if len(matches) == 1: + matched_brand = brand break + disable_obd_multiplexing(Params()) + + # Do non-OBD queries for matched brand, or all if no match is found + for brand in FW_QUERY_CONFIGS.keys(): + if brand == matched_brand or matched_brand is None: + all_car_fw.extend(get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, obd_multiplexed=False, debug=debug, progress=progress)) + return all_car_fw -def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, debug=False, progress=False): +def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, obd_multiplexed=True, debug=False, progress=False): versions = VERSIONS.copy() # Each brand can define extra ECUs to query for data collection @@ -236,15 +254,19 @@ 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 c in brand_versions.values(): - for ecu_type, addr, sub_addr in c.keys(): + for candidate, ecu in brand_versions.items(): + 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) @@ -263,6 +285,9 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, # Skip query if no panda available if r.bus > num_pandas * 4 - 1: continue + # Or if request is not designated for current multiplexing mode + elif r.non_obd == obd_multiplexed: + continue try: addrs = [(a, s) for (b, a, s) in addr_chunk if b in (brand, 'any') and @@ -273,13 +298,15 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, for (tx_addr, sub_addr), version in query.get_data(timeout).items(): f = car.CarParams.CarFw.new_message() - f.ecu = ecu_types.get((brand, tx_addr, sub_addr), Ecu.unknown) + ecu_key = (brand, tx_addr, sub_addr) + f.ecu = ecu_types.get(ecu_key, 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 if sub_addr is not None: f.subAddress = sub_addr diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 8bfc067b48..c28274011e 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -3,10 +3,12 @@ from cereal import car from math import fabs from panda import Panda +from common.numpy_fast import interp from common.conversions import Conversions as CV from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR -from selfdrive.car.interfaces import CarInterfaceBase +from selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD +from selfdrive.controls.lib.drive_helpers import apply_center_deadzone ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -43,6 +45,43 @@ class CarInterface(CarInterfaceBase): else: return CarInterfaceBase.get_steer_feedforward_default + @staticmethod + def torque_from_lateral_accel_bolt(lateral_accel_value, torque_params, lateral_accel_error, lateral_accel_deadzone, vego, friction_compensation): + friction_interp = interp( + apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone), + [-FRICTION_THRESHOLD, FRICTION_THRESHOLD], + [-torque_params.friction, torque_params.friction] + ) + friction = friction_interp if friction_compensation else 0.0 + steer_torque = lateral_accel_value / torque_params.latAccelFactor + + # TODO: + # 1. Learn the correction factors from data + # 2. Generalize the logic to other GM torque control platforms + steer_break_pts = [-1.0, -0.9, -0.75, -0.5, 0.0, 0.5, 0.75, 0.9, 1.0] + steer_lataccel_factors = [1.5, 1.15, 1.02, 1.0, 1.0, 1.0, 1.02, 1.15, 1.5] + steer_correction_factor = interp( + steer_torque, + steer_break_pts, + steer_lataccel_factors + ) + + vego_break_pts = [0.0, 10.0, 15.0, 20.0, 100.0] + vego_lataccel_factors = [1.5, 1.5, 1.25, 1.0, 1.0] + vego_correction_factor = interp( + vego, + vego_break_pts, + vego_lataccel_factors, + ) + + return (steer_torque + friction) / (steer_correction_factor * vego_correction_factor) + + def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: + if self.CP.carFingerprint == CAR.BOLT_EUV: + return self.torque_from_lateral_accel_bolt + else: + return self.torque_from_lateral_accel_linear + @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "gm" @@ -159,6 +198,14 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 15.3 ret.centerToFront = ret.wheelbase * 0.5 + elif candidate == CAR.ESCALADE: + ret.minEnableSpeed = -1. # engage speed is decided by pcm + ret.mass = 5653. * CV.LB_TO_KG + STD_CARGO_KG # (5552+5815)/2 + ret.wheelbase = 2.95 # 116 inches in meters + ret.steerRatio = 17.3 + ret.centerToFront = ret.wheelbase * 0.5 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + elif candidate == CAR.ESCALADE_ESV: ret.minEnableSpeed = -1. # engage speed is decided by pcm ret.mass = 2739. + STD_CARGO_KG @@ -174,9 +221,9 @@ class CarInterface(CarInterfaceBase): ret.mass = 1669. + STD_CARGO_KG ret.wheelbase = 2.63779 ret.steerRatio = 16.8 - ret.centerToFront = 2.15 # measured + ret.centerToFront = ret.wheelbase * 0.4 tire_stiffness_factor = 1.0 - ret.steerActuatorDelay = 0.2 + ret.steerActuatorDelay = 0.12 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.SILVERADO: diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 7628d8420b..207af6bb05 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -14,8 +14,8 @@ class CarControllerParams: STEER_STEP = 3 # Active control frames per command (~33hz) INACTIVE_STEER_STEP = 10 # Inactive control frames per command (10hz) STEER_DELTA_UP = 10 # Delta rates require review due to observed EPS weakness - STEER_DELTA_DOWN = 25 - STEER_DRIVER_ALLOWANCE = 50 + STEER_DELTA_DOWN = 15 + STEER_DRIVER_ALLOWANCE = 65 STEER_DRIVER_MULTIPLIER = 4 STEER_DRIVER_FACTOR = 100 NEAR_STOP_BRAKE_PHASE = 0.5 # m/s @@ -67,6 +67,7 @@ class CAR: MALIBU = "CHEVROLET MALIBU PREMIER 2017" ACADIA = "GMC ACADIA DENALI 2018" BUICK_REGAL = "BUICK REGAL ESSENCE 2018" + ESCALADE = "CADILLAC ESCALADE 2017" ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016" BOLT_EUV = "CHEVROLET BOLT EUV 2022" SILVERADO = "CHEVROLET SILVERADO 1500 2020" @@ -99,6 +100,7 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"), CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"), + CAR.ESCALADE: GMCarInfo("Cadillac Escalade 2017", "Driver Assist Package"), CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"), CAR.BOLT_EUV: [ GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210"), @@ -174,6 +176,10 @@ FINGERPRINTS = { { 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 313: 8, 320: 3, 322: 7, 328: 1, 338: 6, 340: 6, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 }], + CAR.ESCALADE: [ + { + 170: 8, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 4, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 460: 5, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 + }], CAR.ESCALADE_ESV: [ { 309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8 diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 66c0ce4275..d3cf9fa891 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -21,6 +21,8 @@ class CarInterface(CarInterfaceBase): def get_pid_accel_limits(CP, current_speed, cruise_speed): if CP.carFingerprint in HONDA_BOSCH: return CarControllerParams.BOSCH_ACCEL_MIN, CarControllerParams.BOSCH_ACCEL_MAX + elif CP.enableGasInterceptor: + return CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX else: # NIDECs don't allow acceleration near cruise_speed, # so limit limits of pid to prevent windup diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 3d1c006aaa..c085c3fe80 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -171,12 +171,22 @@ FW_QUERY_CONFIG = FwQueryConfig( [HONDA_VERSION_REQUEST], [HONDA_VERSION_RESPONSE], bus=1, + logging=True, ), - # Query Nidec PT bus from camera for data collection + # Nidec PT bus Request( [StdQueries.UDS_VERSION_REQUEST], [StdQueries.UDS_VERSION_RESPONSE], bus=0, + logging=True, + ), + # Bosch PT bus + Request( + [StdQueries.UDS_VERSION_REQUEST], + [StdQueries.UDS_VERSION_RESPONSE], + bus=1, + logging=True, + non_obd=True, ), ], extra_ecus=[ diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 7c31a48ba5..1e6f78af20 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -169,7 +169,8 @@ class CarController: if (self.frame - self.last_button_frame) * DT_CTRL > 0.1: # send 25 messages at a time to increases the likelihood of resume being accepted can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP.carFingerprint)] * 25) - self.last_button_frame = self.frame + if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15: + self.last_button_frame = self.frame if self.frame % 2 == 0 and self.CP.openpilotLongitudinalControl: # TODO: unclear if this is needed @@ -178,11 +179,7 @@ class CarController: hud_control.leadVisible, set_speed_in_units, stopping, CC.cruiseControl.override)) # 20 Hz LFA MFA message - if self.frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, - CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.KONA_EV_2022, - CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022, - CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022, - CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020): + if self.frame % 5 == 0 and self.CP.flags & HyundaiFlags.SEND_LFA.value: can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled)) # 5 Hz ACC options diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 0bab188790..22934c05b2 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -135,8 +135,8 @@ class CarState(CarStateBase): ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear)) if not self.CP.openpilotLongitudinalControl: - aeb_src = "FCA11" if self.CP.carFingerprint in FEATURES["use_fca"] else "SCC12" - aeb_sig = "FCA_CmdAct" if self.CP.carFingerprint in FEATURES["use_fca"] else "AEB_CmdAct" + aeb_src = "FCA11" if self.CP.flags & HyundaiFlags.USE_FCA.value else "SCC12" + aeb_sig = "FCA_CmdAct" if self.CP.flags & HyundaiFlags.USE_FCA.value else "AEB_CmdAct" aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0 aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src][aeb_sig] != 0 ret.stockFcw = aeb_warning and not aeb_braking @@ -317,7 +317,7 @@ class CarState(CarStateBase): ("SCC12", 50), ] - if CP.carFingerprint in FEATURES["use_fca"]: + if CP.flags & HyundaiFlags.USE_FCA.value: signals += [ ("FCA_CmdAct", "FCA11"), ("CF_VSM_Warn", "FCA11"), @@ -408,7 +408,7 @@ class CarState(CarStateBase): ("SCC12", 50), ] - if CP.carFingerprint in FEATURES["use_fca"]: + if CP.flags & HyundaiFlags.USE_FCA.value: signals += [ ("FCA_CmdAct", "FCA11"), ("CF_VSM_Warn", "FCA11"), diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index b746680930..d2cc5b4ec0 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -43,6 +43,14 @@ class CarInterface(CarInterfaceBase): ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value if candidate not in CANFD_RADAR_SCC_CAR: ret.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value + else: + # Send LFA message on cars with HDA + if 0x485 in fingerprint[2]: + ret.flags |= HyundaiFlags.SEND_LFA.value + + # These cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 + if 0x38d in fingerprint[0] or 0x38d in fingerprint[2]: + ret.flags |= HyundaiFlags.USE_FCA.value ret.steerActuatorDelay = 0.1 # Default delay ret.steerLimitTimer = 0.4 diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index ee1fbb9a4c..a10b3e8ad9 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -58,10 +58,10 @@ class HyundaiFlags(IntFlag): CANFD_CAMERA_SCC = 8 ALT_LIMITS = 16 - ENABLE_BLINKERS = 32 - CANFD_ALT_GEARS_2 = 64 + SEND_LFA = 128 + USE_FCA = 256 class CAR: @@ -474,13 +474,14 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x816U3J9051\000\000\xf1\0006U3H1_C2\000\0006U3J9051\000\000PAE0G16NL0\x82zT\xd2', b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00', b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', ], }, CAR.IONIQ_EV_2020: { @@ -520,6 +521,7 @@ FW_VERSIONS = { CAR.IONIQ_HEV_2022: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', + b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', @@ -532,6 +534,7 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x96\xda\xd4\xee', ], }, CAR.SONATA: { @@ -717,12 +720,14 @@ FW_VERSIONS = { }, CAR.SANTA_FE: { (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1210 ', b'\xf1\x00TM__ SCC F-CUP 1.00 1.01 99110-S2000 ', b'\xf1\x00TM__ SCC F-CUP 1.00 1.02 99110-S2000 ', b'\xf1\x00TM__ SCC F-CUP 1.00 1.03 99110-S2000 ', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00TM ESC \r 100\x18\x031 58910-S2650', + b'\xf1\x00TM ESC \r 105\x19\x05# 58910-S1500', b'\xf1\x00TM ESC \r 103\x18\x11\x08 58910-S2650', b'\xf1\x00TM ESC \r 104\x19\x07\x08 58910-S2650', b'\xf1\x00TM ESC \x02 100\x18\x030 58910-S2600', @@ -741,11 +746,14 @@ FW_VERSIONS = { b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409', b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8A12', b'\xf1\x00TM MDPS C 1.00 1.01 56340-S2000 9129', + b'\xf1\x00TM MDPS R 1.00 1.02 57700-S1100 4TMDP102' ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TM MFC AT EUR LHD 1.00 1.01 99211-S1010 181207', b'\xf1\x00TM MFC AT USA LHD 1.00 1.00 99211-S2000 180409', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 U833\x00\x00\x00\x00\x00\x00TTM4V22US3_<]\xf1', b'\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', b'\xf1\x87LBJSGA7082574HG0\x87www\x98\x88\x88\x88\x99\xaa\xb9\x9afw\x86gx\x99\xa7\x89co\xf8\xffvU_\xffR\xaf\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\xa6\xe0\x91', b'\xf1\x87LBKSGA0458404HG0vfvg\x87www\x89\x99\xa8\x99y\xaa\xa7\x9ax\x88\xa7\x88t_\xf9\xff\x86w\x8f\xff\x15x\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\x00\x00\x00', @@ -1595,7 +1603,6 @@ FW_VERSIONS = { CAR.TUCSON_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9240 14T', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW010 14X', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', @@ -1684,9 +1691,6 @@ FEATURES = { "use_cluster_gears": {CAR.ELANTRA, CAR.KONA}, "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022, CAR.KIA_K5_HEV_2020}, - - # these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 - "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020}, } CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN} diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 249818369c..f1e2081d05 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -18,7 +18,7 @@ from selfdrive.controls.lib.vehicle_model import VehicleModel ButtonType = car.CarState.ButtonEvent.Type GearShifter = car.CarState.GearShifter EventName = car.CarEvent.EventName -TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqueTuning, float, float, bool], float] +TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqueTuning, float, float, float, bool], float] MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS ACCEL_MAX = 2.0 @@ -131,7 +131,7 @@ class CarInterfaceBase(ABC): return self.get_steer_feedforward_default @staticmethod - def torque_from_lateral_accel_linear(lateral_accel_value, torque_params, lateral_accel_error, lateral_accel_deadzone, friction_compensation): + def torque_from_lateral_accel_linear(lateral_accel_value, torque_params, lateral_accel_error, lateral_accel_deadzone, vego, friction_compensation): # The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction) friction_interp = interp( apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone), diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index a4d2f55581..02e37492d3 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -50,6 +50,7 @@ routes = [ CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.ACADIA), CarTestRoute("aa20e335f61ba898|2019-02-05--16-59-04", GM.BUICK_REGAL), + CarTestRoute("ef8f2185104d862e|2023-02-09--18-37-13", GM.ESCALADE), CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV), CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT), CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV, segment=1), diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 78ecbe425e..ac8213e4c1 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -32,7 +32,8 @@ class TestCarInterfaces(unittest.TestCase): self.assertGreater(car_params.mass, 1) self.assertGreater(car_params.wheelbase, 0) - self.assertGreater(car_params.centerToFront, 0) + # centerToFront is center of gravity to front wheels, assert a reasonable range + self.assertTrue(car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7) self.assertGreater(car_params.maxLateralAccel, 0) # Longitudinal sanity checks diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 52c9e8d547..cc1681bce1 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -26,6 +26,7 @@ COMMA BODY: [.nan, 1000, .nan] RAM 1500 5TH GEN: [2.0, 2.0, 0.0] RAM HD 5TH GEN: [1.4, 1.4, 0.0] SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11] +CADILLAC ESCALADE 2017: [1.899999976158142, 1.842270016670227, 0.1120000034570694] CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05] CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 6d5ed45076..a85e48649f 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -230,7 +230,7 @@ def startup_master_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubM return StartupAlert("WARNING: This branch is not tested", branch, alert_status=AlertStatus.userPrompt) def below_engage_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: - return NoEntryAlert(f"Speed Below {get_display_speed(CP.minEnableSpeed, metric)}") + return NoEntryAlert(f"Drive above {get_display_speed(CP.minEnableSpeed, metric)} to engage") def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 2f56094379..9129693e5a 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -64,10 +64,10 @@ class LatControlTorque(LatControl): error = setpoint - measurement gravity_adjusted_lateral_accel = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY pid_log.error = self.torque_from_lateral_accel(error, self.torque_params, error, - lateral_accel_deadzone, friction_compensation=False) + lateral_accel_deadzone, CS.vEgo, friction_compensation=False) ff = self.torque_from_lateral_accel(gravity_adjusted_lateral_accel, self.torque_params, desired_lateral_accel - actual_lateral_accel, - lateral_accel_deadzone, friction_compensation=True) + lateral_accel_deadzone, CS.vEgo, friction_compensation=True) freeze_integrator = steer_limited or CS.steeringPressed or CS.vEgo < 5 output_torque = self.pid.update(pid_log.error, diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py index ba2d2f5c02..92fc2468bb 100755 --- a/selfdrive/controls/tests/test_startup.py +++ b/selfdrive/controls/tests/test_startup.py @@ -72,6 +72,7 @@ class TestStartup(unittest.TestCase): params.clear_all() params.put_bool("Passive", False) params.put_bool("OpenpilotEnabledToggle", True) + params.put_bool("ObdMultiplexingDisabled", True) # Build capnn version of FW array if fw_versions is not None: diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index 326e68f8e7..b3598b105c 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -17,7 +17,7 @@ def get_fingerprint(lr): for c in msg.can: # read also msgs sent by EON on CAN bus 0x80 and filter out the # addr with more than 11 bits - if c.src % 0x80 == 0 and c.address < 0x800: + if c.src % 0x80 == 0 and c.address < 0x800 and c.address not in (0x7df, 0x7e0, 0x7e8): msgs[c.address] = len(c.dat) # show CAN fingerprint diff --git a/selfdrive/debug/get_fingerprint.py b/selfdrive/debug/get_fingerprint.py index e678db4f17..f7f7a1604f 100755 --- a/selfdrive/debug/get_fingerprint.py +++ b/selfdrive/debug/get_fingerprint.py @@ -22,7 +22,7 @@ while True: for c in lc.can: # read also msgs sent by EON on CAN bus 0x80 and filter out the # addr with more than 11 bits - if c.src in [0, 2] and c.address < 0x800: + if c.src % 0x80 == 0 and c.address < 0x800 and c.address not in (0x7df, 0x7e0, 0x7e8): msgs[c.address] = len(c.dat) fingerprint = ', '.join("%d: %d" % v for v in sorted(msgs.items())) diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 8941b50248..307626506a 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -619,7 +619,7 @@ kj::ArrayPtr Localizer::get_message_bytes(MessageBuilder& msg_build } bool Localizer::is_gps_ok() { - return (this->kf->get_filter_time() - this->last_gps_msg) < 1.0; + return (this->kf->get_filter_time() - this->last_gps_msg) < 2.0; } bool Localizer::critical_services_valid(std::map critical_services) { diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index b531cb3430..28fc9c452c 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -407,6 +407,7 @@ def setup_env(simulation=False, CP=None, cfg=None, controlsState=None): params.put_bool("WideCameraOnly", False) params.put_bool("DisableLogging", False) params.put_bool("UbloxAvailable", True) + params.put_bool("ObdMultiplexingDisabled", True) os.environ["NO_RADAR_SLEEP"] = "1" os.environ["REPLAY"] = "1" diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 10665ae18c..6884eb4660 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -69e52f02fd21844ff068c495b7fcb01ebc53bea5 \ No newline at end of file +8883c476d5abc12b4b2949e04c6d7c0cd7c8b9fa diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 3ff3d36ebc..569090f606 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -48,7 +48,7 @@ segments = [ ("TOYOTA3", "regen89026F6BD8D|2022-09-27--15-45-37--0"), ("HONDA", "regenC7D5645EB17|2022-09-27--15-47-29--0"), ("HONDA2", "regenCC2ECCE5742|2022-09-27--16-18-01--0"), - ("CHRYSLER", "regenC253C4DAC90|2023-02-10--15-51-03--0"), + ("CHRYSLER", "regenC253C4DAC90|2022-09-27--15-51-03--0"), ("RAM", "regen20490083AE7|2022-09-27--15-53-15--0"), ("SUBARU", "regen1E72BBDCED5|2022-09-27--15-55-31--0"), ("GM", "regen45B05A80EF6|2022-09-27--15-57-22--0"), diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 52b31b3559..20edfd06ff 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -1,15 +1,17 @@ #include "tools/cabana/binaryview.h" +#include + #include #include #include #include #include +#include #include -#include - #include "tools/cabana/commands.h" +#include "tools/cabana/signaledit.h" #include "tools/cabana/streams/abstractstream.h" // BinaryView @@ -38,6 +40,78 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &BinaryView::refresh); QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &BinaryView::refresh); + + addShortcuts(); + setWhatsThis(R"( + Binary View
+ + Shortcuts:
+ Delete Signal: + x , + Backspace , + Delete
+ Change endianness: e
+ Change singedness: s
+ Open chart: + c , + p , + g
+ )"); +} + +void BinaryView::addShortcuts() { + // Delete (x, backspace, delete) + QShortcut *shortcut_delete_x = new QShortcut(QKeySequence(Qt::Key_X), this); + QShortcut *shortcut_delete_backspace = new QShortcut(QKeySequence(Qt::Key_Backspace), this); + QShortcut *shortcut_delete_delete = new QShortcut(QKeySequence(Qt::Key_Delete), this); + QObject::connect(shortcut_delete_delete, &QShortcut::activated, shortcut_delete_x, &QShortcut::activated); + QObject::connect(shortcut_delete_backspace, &QShortcut::activated, shortcut_delete_x, &QShortcut::activated); + QObject::connect(shortcut_delete_x, &QShortcut::activated, [=]{ + if (hovered_sig != nullptr) { + emit removeSignal(hovered_sig); + hovered_sig = nullptr; + } + }); + + // Change endianness (e) + QShortcut *shortcut_endian = new QShortcut(QKeySequence(Qt::Key_E), this); + QObject::connect(shortcut_endian, &QShortcut::activated, [=]{ + if (hovered_sig != nullptr) { + const Signal *hovered_sig_prev = hovered_sig; + Signal s = *hovered_sig; + s.is_little_endian = !s.is_little_endian; + emit editSignal(hovered_sig, s); + + hovered_sig = nullptr; + highlight(hovered_sig_prev); + } + }); + + // Change signedness (s) + QShortcut *shortcut_sign = new QShortcut(QKeySequence(Qt::Key_S), this); + QObject::connect(shortcut_sign, &QShortcut::activated, [=]{ + if (hovered_sig != nullptr) { + const Signal *hovered_sig_prev = hovered_sig; + Signal s = *hovered_sig; + s.is_signed = !s.is_signed; + emit editSignal(hovered_sig, s); + + hovered_sig = nullptr; + highlight(hovered_sig_prev); + } + }); + + // Open chart (c, p, g) + QShortcut *shortcut_plot = new QShortcut(QKeySequence(Qt::Key_P), this); + QShortcut *shortcut_plot_g = new QShortcut(QKeySequence(Qt::Key_G), this); + QShortcut *shortcut_plot_c = new QShortcut(QKeySequence(Qt::Key_C), this); + QObject::connect(shortcut_plot_g, &QShortcut::activated, shortcut_plot, &QShortcut::activated); + QObject::connect(shortcut_plot_c, &QShortcut::activated, shortcut_plot, &QShortcut::activated); + QObject::connect(shortcut_plot, &QShortcut::activated, [=]{ + if (hovered_sig != nullptr) { + emit showChart(*model->msg_id, hovered_sig, true, false); + } + }); } QSize BinaryView::minimumSizeHint() const { @@ -83,7 +157,7 @@ void BinaryView::mousePressEvent(QMouseEvent *event) { if (bit_idx == s->lsb || bit_idx == s->msb) { anchor_index = model->bitIndex(bit_idx == s->lsb ? s->msb : s->lsb, true); resize_sig = s; - delegate->selection_color = item->bg_color; + delegate->selection_color = getColor(s); break; } } @@ -130,14 +204,14 @@ void BinaryView::leaveEvent(QEvent *event) { QTableView::leaveEvent(event); } -void BinaryView::setMessage(const QString &message_id) { +void BinaryView::setMessage(const MessageId &message_id) { model->msg_id = message_id; verticalScrollBar()->setValue(0); refresh(); } void BinaryView::refresh() { - if (model->msg_id.isEmpty()) return; + if (!model->msg_id) return; clearSelection(); anchor_index = QModelIndex(); @@ -172,7 +246,7 @@ std::tuple BinaryView::getSelection(QModelIndex index) { void BinaryViewModel::refresh() { beginResetModel(); items.clear(); - if ((dbc_msg = dbc()->msg(msg_id))) { + if ((dbc_msg = dbc()->msg(*msg_id))) { row_count = dbc_msg->size; items.resize(row_count * column_count); for (auto sig : dbc_msg->getSignals()) { @@ -191,7 +265,7 @@ void BinaryViewModel::refresh() { } } } else { - row_count = can->lastMessage(msg_id).dat.size(); + row_count = can->lastMessage(*msg_id).dat.size(); items.resize(row_count * column_count); } endResetModel(); @@ -200,7 +274,7 @@ void BinaryViewModel::refresh() { void BinaryViewModel::updateState() { auto prev_items = items; - const auto &last_msg = can->lastMessage(msg_id); + const auto &last_msg = can->lastMessage(*msg_id); const auto &binary = last_msg.dat; // data size may changed. @@ -266,6 +340,17 @@ BinaryItemDelegate::BinaryItemDelegate(QObject *parent) : QStyledItemDelegate(pa hex_font.setBold(true); } +bool BinaryItemDelegate::isSameColor(const QModelIndex &index, int dx, int dy) const { + QModelIndex index2 = index.sibling(index.row() + dy, index.column() + dx); + if (!index2.isValid()) { + return false; + } + auto color1 = ((const BinaryViewModel::Item *)index.internalPointer())->bg_color; + auto color2 = ((const BinaryViewModel::Item *)index2.internalPointer())->bg_color; + // Ignore alpha + return (color1.red() == color2.red()) && (color2.green() == color2.green()) && (color1.blue() == color2.blue()); +} + void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto item = (const BinaryViewModel::Item *)index.internalPointer(); BinaryView *bin_view = (BinaryView *)parent(); @@ -277,24 +362,71 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op } else if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, selection_color); painter->setPen(option.palette.color(QPalette::BrightText)); - } else if (!item->sigs.isEmpty() && (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig))) { - bool sig_hovered = item->sigs.contains(bin_view->hovered_sig); - int min_alpha = item->sigs.contains(bin_view->hovered_sig) ? 255 : 50; + } else if (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig)) { // not resizing QColor bg = item->bg_color; - if (bg.alpha() < min_alpha) { - bg.setAlpha(min_alpha); + if (bin_view->hovered_sig && item->sigs.contains(bin_view->hovered_sig)) { + bg.setAlpha(255); + painter->fillRect(option.rect, bg.darker(125)); // 4/5x brightness + painter->setPen(option.palette.color(QPalette::BrightText)); + } else { + if (item->sigs.size() > 0) { + drawBorder(painter, option, index); + bg.setAlpha(std::max(50, bg.alpha())); + } + painter->fillRect(option.rect, bg); + painter->setPen(Qt::black); } - painter->fillRect(option.rect, sig_hovered ? bg.darker(125) : bg); // 4/5x brightness - painter->setPen(sig_hovered ? option.palette.color(QPalette::BrightText) : Qt::black); - } else { - painter->fillRect(option.rect, item->bg_color); } - painter->drawText(option.rect, Qt::AlignCenter, item->val); if (item->is_msb || item->is_lsb) { painter->setFont(small_font); - painter->drawText(option.rect, Qt::AlignHCenter | Qt::AlignBottom, item->is_msb ? "MSB" : "LSB"); + painter->drawText(option.rect.adjusted(8, 0, -8, -3), Qt::AlignRight | Qt::AlignBottom, item->is_msb ? "M" : "L"); } painter->restore(); } + +// Draw border on edge of signal +void BinaryItemDelegate::drawBorder(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + auto item = (const BinaryViewModel::Item *)index.internalPointer(); + QColor border_color = item->bg_color; + border_color.setAlphaF(1.0); + + bool draw_left = !isSameColor(index, -1, 0); + bool draw_top = !isSameColor(index, 0, -1); + bool draw_right = !isSameColor(index, 1, 0); + bool draw_bottom = !isSameColor(index, 0, 1); + + const int spacing = 2; + QRect rc = option.rect.adjusted(draw_left * 3, draw_top * spacing, draw_right * -3, draw_bottom * -spacing); + QRegion subtract; + if (!draw_top) { + if (!draw_left && !isSameColor(index, -1, -1)) { + subtract += QRect{rc.left(), rc.top(), 3, spacing}; + } else if (!draw_right && !isSameColor(index, 1, -1)) { + subtract += QRect{rc.right() - 2, rc.top(), 3, spacing}; + } + } + if (!draw_bottom) { + if (!draw_left && !isSameColor(index, -1, 1)) { + subtract += QRect{rc.left(), rc.bottom() - (spacing - 1), 3, spacing}; + } else if (!draw_right && !isSameColor(index, 1, 1)) { + subtract += QRect{rc.right() - 2, rc.bottom() - (spacing - 1), 3, spacing}; + } + } + + painter->setPen(QPen(border_color, 1)); + if (draw_left) painter->drawLine(rc.topLeft(), rc.bottomLeft()); + if (draw_right) painter->drawLine(rc.topRight(), rc.bottomRight()); + if (draw_bottom) painter->drawLine(rc.bottomLeft(), rc.bottomRight()); + if (draw_top) painter->drawLine(rc.topLeft(), rc.topRight()); + + painter->setClipRegion(QRegion(rc).subtracted(subtract)); + if (!subtract.isEmpty()) { + // fill gaps inside corners. + painter->setPen(QPen(border_color, 2)); + for (auto &r : subtract) { + painter->drawRect(r); + } + } +} diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index d006672793..6743b6cfac 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -13,6 +15,8 @@ public: BinaryItemDelegate(QObject *parent); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setSelectionColor(const QColor &color) { selection_color = color; } + bool isSameColor(const QModelIndex &index, int dx, int dy) const; + void drawBorder(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QFont small_font, hex_font; QColor selection_color; @@ -44,7 +48,7 @@ public: }; std::vector items; - QString msg_id; + std::optional msg_id; const DBCMsg *dbc_msg = nullptr; int row_count = 0; const int column_count = 9; @@ -55,7 +59,7 @@ class BinaryView : public QTableView { public: BinaryView(QWidget *parent = nullptr); - void setMessage(const QString &message_id); + void setMessage(const MessageId &message_id); void highlight(const Signal *sig); QSet getOverlappingSignals() const; inline void updateState() { model->updateState(); } @@ -66,8 +70,12 @@ signals: void signalHovered(const Signal *sig); void addSignal(int start_bit, int size, bool little_endian); void resizeSignal(const Signal *sig, int from, int size); + void removeSignal(const Signal *sig); + void editSignal(const Signal *origin_s, Signal &s); + void showChart(const MessageId &id, const Signal *sig, bool show, bool merge); private: + void addShortcuts(); void refresh(); std::tuple getSelection(QModelIndex index); void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 5063ec9dea..9c5ed097c6 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -11,9 +11,11 @@ #include #include #include +#include #include #include +const int MAX_COLUMN_COUNT = 4; // ChartsWidget ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { @@ -23,14 +25,17 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { QToolBar *toolbar = new QToolBar(tr("Charts"), this); toolbar->setIconSize({16, 16}); - QAction *new_plot_btn = toolbar->addAction(utils::icon("file-plus"), ""); - new_plot_btn->setToolTip(tr("New Plot")); + QAction *new_plot_btn = toolbar->addAction(utils::icon("file-plus"), tr("New Plot")); toolbar->addWidget(title_label = new QLabel()); title_label->setContentsMargins(0, 0, 12, 0); - columns_cb = new QComboBox(this); - columns_cb->addItems({"1", "2", "3", "4"}); - columns_lb_action = toolbar->addWidget(new QLabel(tr("Columns:"))); - columns_cb_action = toolbar->addWidget(columns_cb); + + QMenu *menu = new QMenu(this); + for (int i = 0; i < MAX_COLUMN_COUNT; ++i) { + menu->addAction(tr("%1").arg(i + 1), [=]() { setColumnCount(i + 1); }); + } + columns_action = toolbar->addAction(""); + columns_action->setMenu(menu); + qobject_cast(toolbar->widgetForAction(columns_action))->setPopupMode(QToolButton::InstantPopup); QLabel *stretch_label = new QLabel(this); stretch_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); @@ -44,13 +49,10 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { range_slider->setPageStep(60); // 1 min range_slider_action = toolbar->addWidget(range_slider); - reset_zoom_action = toolbar->addWidget(reset_zoom_btn = new QToolButton()); - reset_zoom_btn->setIcon(utils::icon("zoom-out")); - reset_zoom_btn->setToolTip(tr("Reset zoom")); - reset_zoom_btn->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); - remove_all_btn = toolbar->addAction(utils::icon("x"), ""); - remove_all_btn->setToolTip(tr("Remove all charts")); + remove_all_btn = toolbar->addAction(utils::icon("x"), tr("Remove all charts")); dock_btn = toolbar->addAction(""); main_layout->addWidget(toolbar); @@ -73,10 +75,9 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { // init settings use_dark_theme = QApplication::style()->standardPalette().color(QPalette::WindowText).value() > QApplication::style()->standardPalette().color(QPalette::Background).value(); - column_count = std::clamp(settings.chart_column_count, 1, columns_cb->count()); + column_count = std::clamp(settings.chart_column_count, 1, MAX_COLUMN_COUNT); max_chart_range = std::clamp(settings.chart_range, 1, settings.max_cached_minutes * 60); display_range = {0, max_chart_range}; - columns_cb->setCurrentIndex(column_count - 1); range_slider->setValue(max_chart_range); updateToolBar(); @@ -86,14 +87,18 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { 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(reset_zoom_btn, &QToolButton::clicked, this, &ChartsWidget::zoomReset); - QObject::connect(columns_cb, SIGNAL(activated(int)), SLOT(setColumnCount(int))); + 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]() { emit dock(!docking); docking = !docking; updateToolBar(); }); + + setWhatsThis(tr(R"( + Chart view
+ + )")); } void ChartsWidget::eventsMerged() { @@ -162,11 +167,12 @@ void ChartsWidget::setMaxChartRange(int value) { void ChartsWidget::updateToolBar() { title_label->setText(tr("Charts: %1").arg(charts.size())); + columns_action->setText(tr("Column: %1").arg(column_count)); range_lb->setText(QString("Range: %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0'))); range_lb_action->setVisible(!is_zoomed); range_slider_action->setVisible(!is_zoomed); reset_zoom_action->setVisible(is_zoomed); - reset_zoom_btn->setText(is_zoomed ? tr("Zoomin: %1-%2").arg(zoomed_range.first, 0, 'f', 1).arg(zoomed_range.second, 0, 'f', 1) : ""); + 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()); dock_btn->setIcon(utils::icon(docking ? "arrow-up-right-square" : "arrow-down-left-square")); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); @@ -180,7 +186,7 @@ void ChartsWidget::settingChanged() { } } -ChartView *ChartsWidget::findChart(const QString &id, const Signal *sig) { +ChartView *ChartsWidget::findChart(const MessageId &id, const Signal *sig) { for (auto c : charts) if (c->hasSeries(id, sig)) return c; return nullptr; @@ -203,7 +209,7 @@ ChartView *ChartsWidget::createChart() { return chart; } -void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bool merge) { +void ChartsWidget::showChart(const MessageId &id, const Signal *sig, bool show, bool merge) { setUpdatesEnabled(false); ChartView *chart = findChart(id, sig); if (show && !chart) { @@ -211,29 +217,29 @@ void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bo chart->addSeries(id, sig); updateState(); } else if (!show && chart) { - chart->removeSeries(id, sig); + chart->removeIf([&](auto &s) { return s.msg_id == id && s.sig == sig; }); } updateToolBar(); setUpdatesEnabled(true); } void ChartsWidget::setColumnCount(int n) { - n = std::clamp(n + 1, 1, columns_cb->count()); + n = std::clamp(n, 1, MAX_COLUMN_COUNT); if (column_count != n) { column_count = settings.chart_column_count = n; + updateToolBar(); updateLayout(); } } void ChartsWidget::updateLayout() { - int n = columns_cb->count(); + int n = MAX_COLUMN_COUNT; for (; n > 1; --n) { if ((n * CHART_MIN_WIDTH + (n - 1) * charts_layout->spacing()) < charts_layout->geometry().width()) break; } bool show_column_cb = n > 1; - columns_lb_action->setVisible(show_column_cb); - columns_cb_action->setVisible(show_column_cb); + columns_action->setVisible(show_column_cb); n = std::min(column_count, n); if (charts.size() != charts_layout->count() || n != current_column_count) { @@ -305,7 +311,6 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { series_type = settings.chart_series_type == 0 ? QAbstractSeries::SeriesTypeLine : QAbstractSeries::SeriesTypeScatter; - QChart *chart = new QChart(); chart->setBackgroundVisible(false); axis_x = new QValueAxis(this); @@ -363,43 +368,40 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove); } -void ChartView::addSeries(const QString &msg_id, const Signal *sig) { +void ChartView::addSeries(const MessageId &msg_id, const Signal *sig) { + if (hasSeries(msg_id, sig)) return; + QXYSeries *series = createSeries(series_type, getColor(sig)); - chart()->addSeries(series); - series->attachAxis(axis_x); - series->attachAxis(axis_y); - auto [source, address] = DBCManager::parseId(msg_id); - sigs.push_back({.msg_id = msg_id, .address = address, .source = source, .sig = sig, .series = series}); + sigs.push_back({.msg_id = msg_id, .sig = sig, .series = series}); updateTitle(); updateSeries(sig); updateSeriesPoints(); emit seriesAdded(msg_id, sig); } -void ChartView::removeSeries(const QString &msg_id, const Signal *sig) { - auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; }); - if (it != sigs.end()) { - it = removeItem(it); - } -} - -bool ChartView::hasSeries(const QString &msg_id, const Signal *sig) const { +bool ChartView::hasSeries(const MessageId &msg_id, const Signal *sig) const { return std::any_of(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; }); } -QList::iterator ChartView::removeItem(const QList::iterator &it) { - chart()->removeSeries(it->series); - it->series->deleteLater(); - QString msg_id = it->msg_id; - const Signal *sig = it->sig; - auto ret = sigs.erase(it); - emit seriesRemoved(msg_id, sig); - if (!sigs.isEmpty()) { - updateAxisY(); - } else { +void ChartView::removeIf(std::function predicate) { + int prev_size = sigs.size(); + for (auto it = sigs.begin(); it != sigs.end(); /**/) { + if (predicate(*it)) { + chart()->removeSeries(it->series); + it->series->deleteLater(); + auto msg_id = it->msg_id; + auto sig = it->sig; + it = sigs.erase(it); + emit seriesRemoved(msg_id, sig); + } else { + ++it; + } + } + if (sigs.empty()) { emit remove(); + } else if (sigs.size() != prev_size) { + updateAxisY(); } - return ret; } void ChartView::signalUpdated(const Signal *sig) { @@ -410,23 +412,11 @@ void ChartView::signalUpdated(const Signal *sig) { } } -void ChartView::signalRemoved(const Signal *sig) { - for (auto it = sigs.begin(); it != sigs.end(); /**/) { - it = (it->sig == sig) ? removeItem(it) : ++it; - } -} - void ChartView::msgUpdated(uint32_t address) { - if (std::any_of(sigs.begin(), sigs.end(), [=](auto &s) { return s.address == address; })) + if (std::any_of(sigs.begin(), sigs.end(), [=](auto &s) { return s.msg_id.address == address; })) updateTitle(); } -void ChartView::msgRemoved(uint32_t address) { - for (auto it = sigs.begin(); it != sigs.end(); /**/) { - it = (it->address == address) ? removeItem(it) : ++it; - } -} - void ChartView::manageSeries() { SeriesSelector dlg(tr("Mange Chart"), this); for (auto &s : sigs) { @@ -434,21 +424,12 @@ void ChartView::manageSeries() { } if (dlg.exec() == QDialog::Accepted) { auto items = dlg.seletedItems(); - if (items.isEmpty()) { - emit remove(); - } else { - for (auto s : items) { - if (!hasSeries(s->msg_id, s->sig)) { - addSeries(s->msg_id, s->sig); - } - } - for (auto it = sigs.begin(); it != sigs.end(); /**/) { - bool exists = std::any_of(items.cbegin(), items.cend(), [&](auto &s) { - return s->msg_id == it->msg_id && s->sig == it->sig; - }); - it = exists ? ++it : removeItem(it); - } + for (auto s : items) { + addSeries(s->msg_id, s->sig); } + removeIf([&](auto &s) { + return std::none_of(items.cbegin(), items.cend(), [&](auto &it) { return s.msg_id == it->msg_id && s.sig == it->sig; }); + }); } } @@ -478,7 +459,7 @@ void ChartView::updateTitle() { } for (auto &s : sigs) { auto decoration = s.series->isVisible() ? "none" : "line-through"; - s.series->setName(QString("%2 %3 %4").arg(decoration, s.sig->name.c_str(), msgName(s.msg_id), s.msg_id)); + s.series->setName(QString("%2 %3 %4").arg(decoration, s.sig->name.c_str(), msgName(s.msg_id), s.msg_id.toString())); } } @@ -503,7 +484,7 @@ void ChartView::updateSeriesPoints() { int pixels_per_point = width() / num_points; if (series_type == QAbstractSeries::SeriesTypeScatter) { - ((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 1, 8)); + ((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 2, 8)); } else { s.series->setPointsVisible(pixels_per_point > 20); } @@ -540,7 +521,7 @@ void ChartView::updateSeries(const Signal *sig, const std::vector *even for (auto it = chunk.first; it != chunk.second; ++it) { if ((*it)->which == cereal::Event::Which::CAN) { for (const auto &c : (*it)->event.getCan()) { - if (s.address == c.getAddress() && s.source == c.getSrc()) { + if (s.msg_id.address == c.getAddress() && s.msg_id.source == c.getSrc()) { auto dat = c.getDat(); double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig); double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds @@ -698,7 +679,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { text_list.push_front(QString::number(chart()->mapToValue(pt).x(), 'f', 3)); QPointF tooltip_pt(pt.x() + 12, plot_area.top() - 20); QToolTip::showText(mapToGlobal(tooltip_pt.toPoint()), pt.isNull() ? "" : text_list.join("
"), this, plot_area.toRect()); - scene()->update(); + scene()->invalidate({}, QGraphicsScene::ForegroundLayer); } else { QToolTip::hideText(); } @@ -780,8 +761,10 @@ QXYSeries *ChartView::createSeries(QAbstractSeries::SeriesType type, QColor colo QXYSeries *series = nullptr; if (type == QAbstractSeries::SeriesTypeLine) { series = new QLineSeries(this); + chart()->legend()->setMarkerShape(QLegend::MarkerShapeRectangle); } else { series = new QScatterSeries(this); + chart()->legend()->setMarkerShape(QLegend::MarkerShapeCircle); } series->setColor(color); // TODO: Due to a bug in CameraWidget the camera frames @@ -793,6 +776,9 @@ QXYSeries *ChartView::createSeries(QAbstractSeries::SeriesType type, QColor colo pen.setWidth(2.0 * qApp->devicePixelRatio()); series->setPen(pen); #endif + chart()->addSeries(series); + series->attachAxis(axis_x); + series->attachAxis(axis_y); return series; } @@ -807,9 +793,6 @@ void ChartView::setSeriesType(QAbstractSeries::SeriesType type) { } for (auto &s : sigs) { auto series = createSeries(series_type, getColor(s.sig)); - chart()->addSeries(series); - series->attachAxis(axis_x); - series->attachAxis(axis_y); series->replace(s.vals); s.series = series; } @@ -850,7 +833,9 @@ SeriesSelector::SeriesSelector(QString title, QWidget *parent) : QDialog(parent) // buttons QVBoxLayout *btn_layout = new QVBoxLayout(); QPushButton *add_btn = new QPushButton(utils::icon("chevron-right"), "", this); + add_btn->setEnabled(false); QPushButton *remove_btn = new QPushButton(utils::icon("chevron-left"), "", this); + remove_btn->setEnabled(false); btn_layout->addStretch(0); btn_layout->addWidget(add_btn); btn_layout->addWidget(remove_btn); @@ -866,7 +851,7 @@ SeriesSelector::SeriesSelector(QString title, QWidget *parent) : QDialog(parent) for (auto it = can->can_msgs.cbegin(); it != can->can_msgs.cend(); ++it) { if (auto m = dbc()->msg(it.key())) { - msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(it.key()), it.key()); + msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(it.key().toString()), QVariant::fromValue(it.key())); } } msgs_combo->model()->sort(0); @@ -891,7 +876,7 @@ void SeriesSelector::add(QListWidgetItem *item) { void SeriesSelector::remove(QListWidgetItem *item) { auto it = (ListItem *)item; - if (it->msg_id == msgs_combo->currentData().toString()) { + if (it->msg_id == msgs_combo->currentData().value()) { addItemToList(available_list, it->msg_id, it->sig); } delete item; @@ -900,7 +885,7 @@ void SeriesSelector::remove(QListWidgetItem *item) { void SeriesSelector::updateAvailableList(int index) { if (index == -1) return; available_list->clear(); - QString msg_id = msgs_combo->itemData(index).toString(); + MessageId msg_id = msgs_combo->itemData(index).value(); auto selected_items = seletedItems(); for (auto &[name, s] : dbc()->msg(msg_id)->sigs) { bool is_selected = std::any_of(selected_items.begin(), selected_items.end(), [=, sig=&s](auto it) { return it->msg_id == msg_id && it->sig == sig; }); @@ -910,9 +895,9 @@ void SeriesSelector::updateAvailableList(int index) { } } -void SeriesSelector::addItemToList(QListWidget *parent, const QString id, const Signal *sig, bool show_msg_name) { +void SeriesSelector::addItemToList(QListWidget *parent, const MessageId id, const Signal *sig, bool show_msg_name) { QString text = QString(" %1").arg(getColor(sig).name(), sig->name.c_str()); - if (show_msg_name) text += QString(" %0 %1").arg(msgName(id), id); + if (show_msg_name) text += QString(" %0 %1").arg(msgName(id), id.toString()); QLabel *label = new QLabel(text); label->setContentsMargins(5, 0, 5, 0); diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 25949dd654..7569155d39 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -8,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -27,18 +25,15 @@ class ChartView : public QChartView { public: ChartView(QWidget *parent = nullptr); - void addSeries(const QString &msg_id, const Signal *sig); - void removeSeries(const QString &msg_id, const Signal *sig); - bool hasSeries(const QString &msg_id, const Signal *sig) const; + void addSeries(const MessageId &msg_id, const Signal *sig); + bool hasSeries(const MessageId &msg_id, const Signal *sig) const; void updateSeries(const Signal *sig = nullptr, const std::vector *events = nullptr, bool clear = true); void updatePlot(double cur, double min, double max); void setSeriesType(QAbstractSeries::SeriesType type); void updatePlotArea(int left); struct SigItem { - QString msg_id; - uint8_t source = 0; - uint32_t address = 0; + MessageId msg_id; const Signal *sig = nullptr; QXYSeries *series = nullptr; QVector vals; @@ -46,23 +41,22 @@ public: }; signals: - void seriesRemoved(const QString &id, const Signal *sig); - void seriesAdded(const QString &id, const Signal *sig); + void seriesRemoved(const MessageId &id, const Signal *sig); + void seriesAdded(const MessageId &id, const Signal *sig); void zoomIn(double min, double max); void zoomReset(); void remove(); void axisYLabelWidthChanged(int w); private slots: - void msgRemoved(uint32_t address); void msgUpdated(uint32_t address); void signalUpdated(const Signal *sig); - void signalRemoved(const Signal *sig); void manageSeries(); void handleMarkerClicked(); + void msgRemoved(uint32_t address) { removeIf([=](auto &s) { return s.msg_id.address == address; }); } + void signalRemoved(const Signal *sig) { removeIf([=](auto &s) { return s.sig == sig; }); } private: - QList::iterator removeItem(const QList::iterator &it); void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *ev) override; @@ -78,6 +72,7 @@ private: qreal niceNumber(qreal x, bool ceiling); QXYSeries *createSeries(QAbstractSeries::SeriesType type, QColor color); void updateSeriesPoints(); + void removeIf(std::function predicate); int y_label_width = 0; int align_to = 0; @@ -102,8 +97,8 @@ class ChartsWidget : public QWidget { public: ChartsWidget(QWidget *parent = nullptr); - void showChart(const QString &id, const Signal *sig, bool show, bool merge); - inline bool hasSignal(const QString &id, const Signal *sig) { return findChart(id, sig) != nullptr; } + void showChart(const MessageId &id, const Signal *sig, bool show, bool merge); + inline bool hasSignal(const MessageId &id, const Signal *sig) { return findChart(id, sig) != nullptr; } public slots: void setColumnCount(int n); @@ -129,7 +124,7 @@ private: void updateLayout(); void settingChanged(); bool eventFilter(QObject *obj, QEvent *event) override; - ChartView *findChart(const QString &id, const Signal *sig); + ChartView *findChart(const MessageId &id, const Signal *sig); QLabel *title_label; QLabel *range_lb; @@ -139,7 +134,6 @@ private: bool docking = true; QAction *dock_btn; QAction *reset_zoom_action; - QToolButton *reset_zoom_btn; QAction *remove_all_btn; QGridLayout *charts_layout; QList charts; @@ -148,9 +142,7 @@ private: std::pair display_range; std::pair zoomed_range; bool use_dark_theme = false; - QAction *columns_lb_action; - QAction *columns_cb_action; - QComboBox *columns_cb; + QAction *columns_action; int column_count = 1; int current_column_count = 0; }; @@ -158,18 +150,18 @@ private: class SeriesSelector : public QDialog { public: struct ListItem : public QListWidgetItem { - ListItem(const QString &msg_id, const Signal *sig, QListWidget *parent) : msg_id(msg_id), sig(sig), QListWidgetItem(parent) {} - QString msg_id; + ListItem(const MessageId &msg_id, const Signal *sig, QListWidget *parent) : msg_id(msg_id), sig(sig), QListWidgetItem(parent) {} + MessageId msg_id; const Signal *sig; }; SeriesSelector(QString title, QWidget *parent); QList seletedItems(); - inline void addSelected(const QString &id, const Signal *sig) { addItemToList(selected_list, id, sig, true); } + inline void addSelected(const MessageId &id, const Signal *sig) { addItemToList(selected_list, id, sig, true); } private: void updateAvailableList(int index); - void addItemToList(QListWidget *parent, const QString id, const Signal *sig, bool show_msg_name = false); + void addItemToList(QListWidget *parent, const MessageId id, const Signal *sig, bool show_msg_name = false); void add(QListWidgetItem *item); void remove(QListWidgetItem *item); diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc index e4bf999062..b03f46b5d2 100644 --- a/tools/cabana/commands.cc +++ b/tools/cabana/commands.cc @@ -4,13 +4,13 @@ // EditMsgCommand -EditMsgCommand::EditMsgCommand(const QString &id, const QString &title, int size, QUndoCommand *parent) +EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &title, int size, QUndoCommand *parent) : id(id), new_title(title), new_size(size), QUndoCommand(parent) { if (auto msg = dbc()->msg(id)) { old_title = msg->name; old_size = msg->size; } - setText(QObject::tr("Edit message %1:%2").arg(DBCManager::parseId(id).second).arg(title)); + setText(QObject::tr("Edit message %1:%2").arg(id.address).arg(title)); } void EditMsgCommand::undo() { @@ -26,10 +26,10 @@ void EditMsgCommand::redo() { // RemoveMsgCommand -RemoveMsgCommand::RemoveMsgCommand(const QString &id, QUndoCommand *parent) : id(id), QUndoCommand(parent) { +RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) : id(id), QUndoCommand(parent) { if (auto msg = dbc()->msg(id)) { message = *msg; - setText(QObject::tr("Remove message %1:%2").arg(DBCManager::parseId(id).second).arg(message.name)); + setText(QObject::tr("Remove message %1:%2").arg(id.address).arg(message.name)); } } @@ -48,9 +48,9 @@ void RemoveMsgCommand::redo() { // AddSigCommand -AddSigCommand::AddSigCommand(const QString &id, const Signal &sig, QUndoCommand *parent) +AddSigCommand::AddSigCommand(const MessageId &id, const Signal &sig, QUndoCommand *parent) : id(id), signal(sig), QUndoCommand(parent) { - setText(QObject::tr("Add signal %1 to %2").arg(sig.name.c_str()).arg(DBCManager::parseId(id).second)); + setText(QObject::tr("Add signal %1 to %2").arg(sig.name.c_str()).arg(id.address)); } void AddSigCommand::undo() { dbc()->removeSignal(id, signal.name.c_str()); } @@ -58,9 +58,9 @@ void AddSigCommand::redo() { dbc()->addSignal(id, signal); } // RemoveSigCommand -RemoveSigCommand::RemoveSigCommand(const QString &id, const Signal *sig, QUndoCommand *parent) +RemoveSigCommand::RemoveSigCommand(const MessageId &id, const Signal *sig, QUndoCommand *parent) : id(id), signal(*sig), QUndoCommand(parent) { - setText(QObject::tr("Remove signal %1 from %2").arg(signal.name.c_str()).arg(DBCManager::parseId(id).second)); + setText(QObject::tr("Remove signal %1 from %2").arg(signal.name.c_str()).arg(id.address)); } void RemoveSigCommand::undo() { dbc()->addSignal(id, signal); } @@ -68,7 +68,7 @@ void RemoveSigCommand::redo() { dbc()->removeSignal(id, signal.name.c_str()); } // EditSignalCommand -EditSignalCommand::EditSignalCommand(const QString &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent) +EditSignalCommand::EditSignalCommand(const MessageId &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent) : id(id), old_signal(*sig), new_signal(new_sig), QUndoCommand(parent) { setText(QObject::tr("Edit signal %1").arg(old_signal.name.c_str())); } diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h index c07a00b760..46e9f0a030 100644 --- a/tools/cabana/commands.h +++ b/tools/cabana/commands.h @@ -7,57 +7,57 @@ class EditMsgCommand : public QUndoCommand { public: - EditMsgCommand(const QString &id, const QString &title, int size, QUndoCommand *parent = nullptr); + EditMsgCommand(const MessageId &id, const QString &title, int size, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: - const QString id; + const MessageId id; QString old_title, new_title; int old_size = 0, new_size = 0; }; class RemoveMsgCommand : public QUndoCommand { public: - RemoveMsgCommand(const QString &id, QUndoCommand *parent = nullptr); + RemoveMsgCommand(const MessageId &id, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: - const QString id; + const MessageId id; DBCMsg message; }; class AddSigCommand : public QUndoCommand { public: - AddSigCommand(const QString &id, const Signal &sig, QUndoCommand *parent = nullptr); + AddSigCommand(const MessageId &id, const Signal &sig, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: - const QString id; + const MessageId id; Signal signal = {}; }; class RemoveSigCommand : public QUndoCommand { public: - RemoveSigCommand(const QString &id, const Signal *sig, QUndoCommand *parent = nullptr); + RemoveSigCommand(const MessageId &id, const Signal *sig, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: - const QString id; + const MessageId id; Signal signal = {}; }; class EditSignalCommand : public QUndoCommand { public: - EditSignalCommand(const QString &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent = nullptr); + EditSignalCommand(const MessageId &id, const Signal *sig, const Signal &new_sig, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: - const QString id; + const MessageId id; Signal old_signal = {}; Signal new_signal = {}; }; diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbcmanager.cc index 3d565e7067..27f16c71e5 100644 --- a/tools/cabana/dbcmanager.cc +++ b/tools/cabana/dbcmanager.cc @@ -4,6 +4,10 @@ #include #include +uint qHash(const MessageId &item) { + return qHash(item.source) ^ qHash(item.address); +} + DBCManager::DBCManager(QObject *parent) : QObject(parent) {} DBCManager::~DBCManager() {} @@ -56,29 +60,27 @@ QString DBCManager::generateDBC() { return dbc_string; } -void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) { - auto [_, address] = parseId(id); - auto &m = msgs[address]; +void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size) { + auto &m = msgs[id.address]; m.name = name; m.size = size; - emit msgUpdated(address); + emit msgUpdated(id.address); } -void DBCManager::removeMsg(const QString &id) { - uint32_t address = parseId(id).second; - msgs.erase(address); - emit msgRemoved(address); +void DBCManager::removeMsg(const MessageId &id) { + msgs.erase(id.address); + emit msgRemoved(id.address); } -void DBCManager::addSignal(const QString &id, const Signal &sig) { - if (auto m = const_cast(msg(id))) { +void DBCManager::addSignal(const MessageId &id, const Signal &sig) { + if (auto m = const_cast(msg(id.address))) { auto &s = m->sigs[sig.name.c_str()]; s = sig; - emit signalAdded(parseId(id).second, &s); + emit signalAdded(id.address, &s); } } -void DBCManager::updateSignal(const QString &id, const QString &sig_name, const Signal &sig) { +void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const Signal &sig) { if (auto m = const_cast(msg(id))) { // change key name QString new_name = QString::fromStdString(sig.name); @@ -91,7 +93,7 @@ void DBCManager::updateSignal(const QString &id, const QString &sig_name, const } } -void DBCManager::removeSignal(const QString &id, const QString &sig_name) { +void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { if (auto m = const_cast(msg(id))) { auto it = m->sigs.find(sig_name); if (it != m->sigs.end()) { @@ -101,12 +103,6 @@ void DBCManager::removeSignal(const QString &id, const QString &sig_name) { } } -std::pair DBCManager::parseId(const QString &id) { - const auto list = id.split(':'); - if (list.size() != 2) return {0, 0}; - return {list[0].toInt(), list[1].toUInt(nullptr, 16)}; -} - DBCManager *dbc() { static DBCManager dbc_manager(nullptr); return &dbc_manager; diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index 41471a6169..b766c837b6 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -5,6 +5,35 @@ #include #include "opendbc/can/common_dbc.h" +struct MessageId { + uint8_t source; + uint32_t address; + + QString toString() const { + return QString("%1:%2").arg(source).arg(address, 1, 16); + } + + bool operator==(const MessageId &other) const { + return source == other.source && address == other.address; + } + + bool operator!=(const MessageId &other) const { + return !(*this == other); + } + + bool operator<(const MessageId &other) const { + return std::pair{source, address} < std::pair{other.source, other.address}; + } + + bool operator>(const MessageId &other) const { + return std::pair{source, address} > std::pair{other.source, other.address}; + } +}; + +Q_DECLARE_METATYPE(MessageId); + +uint qHash(const MessageId &item); + struct DBCMsg { QString name; uint32_t size; @@ -24,17 +53,16 @@ public: void open(const QString &dbc_file_name); bool open(const QString &name, const QString &content, QString *error = nullptr); QString generateDBC(); - void addSignal(const QString &id, const Signal &sig); - void updateSignal(const QString &id, const QString &sig_name, const Signal &sig); - void removeSignal(const QString &id, const QString &sig_name); + void addSignal(const MessageId &id, const Signal &sig); + void updateSignal(const MessageId &id, const QString &sig_name, const Signal &sig); + void removeSignal(const MessageId &id, const QString &sig_name); - static std::pair parseId(const QString &id); inline static std::vector allDBCNames() { return get_dbc_names(); } inline QString name() const { return dbc ? dbc->name.c_str() : ""; } - void updateMsg(const QString &id, const QString &name, uint32_t size); - void removeMsg(const QString &id); + void updateMsg(const MessageId &id, const QString &name, uint32_t size); + void removeMsg(const MessageId &id); inline const std::map &messages() const { return msgs; } - inline const DBCMsg *msg(const QString &id) const { return msg(parseId(id).second); } + inline const DBCMsg *msg(const MessageId &id) const { return msg(id.address); } inline const DBCMsg *msg(uint32_t address) const { auto it = msgs.find(address); return it != msgs.end() ? &it->second : nullptr; @@ -54,6 +82,8 @@ private: std::map msgs; }; +const QString UNTITLED = "untitled"; + // TODO: Add helper function in dbc.h double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig); bool operator==(const Signal &l, const Signal &r); @@ -63,7 +93,7 @@ int bigEndianBitIndex(int index); void updateSigSizeParamsFromRange(Signal &s, int start_bit, int size); std::pair getSignalRange(const Signal *s); DBCManager *dbc(); -inline QString msgName(const QString &id, const char *def = "untitled") { +inline QString msgName(const MessageId &id) { auto msg = dbc()->msg(id); - return msg ? msg->name : def; + return msg ? msg->name : UNTITLED; } diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 25d9b3b9ca..95ef975dd1 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -1,6 +1,5 @@ #include "tools/cabana/detailwidget.h" -#include #include #include #include @@ -75,6 +74,9 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart QObject::connect(binary_view, &BinaryView::addSignal, signal_view->model, &SignalModel::addSignal); QObject::connect(binary_view, &BinaryView::signalHovered, signal_view, &SignalView::signalHovered); QObject::connect(binary_view, &BinaryView::signalClicked, signal_view, &SignalView::expandSignal); + QObject::connect(binary_view, &BinaryView::editSignal, signal_view->model, &SignalModel::saveSignal); + QObject::connect(binary_view, &BinaryView::removeSignal, signal_view->model, &SignalModel::removeSignal); + QObject::connect(binary_view, &BinaryView::showChart, charts, &ChartsWidget::showChart); QObject::connect(signal_view, &SignalView::showChart, charts, &ChartsWidget::showChart); QObject::connect(signal_view, &SignalView::highlight, binary_view, &BinaryView::highlight); QObject::connect(tab_widget, &QTabWidget::currentChanged, [this]() { updateState(); }); @@ -83,11 +85,15 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &DetailWidget::refresh); QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu); QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) { - if (index != -1 && tabbar->tabText(index) != msg_id) { - setMessage(tabbar->tabText(index)); + if (index != -1) { + setMessage(tabbar_ids[index]); } }); - QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab); + QObject::connect(tabbar, &QTabBar::tabCloseRequested, [this](int index) { + tabbar_ids.removeAt(index); + tabbar->removeTab(index); + assert(tabbar_ids.size() == tabbar->count()); + }); QObject::connect(charts, &ChartsWidget::seriesChanged, signal_view, &SignalView::updateChartState); } @@ -97,38 +103,45 @@ void DetailWidget::showTabBarContextMenu(const QPoint &pt) { QMenu menu(this); menu.addAction(tr("Close Other Tabs")); if (menu.exec(tabbar->mapToGlobal(pt))) { + tabbar_ids.move(index, 0); tabbar->moveTab(index, 0); tabbar->setCurrentIndex(0); - while (tabbar->count() > 1) + while (tabbar->count() > 1) { + tabbar_ids.removeAt(1); tabbar->removeTab(1); + } + assert(tabbar_ids.size() == tabbar->count()); } } } void DetailWidget::removeAll() { - msg_id = ""; + msg_id = std::nullopt; tabbar->blockSignals(true); while (tabbar->count() > 0) { tabbar->removeTab(0); } + tabbar_ids.clear(); tabbar->blockSignals(false); stacked_layout->setCurrentIndex(0); } -void DetailWidget::setMessage(const QString &message_id) { +void DetailWidget::setMessage(const MessageId &message_id) { msg_id = message_id; - int index = tabbar->count() - 1; - for (/**/; index >= 0 && tabbar->tabText(index) != msg_id; --index) { /**/ } + int index = tabbar_ids.indexOf(*msg_id); + if (index == -1) { - index = tabbar->addTab(message_id); + tabbar_ids.append(*msg_id); + index = tabbar->addTab(message_id.toString()); tabbar->setTabToolTip(index, msgName(message_id)); } + assert(tabbar->count() == tabbar_ids.size()); setUpdatesEnabled(false); - signal_view->setMessage(msg_id); - binary_view->setMessage(msg_id); - history_log->setMessage(msg_id); + signal_view->setMessage(*msg_id); + binary_view->setMessage(*msg_id); + history_log->setMessage(*msg_id); stacked_layout->setCurrentIndex(1); tabbar->setCurrentIndex(index); @@ -139,12 +152,12 @@ void DetailWidget::setMessage(const QString &message_id) { } void DetailWidget::refresh() { - if (msg_id.isEmpty()) return; + if (!msg_id) return; QStringList warnings; - const DBCMsg *msg = dbc()->msg(msg_id); + const DBCMsg *msg = dbc()->msg(*msg_id); if (msg) { - if (msg->size != can->lastMessage(msg_id).dat.size()) { + if (msg->size != can->lastMessage(*msg_id).dat.size()) { warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size)); } for (auto s : binary_view->getOverlappingSignals()) { @@ -154,7 +167,7 @@ void DetailWidget::refresh() { warnings.push_back(tr("Drag-Select in binary view to create new signal.")); } remove_msg_act->setEnabled(msg != nullptr); - name_label->setText(msgName(msg_id)); + name_label->setText(msgName(*msg_id)); if (!warnings.isEmpty()) { warning_label->setText(warnings.join('\n')); @@ -163,9 +176,9 @@ void DetailWidget::refresh() { warning_widget->setVisible(!warnings.isEmpty()); } -void DetailWidget::updateState(const QHash *msgs) { +void DetailWidget::updateState(const QHash *msgs) { time_label->setText(QString::number(can->currentSec(), 'f', 3)); - if (msg_id.isEmpty() || (msgs && !msgs->contains(msg_id))) + if (!msg_id || (msgs && !msgs->contains(*msg_id))) return; if (tab_widget->currentIndex() == 0) @@ -175,26 +188,28 @@ void DetailWidget::updateState(const QHash *msgs) { } void DetailWidget::editMsg() { - QString id = msg_id; + MessageId id = *msg_id; auto msg = dbc()->msg(id); int size = msg ? msg->size : can->lastMessage(id).dat.size(); EditMessageDialog dlg(id, msgName(id), size, this); if (dlg.exec()) { - UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text(), dlg.size_spin->value())); + UndoStack::push(new EditMsgCommand(*msg_id, dlg.name_edit->text(), dlg.size_spin->value())); } } void DetailWidget::removeMsg() { - UndoStack::push(new RemoveMsgCommand(msg_id)); + UndoStack::push(new RemoveMsgCommand(*msg_id)); } // EditMessageDialog -EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Edit message")); +EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent) + : original_name(title), QDialog(parent) { + setWindowTitle(tr("Edit message: %1").arg(msg_id.toString())); QFormLayout *form_layout = new QFormLayout(this); - form_layout->addRow("ID", new QLabel(msg_id)); + form_layout->addRow("", error_label = new QLabel); + error_label->setVisible(false); name_edit = new QLineEdit(title, this); name_edit->setValidator(new NameValidator(name_edit)); form_layout->addRow(tr("Name"), name_edit); @@ -205,12 +220,28 @@ EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title size_spin->setValue(size); form_layout->addRow(tr("Size"), size_spin); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - form_layout->addRow(buttonBox); + btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + btn_box->button(QDialogButtonBox::Ok)->setEnabled(false); + form_layout->addRow(btn_box); + setFixedWidth(parent->width() * 0.9); + connect(name_edit, &QLineEdit::textEdited, this, &EditMessageDialog::validateName); + connect(btn_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); +} - connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +void EditMessageDialog::validateName(const QString &text) { + bool valid = false; + error_label->setVisible(false); + if (!text.isEmpty() && text != original_name && text.compare(UNTITLED, Qt::CaseInsensitive) != 0) { + valid = std::none_of(dbc()->messages().begin(), dbc()->messages().end(), + [&text](auto &m) { return m.second.name == text; }); + if (!valid) { + error_label->setText(tr("Name already exists")); + error_label->setVisible(true); + } + } + btn_box->button(QDialogButtonBox::Ok)->setEnabled(valid); } // WelcomeWidget @@ -233,11 +264,12 @@ WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) { return hlayout; }; - auto lb = new QLabel(tr("<-Select a message to to view details")); + auto lb = new QLabel(tr("<-Select a message to view details")); lb->setAlignment(Qt::AlignHCenter); main_layout->addWidget(lb); main_layout->addLayout(newShortcutRow("Pause", "Space")); - main_layout->addLayout(newShortcutRow("Help", "Alt + H")); + main_layout->addLayout(newShortcutRow("Help", "F1")); + main_layout->addLayout(newShortcutRow("WhatsThis", "Shift+F1")); main_layout->addStretch(0); setStyleSheet("QLabel{color:darkGray;}"); diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 0983f49924..949a8c9b8d 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -13,9 +14,13 @@ class EditMessageDialog : public QDialog { public: - EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent); + EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent); + void validateName(const QString &text); + QString original_name; + QDialogButtonBox *btn_box; QLineEdit *name_edit; + QLabel *error_label; QSpinBox *size_spin; }; @@ -29,7 +34,7 @@ class DetailWidget : public QWidget { public: DetailWidget(ChartsWidget *charts, QWidget *parent); - void setMessage(const QString &message_id); + void setMessage(const MessageId &message_id); void refresh(); void removeAll(); QSize minimumSizeHint() const override { return binary_view->minimumSizeHint(); } @@ -38,13 +43,14 @@ private: void showTabBarContextMenu(const QPoint &pt); void editMsg(); void removeMsg(); - void updateState(const QHash * msgs = nullptr); + void updateState(const QHash * msgs = nullptr); - QString msg_id; + std::optional msg_id; QLabel *time_label, *warning_icon, *warning_label; ElidedLabel *name_label; QWidget *warning_widget; QTabBar *tabbar; + QList tabbar_ids; QTabWidget *tab_widget; QAction *remove_msg_act; LogsWidget *history_log; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index e4ad99758b..87968d495c 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -17,19 +17,19 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { } return show_signals ? QString::number(m.sig_values[index.column() - 1]) : toHex(m.data); } else if (role == Qt::UserRole && index.column() == 1 && !show_signals) { - return ChangeTracker::toVariantList(m.colors); + return QVariant::fromValue(m.colors); } return {}; } -void HistoryLogModel::setMessage(const QString &message_id) { +void HistoryLogModel::setMessage(const MessageId &message_id) { msg_id = message_id; } void HistoryLogModel::refresh() { beginResetModel(); sigs.clear(); - if (auto dbc_msg = dbc()->msg(msg_id)) { + if (auto dbc_msg = dbc()->msg(*msg_id)) { sigs = dbc_msg->getSignals(); } last_fetch_time = 0; @@ -78,8 +78,8 @@ void HistoryLogModel::setFilter(int sig_idx, const QString &value, std::function } void HistoryLogModel::updateState() { - if (!msg_id.isEmpty()) { - uint64_t current_time = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9 + 1; + if (msg_id) { + uint64_t current_time = (can->lastMessage(*msg_id).ts + can->routeStartTime()) * 1e9 + 1; auto new_msgs = dynamic_mode ? fetchData(current_time, last_fetch_time) : fetchData(0); if (!new_msgs.empty()) { beginInsertRows({}, 0, new_msgs.size() - 1); @@ -106,12 +106,11 @@ void HistoryLogModel::fetchMore(const QModelIndex &parent) { template std::deque HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) { std::deque msgs; - const auto [src, address] = DBCManager::parseId(msg_id); QVector values(sigs.size()); for (auto it = first; it != last && (*it)->mono_time > min_time; ++it) { if ((*it)->which == cereal::Event::Which::CAN) { for (const auto &c : (*it)->event.getCan()) { - if (address == c.getAddress() && src == c.getSrc()) { + if (msg_id->address == c.getAddress() && msg_id->source == c.getSrc()) { const auto dat = c.getDat(); for (int i = 0; i < sigs.size(); ++i) { values[i] = get_raw_value((uint8_t *)dat.begin(), dat.size(), *(sigs[i])); @@ -136,7 +135,7 @@ template std::deque HistoryLogModel::fetchData<>(std:: std::deque HistoryLogModel::fetchData(uint64_t from_time, uint64_t min_time) { auto events = can->events(); - const auto freq = can->lastMessage(msg_id).freq; + const auto freq = can->lastMessage(*msg_id).freq; const bool update_colors = !display_signals_mode || sigs.empty(); if (dynamic_mode) { @@ -241,13 +240,13 @@ LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) { QObject::connect(can, &AbstractStream::eventsMerged, model, &HistoryLogModel::segmentsMerged); } -void LogsWidget::setMessage(const QString &message_id) { +void LogsWidget::setMessage(const MessageId &message_id) { model->setMessage(message_id); refresh(); } void LogsWidget::refresh() { - if (model->msg_id.isEmpty()) return; + if (!model->msg_id) return; model->setFilter(0, "", nullptr); model->refresh(); diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index 2458fc1c31..00a8f73836 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -1,6 +1,8 @@ #pragma once #include +#include + #include #include #include @@ -22,7 +24,7 @@ class HistoryLogModel : public QAbstractTableModel { public: HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {} - void setMessage(const QString &message_id); + void setMessage(const MessageId &message_id); void updateState(); void setFilter(int sig_idx, const QString &value, std::function cmp); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; @@ -52,7 +54,7 @@ public: std::deque fetchData(InputIt first, InputIt last, uint64_t min_time); std::deque fetchData(uint64_t from_time, uint64_t min_time = 0); - QString msg_id; + std::optional msg_id; ChangeTracker hex_colors; bool has_more_data = true; const int batch_size = 50; @@ -71,7 +73,7 @@ class LogsWidget : public QWidget { public: LogsWidget(QWidget *parent); - void setMessage(const QString &message_id); + void setMessage(const MessageId &message_id); void updateState() {if (dynamic_mode->isChecked()) model->updateState(); } void showEvent(QShowEvent *event) override { if (dynamic_mode->isChecked()) model->refresh(); } diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index bce6d313c8..07d37df4e4 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -9,7 +9,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -20,7 +22,7 @@ static MainWindow *main_win = nullptr; void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl; - if (main_win) emit main_win->showMessage(msg, 0); + if (main_win) emit main_win->showMessage(msg, 2000); } MainWindow::MainWindow() : QMainWindow() { @@ -43,7 +45,7 @@ MainWindow::MainWindow() : QMainWindow() { qRegisterMetaType("ReplyMsgType"); installMessageHandler([this](ReplyMsgType type, const std::string msg) { // use queued connection to recv the log messages from replay. - emit showMessage(QString::fromStdString(msg), 3000); + emit showMessage(QString::fromStdString(msg), 2000); }); installDownloadProgressHandler([this](uint64_t cur, uint64_t total, bool success) { emit updateProgressBar(cur, total, success); @@ -125,6 +127,7 @@ void MainWindow::createActions() { tools_menu->addAction(tr("Find &Similar Bits"), this, &MainWindow::findSimilarBits); QMenu *help_menu = menuBar()->addMenu(tr("&Help")); + help_menu->addAction(tr("Help"), this, &MainWindow::onlineHelp)->setShortcuts(QKeySequence::HelpContents); help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); } @@ -173,6 +176,7 @@ void MainWindow::createStatusBar() { progress_bar->setTextVisible(true); progress_bar->setFixedSize({230, 16}); progress_bar->setVisible(false); + statusBar()->addWidget(new QLabel(tr("For Help,Press F1"))); statusBar()->addPermanentWidget(progress_bar); } @@ -422,3 +426,61 @@ void MainWindow::findSimilarBits() { QObject::connect(dlg, &FindSimilarBitsDlg::openMessage, messages_widget, &MessagesWidget::selectMessage); dlg->show(); } + +void MainWindow::onlineHelp() { + if (auto help = findChild()) { + help->close(); + } else { + help = new HelpOverlay(this); + help->setGeometry(rect()); + help->show(); + help->raise(); + } +} + +// HelpOverlay +HelpOverlay::HelpOverlay(MainWindow *parent) : QWidget(parent) { + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_TranslucentBackground, true); + setAttribute(Qt::WA_DeleteOnClose); + parent->installEventFilter(this); +} + +void HelpOverlay::paintEvent(QPaintEvent *event) { + QPainter painter(this); + painter.fillRect(rect(), QColor(0, 0, 0, 50)); + MainWindow *parent = (MainWindow *)parentWidget(); + drawHelpForWidget(painter, parent->findChild()); + drawHelpForWidget(painter, parent->findChild()); + drawHelpForWidget(painter, parent->findChild()); + drawHelpForWidget(painter, parent->findChild()); + drawHelpForWidget(painter, parent->findChild()); +} + +void HelpOverlay::drawHelpForWidget(QPainter &painter, QWidget *w) { + if (w && w->isVisible() && !w->whatsThis().isEmpty()) { + QPoint pt = mapFromGlobal(w->mapToGlobal(w->rect().center())); + if (rect().contains(pt)) { + QTextDocument document; + document.setHtml(w->whatsThis()); + QSize doc_size = document.size().toSize(); + QPoint topleft = {pt.x() - doc_size.width() / 2, pt.y() - doc_size.height() / 2}; + painter.translate(topleft); + painter.fillRect(QRect{{0, 0}, doc_size}, palette().toolTipBase()); + document.drawContents(&painter); + painter.translate(-topleft); + } + } +} + +bool HelpOverlay::eventFilter(QObject *obj, QEvent *event) { + if (obj == parentWidget() && event->type() == QEvent::Resize) { + QResizeEvent *resize_event = (QResizeEvent *)(event); + setGeometry(QRect{QPoint(0, 0), resize_event->size()}); + } + return false; +} + +void HelpOverlay::mouseReleaseEvent(QMouseEvent *event) { + close(); +} diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 5e627df58b..e7a39adab7 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -54,6 +54,7 @@ protected: void setOption(); void findSimilarBits(); void undoStackCleanChanged(bool clean); + void onlineHelp(); VideoWidget *video_widget = nullptr; QDockWidget *video_dock; @@ -69,4 +70,17 @@ protected: enum { MAX_RECENT_FILES = 15 }; QAction *recent_files_acts[MAX_RECENT_FILES] = {}; QMenu *open_recent_menu = nullptr; + friend class OnlineHelp; +}; + +class HelpOverlay : public QWidget { + Q_OBJECT +public: + HelpOverlay(MainWindow *parent); + +protected: + void drawHelpForWidget(QPainter &painter, QWidget *w); + void paintEvent(QPaintEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + bool eventFilter(QObject *obj, QEvent *event) override; }; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 8279d08a5c..81ebc6af20 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -46,12 +46,12 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::sortMessages); QObject::connect(dbc(), &DBCManager::msgUpdated, model, &MessageListModel::sortMessages); QObject::connect(dbc(), &DBCManager::msgRemoved, model, &MessageListModel::sortMessages); - QObject::connect(model, &MessageListModel::modelReset, [this]() { selectMessage(current_msg_id); }); + QObject::connect(model, &MessageListModel::modelReset, [this]() { selectMessage(*current_msg_id); }); QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { if (current.isValid() && current.row() < model->msgs.size()) { - if (model->msgs[current.row()] != current_msg_id) { + if (model->msgs[current.row()] != *current_msg_id) { current_msg_id = model->msgs[current.row()]; - emit msgSelectionChanged(current_msg_id); + emit msgSelectionChanged(*current_msg_id); } } }); @@ -65,9 +65,18 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { }); updateSuppressedButtons(); + + setWhatsThis(tr(R"( + Message View
+ + Byte color:
+ constant changing
+ increasing
+ decreasing
+ )")); } -void MessagesWidget::selectMessage(const QString &msg_id) { +void MessagesWidget::selectMessage(const MessageId &msg_id) { if (int row = model->msgs.indexOf(msg_id); row != -1) { table_widget->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); } @@ -86,7 +95,7 @@ void MessagesWidget::updateSuppressedButtons() { void MessagesWidget::reset() { model->reset(); filter->clear(); - current_msg_id = ""; + current_msg_id = std::nullopt; updateSuppressedButtons(); } @@ -106,31 +115,29 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { switch (index.column()) { case 0: return msgName(id); - case 1: return id; + case 1: return id.toString(); // TODO: put source and address in separate columns case 2: return can_data.freq; case 3: return can_data.count; case 4: return toHex(can_data.dat); } } else if (role == Qt::UserRole && index.column() == 4) { - QList colors; - colors.reserve(can_data.dat.size()); - for (int i = 0; i < can_data.dat.size(); i++){ - if (suppressed_bytes.contains({id, i})) { - colors.append(QColor(255, 255, 255, 0)); - } else { - colors.append(i < can_data.colors.size() ? can_data.colors[i] : QColor(255, 255, 255, 0)); + QVector colors = can_data.colors; + if (!suppressed_bytes.empty()) { + for (int i = 0; i < colors.size(); i++) { + if (suppressed_bytes.contains({id, i})) { + colors[i] = QColor(255, 255, 255, 0); + } } } - return colors; - + return QVariant::fromValue(colors); } return {}; } void MessageListModel::setFilterString(const QString &string) { - auto contains = [](const QString &id, const QString &txt) { + auto contains = [](const MessageId &id, const QString &txt) { auto cs = Qt::CaseInsensitive; - if (id.contains(txt, cs) || msgName(id).contains(txt, cs)) return true; + if (id.toString().contains(txt, cs) || msgName(id).contains(txt, cs)) return true; // Search by signal name if (const auto msg = dbc()->msg(id)) { for (auto &signal : msg->getSignals()) { @@ -160,9 +167,7 @@ void MessageListModel::sortMessages() { }); } else if (sort_column == 1) { std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { - auto ll = DBCManager::parseId(l); - auto rr = DBCManager::parseId(r); - return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; + return sort_order == Qt::AscendingOrder ? l < r : l > r; }); } else if (sort_column == 2) { std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { @@ -180,7 +185,7 @@ void MessageListModel::sortMessages() { endResetModel(); } -void MessageListModel::msgsReceived(const QHash *new_msgs) { +void MessageListModel::msgsReceived(const QHash *new_msgs) { int prev_row_count = msgs.size(); if (filter_str.isEmpty() && msgs.size() != can->can_msgs.size()) { msgs = can->can_msgs.keys(); diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 81ee36cd6f..562069c3ae 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -20,13 +22,13 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override { return msgs.size(); } void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; void setFilterString(const QString &string); - void msgsReceived(const QHash *new_msgs = nullptr); + void msgsReceived(const QHash *new_msgs = nullptr); void sortMessages(); void suppress(); void clearSuppress(); void reset(); - QStringList msgs; - QSet> suppressed_bytes; + QList msgs; + QSet> suppressed_bytes; private: QString filter_str; @@ -39,18 +41,18 @@ class MessagesWidget : public QWidget { public: MessagesWidget(QWidget *parent); - void selectMessage(const QString &message_id); + void selectMessage(const MessageId &message_id); QByteArray saveHeaderState() const { return table_widget->horizontalHeader()->saveState(); } bool restoreHeaderState(const QByteArray &state) const { return table_widget->horizontalHeader()->restoreState(state); } void updateSuppressedButtons(); void reset(); signals: - void msgSelectionChanged(const QString &message_id); + void msgSelectionChanged(const MessageId &message_id); protected: QTableView *table_widget; - QString current_msg_id; + std::optional current_msg_id; QLineEdit *filter; MessageListModel *model; QPushButton *suppress_add; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 0499b1be8a..e2be5c85d0 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -31,7 +31,7 @@ void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const Sign } } -void SignalModel::setMessage(const QString &id) { +void SignalModel::setMessage(const MessageId &id) { msg_id = id; filter_str = ""; refresh(); @@ -56,7 +56,7 @@ void SignalModel::refresh() { endResetModel(); } -void SignalModel::updateState(const QHash *msgs) { +void SignalModel::updateState(const QHash *msgs) { if (!msgs || (msgs->contains(msg_id))) { auto &dat = can->lastMessage(msg_id).dat; int row = 0; @@ -230,13 +230,13 @@ void SignalModel::removeSignal(const Signal *sig) { } void SignalModel::handleMsgChanged(uint32_t address) { - if (address == DBCManager::parseId(msg_id).second) { + if (address == msg_id.address) { refresh(); } } void SignalModel::handleSignalAdded(uint32_t address, const Signal *sig) { - if (address == DBCManager::parseId(msg_id).second) { + if (address == msg_id.address) { int i = 0; for (; i < root->children.size(); ++i) { if (sig->start_bit < root->children[i]->sig->start_bit) break; @@ -365,9 +365,14 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &SignalView::rowsChanged); QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged); QObject::connect(dbc(), &DBCManager::signalAdded, [this](uint32_t address, const Signal *sig) { expandSignal(sig); }); + + setWhatsThis(tr(R"( + Signal view
+ + )")); } -void SignalView::setMessage(const QString &id) { +void SignalView::setMessage(const MessageId &id) { msg_id = id; filter_edit->clear(); model->setMessage(id); diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index c0b649209a..7e5015f707 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -37,7 +37,7 @@ public: QModelIndex parent(const QModelIndex &index) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - void setMessage(const QString &id); + void setMessage(const MessageId &id); void setFilter(const QString &txt); void addSignal(int start_bit, int size, bool little_endian); bool saveSignal(const Signal *origin_s, Signal &s); @@ -54,9 +54,9 @@ private: void handleSignalRemoved(const Signal *sig); void handleMsgChanged(uint32_t address); void refresh(); - void updateState(const QHash *msgs); + void updateState(const QHash *msgs); - QString msg_id; + MessageId msg_id; QString filter_str; std::unique_ptr root; friend class SignalView; @@ -76,7 +76,7 @@ class SignalView : public QWidget { public: SignalView(ChartsWidget *charts, QWidget *parent); - void setMessage(const QString &id); + void setMessage(const MessageId &id); void signalHovered(const Signal *sig); void updateChartState(); void expandSignal(const Signal *sig); @@ -85,13 +85,13 @@ public: signals: void highlight(const Signal *sig); - void showChart(const QString &name, const Signal *sig, bool show, bool merge); + void showChart(const MessageId &id, const Signal *sig, bool show, bool merge); private: void rowsChanged(); void leaveEvent(QEvent *event); - QString msg_id; + MessageId msg_id; QTreeView *tree; QLineEdit *filter_edit; ChartsWidget *charts; diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index 13b154a7ea..8fdfbd5c1b 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -4,12 +4,12 @@ AbstractStream *can = nullptr; AbstractStream::AbstractStream(QObject *parent, bool is_live_streaming) : is_live_streaming(is_live_streaming), QObject(parent) { can = this; - new_msgs = std::make_unique>(); + new_msgs = std::make_unique>(); QObject::connect(this, &AbstractStream::received, this, &AbstractStream::process, Qt::QueuedConnection); QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo); } -void AbstractStream::process(QHash *messages) { +void AbstractStream::process(QHash *messages) { for (auto it = messages->begin(); it != messages->end(); ++it) { can_msgs[it.key()] = it.value(); } @@ -25,7 +25,7 @@ bool AbstractStream::updateEvent(const Event *event) { if (event->which == cereal::Event::Which::CAN) { double current_sec = event->mono_time / 1e9 - routeStartTime(); for (const auto &c : event->event.getCan()) { - QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16); + MessageId id = {.source = c.getSrc(), .address = c.getAddress()}; CanData &data = (*new_msgs)[id]; data.ts = current_sec; data.dat = QByteArray((char *)c.getDat().begin(), c.getDat().size()); @@ -44,29 +44,30 @@ bool AbstractStream::updateEvent(const Event *event) { prev_update_ts = ts; // use pointer to avoid data copy in queued connection. emit received(new_msgs.release()); - new_msgs.reset(new QHash); + new_msgs.reset(new QHash); new_msgs->reserve(100); } } return true; } -const CanData &AbstractStream::lastMessage(const QString &id) { +const CanData &AbstractStream::lastMessage(const MessageId &id) { static CanData empty_data; auto it = can_msgs.find(id); return it != can_msgs.end() ? it.value() : empty_data; } void AbstractStream::updateLastMsgsTo(double sec) { - QHash, CanData> last_msgs; // Much faster than QHash + QHash last_msgs; last_msgs.reserve(can_msgs.size()); double route_start_time = routeStartTime(); uint64_t last_ts = (sec + route_start_time) * 1e9; - auto last = std::upper_bound(events()->rbegin(), events()->rend(), last_ts, [](uint64_t ts, auto &e) { return e->mono_time < ts; }); - for (auto it = last; it != events()->rend(); ++it) { + auto evs = events(); + auto last = std::upper_bound(evs->rbegin(), evs->rend(), last_ts, [](uint64_t ts, auto &e) { return e->mono_time < ts; }); + for (auto it = last; it != evs->rend(); ++it) { if ((*it)->which == cereal::Event::Which::CAN) { for (const auto &c : (*it)->event.getCan()) { - auto &m = last_msgs[{c.getSrc(), c.getAddress()}]; + auto &m = last_msgs[{.source = c.getSrc(), .address = c.getAddress()}]; if (++m.count == 1) { m.ts = ((*it)->mono_time / 1e9) - route_start_time; m.dat = QByteArray((char *)c.getDat().begin(), c.getDat().size()); @@ -87,9 +88,8 @@ void AbstractStream::updateLastMsgsTo(double sec) { counters.clear(); can_msgs.clear(); for (auto it = last_msgs.cbegin(); it != last_msgs.cend(); ++it) { - QString msg_id = QString("%1:%2").arg(it.key().first).arg(it.key().second, 1, 16); - can_msgs[msg_id] = it.value(); - counters[msg_id] = it.value().count; + can_msgs[it.key()] = it.value(); + counters[it.key()] = it.value().count; } emit updated(); emit msgsReceived(&can_msgs); diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 8c10d959cb..e582682971 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -5,6 +5,7 @@ #include #include +#include "tools/cabana/dbcmanager.h" #include "tools/cabana/settings.h" #include "tools/cabana/util.h" #include "tools/replay/replay.h" @@ -33,7 +34,7 @@ public: virtual double routeStartTime() const { return 0; } virtual double currentSec() const = 0; virtual QDateTime currentDateTime() const { return {}; } - virtual const CanData &lastMessage(const QString &id); + virtual const CanData &lastMessage(const MessageId &id); virtual VisionStreamType visionStreamType() const { return VISION_STREAM_ROAD; } virtual const Route *route() const { return nullptr; } virtual const std::vector *events() const = 0; @@ -49,22 +50,22 @@ signals: void streamStarted(); void eventsMerged(); void updated(); - void msgsReceived(const QHash *); - void received(QHash *); + void msgsReceived(const QHash *); + void received(QHash *); public: - QHash can_msgs; + QHash can_msgs; protected: - void process(QHash *); + void process(QHash *); bool updateEvent(const Event *event); void updateLastMsgsTo(double sec); bool is_live_streaming = false; std::atomic processing = false; - QHash counters; - std::unique_ptr> new_msgs; - QHash change_trackers; + QHash counters; + std::unique_ptr> new_msgs; + QHash change_trackers; }; // A global pointer referring to the unique AbstractStream object diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc index 63d01b152d..ffb0e54b0e 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -20,10 +20,10 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi bus_combo = new QComboBox(this); QSet bus_set; for (auto it = can->can_msgs.begin(); it != can->can_msgs.end(); ++it) { - bus_set << DBCManager::parseId(it.key()).first; + bus_set << it.key().source; } for (uint8_t bus : bus_set) { - bus_combo->addItem(QString::number(bus)); + bus_combo->addItem(QString::number(bus), bus); } bus_combo->model()->sort(0); bus_combo->setCurrentIndex(0); @@ -69,9 +69,11 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi setMinimumSize({700, 500}); QObject::connect(search_btn, &QPushButton::clicked, this, &FindSimilarBitsDlg::find); + QObject::connect(table, &QTableWidget::doubleClicked, [this](const QModelIndex &index) { if (index.isValid()) { - emit openMessage(bus_combo->currentText() + ":" + table->item(index.row(), 0)->text()); + MessageId msg_id = {.source = (uint8_t)bus_combo->currentData().toUInt(), .address = table->item(index.row(), 0)->text().toUInt(0, 16)}; + emit openMessage(msg_id); } }); } diff --git a/tools/cabana/tools/findsimilarbits.h b/tools/cabana/tools/findsimilarbits.h index 30d78f0dea..53d7806a8f 100644 --- a/tools/cabana/tools/findsimilarbits.h +++ b/tools/cabana/tools/findsimilarbits.h @@ -6,6 +6,8 @@ #include #include +#include "tools/cabana/dbcmanager.h" + class FindSimilarBitsDlg : public QDialog { Q_OBJECT @@ -13,7 +15,7 @@ public: FindSimilarBitsDlg(QWidget *parent); signals: - void openMessage(const QString &msg_id); + void openMessage(const MessageId &msg_id); private: struct mismatched_struct { diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index 454dd29b87..9843a3f00c 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -66,13 +66,6 @@ void ChangeTracker::clear() { colors.clear(); } -QList ChangeTracker::toVariantList(const QVector &colors) { - QList ret; - ret.reserve(colors.size()); - for (auto &c : colors) ret.append(c); - return ret; -} - // MessageBytesDelegate MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegate(parent) { @@ -89,12 +82,8 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem & return; } - if ((option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active)) { - painter->setPen(option.palette.color(QPalette::HighlightedText)); - } else { - painter->setPen(option.palette.color(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); QRect space = painter->boundingRect(opt.rect, opt.displayAlignment, " "); QRect pos = painter->boundingRect(opt.rect, opt.displayAlignment, "00"); @@ -103,15 +92,13 @@ void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem & int m = space.width() / 2; const QMargins margins(m, m, m, m); - QList colors = index.data(Qt::UserRole).toList(); - int i = 0; - for (auto &byte : byte_list) { + auto colors = index.data(Qt::UserRole).value>(); + for (int i = 0; i < byte_list.size(); ++i) { if (i < colors.size()) { - painter->fillRect(pos.marginsAdded(margins), colors[i].value()); + painter->fillRect(pos.marginsAdded(margins), colors[i]); } - painter->drawText(pos, opt.displayAlignment, byte); + painter->drawText(pos, opt.displayAlignment, byte_list[i]); pos.moveLeft(pos.right() + space.width()); - i++; } } diff --git a/tools/cabana/util.h b/tools/cabana/util.h index 8ec4cda90c..eb5203fb0b 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -14,7 +14,6 @@ class ChangeTracker { public: void compute(const QByteArray &dat, double ts, uint32_t freq); - static QList toVariantList(const QVector &colors); void clear(); QVector last_change_t; diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index cd3dc0b516..f77f7c306d 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -53,6 +53,13 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState); QObject::connect(can, &AbstractStream::resume, this, &VideoWidget::updatePlayBtnState); updatePlayBtnState(); + + setWhatsThis(tr(R"( + Video
+ + Shortcuts:
+ Pause/Resume: space
+ )")); } QWidget *VideoWidget::createCameraWidget() { diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index f0694078e2..7bb12badb2 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -211,7 +211,10 @@ def fake_driver_monitoring(exit_event: threading.Event): while not exit_event.is_set(): # dmonitoringmodeld output dat = messaging.new_message('driverStateV2') + dat.driverStateV2.leftDriverData.faceOrientation = [0., 0., 0.] dat.driverStateV2.leftDriverData.faceProb = 1.0 + dat.driverStateV2.rightDriverData.faceOrientation = [0., 0., 0.] + dat.driverStateV2.rightDriverData.faceProb = 1.0 pm.send('driverStateV2', dat) # dmonitoringd output