Merge remote-tracking branch 'upstream/master' into hkg-fuzzy

pull/28386/head
Shane Smiskol 3 years ago
commit 0c599a8e84
  1. 3
      .github/workflows/selfdrive_tests.yaml
  2. 2
      .pre-commit-config.yaml
  3. 3
      RELEASES.md
  4. 2
      cereal
  5. 1
      common/params.cc
  6. 3
      docs/CARS.md
  7. 2
      panda
  8. 269
      poetry.lock
  9. 3
      pyproject.toml
  10. 2
      selfdrive/boardd/boardd.cc
  11. 4
      selfdrive/car/car_helpers.py
  12. 23
      selfdrive/car/chrysler/interface.py
  13. 11
      selfdrive/car/chrysler/values.py
  14. 4
      selfdrive/car/fw_query_definitions.py
  15. 43
      selfdrive/car/fw_versions.py
  16. 53
      selfdrive/car/gm/interface.py
  17. 10
      selfdrive/car/gm/values.py
  18. 2
      selfdrive/car/honda/interface.py
  19. 12
      selfdrive/car/honda/values.py
  20. 9
      selfdrive/car/hyundai/carcontroller.py
  21. 8
      selfdrive/car/hyundai/carstate.py
  22. 8
      selfdrive/car/hyundai/interface.py
  23. 18
      selfdrive/car/hyundai/values.py
  24. 4
      selfdrive/car/interfaces.py
  25. 1
      selfdrive/car/tests/routes.py
  26. 3
      selfdrive/car/tests/test_car_interfaces.py
  27. 1
      selfdrive/car/torque_data/override.yaml
  28. 2
      selfdrive/controls/lib/events.py
  29. 4
      selfdrive/controls/lib/latcontrol_torque.py
  30. 1
      selfdrive/controls/tests/test_startup.py
  31. 2
      selfdrive/debug/fingerprint_from_route.py
  32. 2
      selfdrive/debug/get_fingerprint.py
  33. 2
      selfdrive/locationd/locationd.cc
  34. 1
      selfdrive/test/process_replay/process_replay.py
  35. 2
      selfdrive/test/process_replay/ref_commit
  36. 2
      selfdrive/test/process_replay/test_processes.py
  37. 170
      tools/cabana/binaryview.cc
  38. 12
      tools/cabana/binaryview.h
  39. 163
      tools/cabana/chartswidget.cc
  40. 40
      tools/cabana/chartswidget.h
  41. 18
      tools/cabana/commands.cc
  42. 20
      tools/cabana/commands.h
  43. 34
      tools/cabana/dbcmanager.cc
  44. 48
      tools/cabana/dbcmanager.h
  45. 94
      tools/cabana/detailwidget.cc
  46. 14
      tools/cabana/detailwidget.h
  47. 19
      tools/cabana/historylog.cc
  48. 8
      tools/cabana/historylog.h
  49. 66
      tools/cabana/mainwin.cc
  50. 14
      tools/cabana/mainwin.h
  51. 47
      tools/cabana/messageswidget.cc
  52. 14
      tools/cabana/messageswidget.h
  53. 15
      tools/cabana/signaledit.cc
  54. 12
      tools/cabana/signaledit.h
  55. 24
      tools/cabana/streams/abstractstream.cc
  56. 17
      tools/cabana/streams/abstractstream.h
  57. 8
      tools/cabana/tools/findsimilarbits.cc
  58. 4
      tools/cabana/tools/findsimilarbits.h
  59. 25
      tools/cabana/util.cc
  60. 1
      tools/cabana/util.h
  61. 7
      tools/cabana/videowidget.cc
  62. 3
      tools/sim/bridge.py

@ -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"

@ -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

@ -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

@ -1 +1 @@
Subproject commit fa3e77b7c8eee8752f19427b34adcb1ae5c70ec5
Subproject commit 162a26ca2d7e5bc9a42bb5ea11e98194f722027b

@ -155,6 +155,7 @@ std::unordered_map<std::string, uint32_t> 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},

@ -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[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<a href="https://comma.ai/shop/comma-three.html?make=Audi&model=Q3 2019-23">J533</a>||
|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<a href="https://comma.ai/shop/comma-three.html?make=Audi&model=RS3 2018">J533</a>||
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<a href="https://comma.ai/shop/comma-three.html?make=Audi&model=S3 2015-17">J533</a>||
|Cadillac|Escalade 2017[<sup>3</sup>](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<a href="https://comma.ai/shop/comma-three.html?make=Cadillac&model=Escalade 2017">OBD-II</a>||
|Cadillac|Escalade ESV 2016[<sup>3</sup>](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<a href="https://comma.ai/shop/comma-three.html?make=Cadillac&model=Escalade ESV 2016">OBD-II</a>||
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<a href="https://comma.ai/shop/comma-three.html?make=Chevrolet&model=Bolt EUV 2022-23">GM</a>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<a href="https://comma.ai/shop/comma-three.html?make=Chevrolet&model=Bolt EV 2022-23">GM</a>||

@ -1 +1 @@
Subproject commit d15250cb1454292c6f1217c79642b9ffd93e7595
Subproject commit 0d2ee009218009173f26c73a16aefa12dd169de8

269
poetry.lock generated

@ -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"},

@ -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]

@ -137,6 +137,8 @@ bool safety_setter_thread(std::vector<Panda *> pandas) {
panda->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U);
}
p.putBool("ObdMultiplexingDisabled", true);
std::string params;
LOGW("waiting for params to set safety model");
while (true) {

@ -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

@ -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:

@ -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):

@ -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

@ -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

@ -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:

@ -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

@ -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

@ -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=[

@ -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

@ -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"),

@ -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

@ -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}

@ -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),

@ -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),

@ -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

@ -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]

@ -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:

@ -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,

@ -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:

@ -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

@ -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()))

@ -619,7 +619,7 @@ kj::ArrayPtr<capnp::byte> 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<std::string, double> critical_services) {

@ -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"

@ -1 +1 @@
69e52f02fd21844ff068c495b7fcb01ebc53bea5
8883c476d5abc12b4b2949e04c6d7c0cd7c8b9fa

@ -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"),

@ -1,15 +1,17 @@
#include "tools/cabana/binaryview.h"
#include <cmath>
#include <QFontDatabase>
#include <QHeaderView>
#include <QMouseEvent>
#include <QPainter>
#include <QScrollBar>
#include <QShortcut>
#include <QToolTip>
#include <cmath>
#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"(
<b>Binary View</b><br/>
<!-- TODO: add descprition here -->
Shortcuts:<br />
Delete Signal:
<span style="background-color:lightGray;color:gray"> x </span>,
<span style="background-color:lightGray;color:gray"> Backspace </span>,
<span style="background-color:lightGray;color:gray"> Delete</span><br />
Change endianness: <span style="background-color:lightGray;color:gray"> e </span><br />
Change singedness: <span style="background-color:lightGray;color:gray"> s </span><br />
Open chart:
<span style="background-color:lightGray;color:gray"> c </span>,
<span style="background-color:lightGray;color:gray"> p </span>,
<span style="background-color:lightGray;color:gray"> g </span><br />
)");
}
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<int, int, bool> 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);
}
}
}

@ -1,5 +1,7 @@
#pragma once
#include <optional>
#include <QApplication>
#include <QList>
#include <QSet>
@ -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<Item> items;
QString msg_id;
std::optional<MessageId> 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<const Signal*> 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<int, int, bool> getSelection(QModelIndex index);
void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override;

@ -11,9 +11,11 @@
#include <QRubberBand>
#include <QPushButton>
#include <QToolBar>
#include <QToolButton>
#include <QToolTip>
#include <QtConcurrent>
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<QToolButton*>(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<QToolButton*>(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"(
<b>Chart view</b><br />
<!-- TODO: add descprition here -->
)"));
}
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<ChartView::SigItem>::iterator ChartView::removeItem(const QList<ChartView::SigItem>::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<bool(const SigItem &s)> 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("<span style=\"text-decoration:%1\"><b>%2</b> <font color=\"gray\">%3 %4</font></span>").arg(decoration, s.sig->name.c_str(), msgName(s.msg_id), s.msg_id));
s.series->setName(QString("<span style=\"text-decoration:%1\"><b>%2</b> <font color=\"gray\">%3 %4</font></span>").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<Event *> *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("<br />"), 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<MessageId>()) {
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<MessageId>();
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("<span style=\"color:%0;\">■ </span> %1").arg(getColor(sig).name(), sig->name.c_str());
if (show_msg_name) text += QString(" <font color=\"gray\">%0 %1</font>").arg(msgName(id), id);
if (show_msg_name) text += QString(" <font color=\"gray\">%0 %1</font>").arg(msgName(id), id.toString());
QLabel *label = new QLabel(text);
label->setContentsMargins(5, 0, 5, 0);

@ -1,6 +1,5 @@
#pragma once
#include <QComboBox>
#include <QDragEnterEvent>
#include <QGridLayout>
#include <QLabel>
@ -8,7 +7,6 @@
#include <QGraphicsPixmapItem>
#include <QGraphicsProxyWidget>
#include <QSlider>
#include <QToolButton>
#include <QtCharts/QChartView>
#include <QtCharts/QLegendMarker>
#include <QtCharts/QLineSeries>
@ -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<Event*> *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<QPointF> 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<ChartView::SigItem>::iterator removeItem(const QList<ChartView::SigItem>::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<bool(const SigItem &)> 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<ChartView *> charts;
@ -148,9 +142,7 @@ private:
std::pair<double, double> display_range;
std::pair<double, double> 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<ListItem *> 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);

@ -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()));
}

@ -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 = {};
};

@ -4,6 +4,10 @@
#include <sstream>
#include <QVector>
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<DBCMsg *>(msg(id))) {
void DBCManager::addSignal(const MessageId &id, const Signal &sig) {
if (auto m = const_cast<DBCMsg *>(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<DBCMsg *>(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<DBCMsg *>(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<uint8_t, uint32_t> 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;

@ -5,6 +5,35 @@
#include <QString>
#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<uint8_t, uint32_t> parseId(const QString &id);
inline static std::vector<std::string> 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<uint32_t, DBCMsg> &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<uint32_t, DBCMsg> 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<int, int> 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;
}

@ -1,6 +1,5 @@
#include "tools/cabana/detailwidget.h"
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QMenu>
#include <QMessageBox>
@ -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<QString, CanData> *msgs) {
void DetailWidget::updateState(const QHash<MessageId, CanData> *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<QString, CanData> *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;}");

@ -1,5 +1,6 @@
#pragma once
#include <QDialogButtonBox>
#include <QSplitter>
#include <QStackedLayout>
#include <QTabWidget>
@ -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<QString, CanData> * msgs = nullptr);
void updateState(const QHash<MessageId, CanData> * msgs = nullptr);
QString msg_id;
std::optional<MessageId> msg_id;
QLabel *time_label, *warning_icon, *warning_label;
ElidedLabel *name_label;
QWidget *warning_widget;
QTabBar *tabbar;
QList<MessageId> tabbar_ids;
QTabWidget *tab_widget;
QAction *remove_msg_act;
LogsWidget *history_log;

@ -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 <class InputIt>
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) {
std::deque<HistoryLogModel::Message> msgs;
const auto [src, address] = DBCManager::parseId(msg_id);
QVector<double> 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::Message> HistoryLogModel::fetchData<>(std::
std::deque<HistoryLogModel::Message> 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();

@ -1,6 +1,8 @@
#pragma once
#include <deque>
#include <optional>
#include <QCheckBox>
#include <QComboBox>
#include <QHeaderView>
@ -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<bool(double, double)> cmp);
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
@ -52,7 +54,7 @@ public:
std::deque<HistoryLogModel::Message> fetchData(InputIt first, InputIt last, uint64_t min_time);
std::deque<Message> fetchData(uint64_t from_time, uint64_t min_time = 0);
QString msg_id;
std::optional<MessageId> 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(); }

@ -9,7 +9,9 @@
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QResizeEvent>
#include <QShortcut>
#include <QTextDocument>
#include <QUndoView>
#include <QVBoxLayout>
#include <QWidgetAction>
@ -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>("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<HelpOverlay*>()) {
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<MessagesWidget *>());
drawHelpForWidget(painter, parent->findChild<BinaryView *>());
drawHelpForWidget(painter, parent->findChild<SignalView *>());
drawHelpForWidget(painter, parent->findChild<ChartsWidget *>());
drawHelpForWidget(painter, parent->findChild<VideoWidget *>());
}
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();
}

@ -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;
};

@ -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 &current, 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"(
<b>Message View</b><br/>
<!-- TODO: add descprition here -->
Byte color: <br />
<span style="color:gray;"> </span> constant changing<br />
<span style="color:blue;"> </span> increasing<br />
<span style="color:red;"> </span> decreasing <br />
)"));
}
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<QVariant> 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<QColor> 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<QString, CanData> *new_msgs) {
void MessageListModel::msgsReceived(const QHash<MessageId, CanData> *new_msgs) {
int prev_row_count = msgs.size();
if (filter_str.isEmpty() && msgs.size() != can->can_msgs.size()) {
msgs = can->can_msgs.keys();

@ -1,5 +1,7 @@
#pragma once
#include <optional>
#include <QAbstractTableModel>
#include <QHeaderView>
#include <QLineEdit>
@ -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<QString, CanData> *new_msgs = nullptr);
void msgsReceived(const QHash<MessageId, CanData> *new_msgs = nullptr);
void sortMessages();
void suppress();
void clearSuppress();
void reset();
QStringList msgs;
QSet<std::pair<QString, int>> suppressed_bytes;
QList<MessageId> msgs;
QSet<std::pair<MessageId, int>> 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<MessageId> current_msg_id;
QLineEdit *filter;
MessageListModel *model;
QPushButton *suppress_add;

@ -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<QString, CanData> *msgs) {
void SignalModel::updateState(const QHash<MessageId, CanData> *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"(
<b>Signal view</b><br />
<!-- TODO: add descprition here -->
)"));
}
void SignalView::setMessage(const QString &id) {
void SignalView::setMessage(const MessageId &id) {
msg_id = id;
filter_edit->clear();
model->setMessage(id);

@ -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<QString, CanData> *msgs);
void updateState(const QHash<MessageId, CanData> *msgs);
QString msg_id;
MessageId msg_id;
QString filter_str;
std::unique_ptr<Item> 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;

@ -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<QHash<QString, CanData>>();
new_msgs = std::make_unique<QHash<MessageId, CanData>>();
QObject::connect(this, &AbstractStream::received, this, &AbstractStream::process, Qt::QueuedConnection);
QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo);
}
void AbstractStream::process(QHash<QString, CanData> *messages) {
void AbstractStream::process(QHash<MessageId, CanData> *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<QString, CanData>);
new_msgs.reset(new QHash<MessageId, CanData>);
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<std::pair<uint8_t, uint32_t>, CanData> last_msgs; // Much faster than QHash<String, CanData>
QHash<MessageId, CanData> 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);

@ -5,6 +5,7 @@
#include <QColor>
#include <QHash>
#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<Event *> *events() const = 0;
@ -49,22 +50,22 @@ signals:
void streamStarted();
void eventsMerged();
void updated();
void msgsReceived(const QHash<QString, CanData> *);
void received(QHash<QString, CanData> *);
void msgsReceived(const QHash<MessageId, CanData> *);
void received(QHash<MessageId, CanData> *);
public:
QHash<QString, CanData> can_msgs;
QHash<MessageId, CanData> can_msgs;
protected:
void process(QHash<QString, CanData> *);
void process(QHash<MessageId, CanData> *);
bool updateEvent(const Event *event);
void updateLastMsgsTo(double sec);
bool is_live_streaming = false;
std::atomic<bool> processing = false;
QHash<QString, uint32_t> counters;
std::unique_ptr<QHash<QString, CanData>> new_msgs;
QHash<QString, ChangeTracker> change_trackers;
QHash<MessageId, uint32_t> counters;
std::unique_ptr<QHash<MessageId, CanData>> new_msgs;
QHash<MessageId, ChangeTracker> change_trackers;
};
// A global pointer referring to the unique AbstractStream object

@ -20,10 +20,10 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi
bus_combo = new QComboBox(this);
QSet<uint8_t> 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);
}
});
}

@ -6,6 +6,8 @@
#include <QSpinBox>
#include <QTableWidget>
#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 {

@ -66,13 +66,6 @@ void ChangeTracker::clear() {
colors.clear();
}
QList<QVariant> ChangeTracker::toVariantList(const QVector<QColor> &colors) {
QList<QVariant> 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<QVariant> colors = index.data(Qt::UserRole).toList();
int i = 0;
for (auto &byte : byte_list) {
auto colors = index.data(Qt::UserRole).value<QVector<QColor>>();
for (int i = 0; i < byte_list.size(); ++i) {
if (i < colors.size()) {
painter->fillRect(pos.marginsAdded(margins), colors[i].value<QColor>());
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++;
}
}

@ -14,7 +14,6 @@
class ChangeTracker {
public:
void compute(const QByteArray &dat, double ts, uint32_t freq);
static QList<QVariant> toVariantList(const QVector<QColor> &colors);
void clear();
QVector<double> last_change_t;

@ -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"(
<b>Video</b><br />
<!-- TODO: add descprition here -->
Shortcuts:<br />
Pause/Resume: <span style="background-color:lightGray;color:gray"> space </span></br>
)"));
}
QWidget *VideoWidget::createCameraWidget() {

@ -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

Loading…
Cancel
Save