Merge remote-tracking branch 'upstream/master' into port_honda_hrv_2023

pull/27731/head
Shane Smiskol 2 years ago
commit a5b1384e07
  1. 17
      Jenkinsfile
  2. 8
      SConstruct
  3. 2
      cereal
  4. 13
      common/gpio.py
  5. 2
      opendbc
  6. 2
      panda
  7. 89
      poetry.lock
  8. 1
      pyproject.toml
  9. 1
      selfdrive/boardd/boardd.cc
  10. 38
      selfdrive/boardd/set_time.py
  11. 5
      selfdrive/boardd/spi.cc
  12. 5
      selfdrive/boardd/tests/test_boardd_loopback.py
  13. 6
      selfdrive/boardd/tests/test_pandad.py
  14. 13
      selfdrive/car/honda/values.py
  15. 9
      selfdrive/car/volkswagen/pqcan.py
  16. 4
      selfdrive/debug/profiling/watch-irqs.sh
  17. 4
      system/camerad/cameras/camera_qcom2.cc
  18. 4
      system/camerad/cameras/real_debayer.cl
  19. 63
      system/hardware/tici/amplifier.py
  20. 40
      system/hardware/tici/hardware.py
  21. 4
      system/hardware/tici/tests/test_agnos_updater.py
  22. 70
      system/hardware/tici/tests/test_amplifier.py
  23. 0
      system/hardware/tici/tests/test_power_draw.py
  24. 10
      system/sensord/tests/test_sensord.py
  25. 79
      tools/cabana/chartswidget.cc
  26. 13
      tools/cabana/chartswidget.h
  27. 17
      tools/cabana/videowidget.cc
  28. 100
      tools/plotjuggler/layouts/camera-timings.xml
  29. 44
      tools/plotjuggler/layouts/ublox-debug.xml

17
Jenkinsfile vendored

@ -125,6 +125,21 @@ pipeline {
}
*/
stage('tizi-tests') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tizi", [
["build openpilot", "cd selfdrive/manager && ./build.py"],
["test boardd loopback", "SINGLE_PANDA=1 python selfdrive/boardd/tests/test_boardd_loopback.py"],
["test pandad", "python selfdrive/boardd/tests/test_pandad.py"],
["test sensord", "cd system/sensord/tests && python -m unittest test_sensord.py"],
["test camerad", "python system/camerad/test/test_camerad.py"],
["test exposure", "python system/camerad/test/test_exposure.py"],
["test amp", "python system/hardware/tici/tests/test_amplifier.py"],
])
}
}
stage('build') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
environment {
@ -156,7 +171,7 @@ pipeline {
steps {
phone_steps("tici-common", [
["build", "cd selfdrive/manager && ./build.py"],
["test power draw", "python system/hardware/tici/test_power_draw.py"],
["test power draw", "python system/hardware/tici/tests/test_power_draw.py"],
["test loggerd", "python system/loggerd/tests/test_loggerd.py"],
["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python system/loggerd/tests/test_encoder.py"],
["test pigeond", "python system/sensord/tests/test_pigeond.py"],

@ -5,6 +5,8 @@ import sysconfig
import platform
import numpy as np
import SCons.Errors
TICI = os.path.isfile('/TICI')
AGNOS = TICI
@ -311,7 +313,11 @@ else:
elif arch != "Darwin":
qt_libs += ["GL"]
qt_env.Tool('qt')
try:
qt_env.Tool('qt3')
except SCons.Errors.UserError:
qt_env.Tool('qt')
qt_env['CPPPATH'] += qt_dirs + ["#selfdrive/ui/qt/"]
qt_flags = [
"-D_REENTRANT",

@ -1 +1 @@
Subproject commit 9baf462ce897182f57054322ef229485d6df9031
Subproject commit 4f5502c8657813ccb538e9575ca1a7b258b17af9

@ -1,4 +1,5 @@
from typing import Optional
import glob
from typing import Optional, List
def gpio_init(pin: int, output: bool) -> None:
try:
@ -23,3 +24,13 @@ def gpio_read(pin: int) -> Optional[bool]:
print(f"Failed to set gpio {pin} value: {e}")
return val
def get_irq_for_action(action: str) -> List[int]:
ret = []
for fn in glob.glob('/sys/kernel/irq/*/actions'):
with open(fn) as f:
actions = f.read().strip().split(',')
if action in actions:
irq = int(fn.split('/')[-2])
ret.append(irq)
return ret

@ -1 +1 @@
Subproject commit 9a1de83e4b0625c38c6bc677a762528b7026aa5e
Subproject commit 8a3c9a72519d90d12a6c7705ee0daaa3ba5662c4

@ -1 +1 @@
Subproject commit e516a5752b56d9b95229042c5cca8c74b6a9feb0
Subproject commit 878e0077ac077c5325b37710b8df856468d85674

89
poetry.lock generated

@ -55,7 +55,7 @@ frozenlist = ">=1.1.0"
name = "alabaster"
version = "0.7.12"
description = "A configurable sidebar-enabled Sphinx theme"
category = "dev"
category = "main"
optional = false
python-versions = "*"
@ -374,7 +374,7 @@ azure-nspkg = ">=2.0.0"
name = "babel"
version = "2.10.3"
description = "Internationalization utilities"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.6"
@ -856,7 +856,7 @@ python-versions = "*"
name = "docutils"
version = "0.17.1"
description = "Docutils -- Python Documentation Utilities"
category = "dev"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@ -1390,7 +1390,7 @@ tifffile = ["tifffile"]
name = "imagesize"
version = "1.4.1"
description = "Getting image size from png/jpeg/jpeg2000/gif file"
category = "dev"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
@ -2703,6 +2703,23 @@ category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "panflute"
version = "2.3.0"
description = "Pythonic Pandoc filters"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
click = ">=6,<9"
pyyaml = ">=3,<7"
[package.extras]
dev = ["configparser", "coverage", "flake8", "pandocfilters", "pytest", "pytest-cov", "requests"]
extras = ["yamlloader (>=1,<2)"]
pypi = ["Pygments", "docutils", "twine", "wheel"]
[[package]]
name = "parameterized"
version = "0.8.1"
@ -3115,7 +3132,7 @@ python-versions = ">=3.6"
name = "pygments"
version = "2.13.0"
description = "Pygments is a syntax highlighting package written in Python."
category = "dev"
category = "main"
optional = false
python-versions = ">=3.6"
@ -3426,7 +3443,7 @@ numpy = ["numpy (>=1.6.0)"]
name = "pytz"
version = "2022.5"
description = "World timezone definitions, modern and historical"
category = "dev"
category = "main"
optional = false
python-versions = "*"
@ -3714,6 +3731,27 @@ python-versions = ">=3.6"
[package.dependencies]
setuptools = "*"
[[package]]
name = "sconscontrib"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = ">=3.6, <4"
develop = false
[package.dependencies]
docutils = "*"
panflute = "*"
SCons = ">=4"
sphinx = "*"
[package.source]
type = "git"
url = "https://github.com/SCons/scons-contrib.git"
reference = "HEAD"
resolved_reference = "f3b0100d3a628e4d18f496815903660a99489bae"
[[package]]
name = "secretstorage"
version = "3.3.3"
@ -3892,7 +3930,7 @@ python-versions = ">=3.7"
name = "snowballstemmer"
version = "2.2.0"
description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
category = "dev"
category = "main"
optional = false
python-versions = "*"
@ -3930,7 +3968,7 @@ python-versions = ">=3.6"
name = "sphinx"
version = "5.3.0"
description = "Python documentation generator"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.6"
@ -3989,7 +4027,7 @@ sphinx = ">=1.2"
name = "sphinxcontrib-applehelp"
version = "1.0.2"
description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.5"
@ -4001,7 +4039,7 @@ test = ["pytest"]
name = "sphinxcontrib-devhelp"
version = "1.0.2"
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
category = "dev"
category = "main"
optional = false
python-versions = ">=3.5"
@ -4013,7 +4051,7 @@ test = ["pytest"]
name = "sphinxcontrib-htmlhelp"
version = "2.0.0"
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.6"
@ -4025,7 +4063,7 @@ test = ["html5lib", "pytest"]
name = "sphinxcontrib-jsmath"
version = "1.0.1"
description = "A sphinx extension which renders display math in HTML via JavaScript"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.5"
@ -4036,7 +4074,7 @@ test = ["flake8", "mypy", "pytest"]
name = "sphinxcontrib-qthelp"
version = "1.0.3"
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
category = "dev"
category = "main"
optional = false
python-versions = ">=3.5"
@ -4048,7 +4086,7 @@ test = ["pytest"]
name = "sphinxcontrib-serializinghtml"
version = "1.1.5"
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
category = "dev"
category = "main"
optional = false
python-versions = ">=3.5"
@ -4659,7 +4697,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "1.1"
python-versions = "~3.8"
content-hash = "b7cd75dfa0dcddff224696ccc7f41d87aac64652f744ab386321c1eee920fbe9"
content-hash = "562996977917001bad838c79b9276451d97ab632ae9e2b0f7357c6aa2c50f3d8"
[metadata.files]
adal = [
@ -6896,7 +6934,6 @@ onnx = [
]
onnx2torch = [
{file = "onnx2torch-1.5.4-py3-none-any.whl", hash = "sha256:fd1a0fe05072bfb9f3d86d9330299b130b41f11bd4ae634db17078974e711725"},
{file = "onnx2torch-1.5.4.tar.gz", hash = "sha256:df837b557a63540223d85fde4a1d679fde0ca8d8bb89d5379c030b01eddc9c24"},
]
onnxoptimizer = [
{file = "onnxoptimizer-0.3.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e73a5e2e3ca4db9bff54f7131768749c861677b97ee811a136fcf1a52783cf6e"},
@ -6980,6 +7017,10 @@ pandocfilters = [
{file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"},
{file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"},
]
panflute = [
{file = "panflute-2.3.0-py3-none-any.whl", hash = "sha256:02673bcbdb521a805f08a2ca0ce864de86ad409ad406a01b3700fcf2aca81635"},
{file = "panflute-2.3.0.tar.gz", hash = "sha256:cefd9dfc48ccd9732a53db57610701d22806da397a8a97e5cc8dc070b55865ca"},
]
parameterized = [
{file = "parameterized-0.8.1-py2.py3-none-any.whl", hash = "sha256:9cbb0b69a03e8695d68b3399a8a5825200976536fe1cb79db60ed6a4c8c9efe9"},
{file = "parameterized-0.8.1.tar.gz", hash = "sha256:41bbff37d6186430f77f900d777e5bb6a24928a1c46fb1de692f8b52b8833b5c"},
@ -7282,7 +7323,6 @@ pycryptodome = [
{file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c9ed8aa31c146bef65d89a1b655f5f4eab5e1120f55fc297713c89c9e56ff0b"},
{file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:5099c9ca345b2f252f0c28e96904643153bae9258647585e5e6f649bb7a1844a"},
{file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2ec709b0a58b539a4f9d33fb8508264c3678d7edb33a68b8906ba914f71e8c13"},
{file = "pycryptodome-3.15.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:2ae53125de5b0d2c95194d957db9bb2681da8c24d0fb0fe3b056de2bcaf5d837"},
{file = "pycryptodome-3.15.0-cp27-cp27m-win32.whl", hash = "sha256:fd2184aae6ee2a944aaa49113e6f5787cdc5e4db1eb8edb1aea914bd75f33a0c"},
{file = "pycryptodome-3.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:7e3a8f6ee405b3bd1c4da371b93c31f7027944b2bcce0697022801db93120d83"},
{file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:b9c5b1a1977491533dfd31e01550ee36ae0249d78aae7f632590db833a5012b8"},
@ -7290,14 +7330,12 @@ pycryptodome = [
{file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2aa55aae81f935a08d5a3c2042eb81741a43e044bd8a81ea7239448ad751f763"},
{file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c3640deff4197fa064295aaac10ab49a0d55ef3d6a54ae1499c40d646655c89f"},
{file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:045d75527241d17e6ef13636d845a12e54660aa82e823b3b3341bcf5af03fa79"},
{file = "pycryptodome-3.15.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:eb6fce570869e70cc8ebe68eaa1c26bed56d40ad0f93431ee61d400525433c54"},
{file = "pycryptodome-3.15.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9ee40e2168f1348ae476676a2e938ca80a2f57b14a249d8fe0d3cdf803e5a676"},
{file = "pycryptodome-3.15.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:4c3ccad74eeb7b001f3538643c4225eac398c77d617ebb3e57571a897943c667"},
{file = "pycryptodome-3.15.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:1b22bcd9ec55e9c74927f6b1f69843cb256fb5a465088ce62837f793d9ffea88"},
{file = "pycryptodome-3.15.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:57f565acd2f0cf6fb3e1ba553d0cb1f33405ec1f9c5ded9b9a0a5320f2c0bd3d"},
{file = "pycryptodome-3.15.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:4b52cb18b0ad46087caeb37a15e08040f3b4c2d444d58371b6f5d786d95534c2"},
{file = "pycryptodome-3.15.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:092a26e78b73f2530b8bd6b3898e7453ab2f36e42fd85097d705d6aba2ec3e5e"},
{file = "pycryptodome-3.15.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:50ca7e587b8e541eb6c192acf92449d95377d1f88908c0a32ac5ac2703ebe28b"},
{file = "pycryptodome-3.15.0-cp35-abi3-win32.whl", hash = "sha256:e244ab85c422260de91cda6379e8e986405b4f13dc97d2876497178707f87fc1"},
{file = "pycryptodome-3.15.0-cp35-abi3-win_amd64.whl", hash = "sha256:c77126899c4b9c9827ddf50565e93955cb3996813c18900c16b2ea0474e130e9"},
{file = "pycryptodome-3.15.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:9eaadc058106344a566dc51d3d3a758ab07f8edde013712bc8d22032a86b264f"},
@ -7968,6 +8006,7 @@ scons = [
{file = "SCons-4.4.0-py3-none-any.whl", hash = "sha256:cbbd73b83cf85f1aaaf986370359de1bbfd3af97a765cb3554734f6dcec734e1"},
{file = "SCons-4.4.0.tar.gz", hash = "sha256:7703c4e9d2200b4854a31800c1dbd4587e1fa86e75f58795c740bcfa7eca7eaa"},
]
sconscontrib = []
secretstorage = [
{file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"},
{file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"},
@ -7997,18 +8036,6 @@ setproctitle = [
{file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1"},
{file = "setproctitle-1.3.2-cp310-cp310-win32.whl", hash = "sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099"},
{file = "setproctitle-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d"},
{file = "setproctitle-1.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a97d51c17d438cf5be284775a322d57b7ca9505bb7e118c28b1824ecaf8aeaa"},
{file = "setproctitle-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587c7d6780109fbd8a627758063d08ab0421377c0853780e5c356873cdf0f077"},
{file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d17c8bd073cbf8d141993db45145a70b307385b69171d6b54bcf23e5d644de"},
{file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e932089c35a396dc31a5a1fc49889dd559548d14cb2237adae260382a090382e"},
{file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e4f8f12258a8739c565292a551c3db62cca4ed4f6b6126664e2381acb4931bf"},
{file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:570d255fd99c7f14d8f91363c3ea96bd54f8742275796bca67e1414aeca7d8c3"},
{file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a8e0881568c5e6beff91ef73c0ec8ac2a9d3ecc9edd6bd83c31ca34f770910c4"},
{file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4bba3be4c1fabf170595b71f3af46c6d482fbe7d9e0563999b49999a31876f77"},
{file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:37ece938110cab2bb3957e3910af8152ca15f2b6efdf4f2612e3f6b7e5459b80"},
{file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db684d6bbb735a80bcbc3737856385b55d53f8a44ce9b46e9a5682c5133a9bf7"},
{file = "setproctitle-1.3.2-cp311-cp311-win32.whl", hash = "sha256:ca58cd260ea02759238d994cfae844fc8b1e206c684beb8f38877dcab8451dfc"},
{file = "setproctitle-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:88486e6cce2a18a033013d17b30a594f1c5cb42520c49c19e6ade40b864bb7ff"},
{file = "setproctitle-1.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927"},
{file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02"},
{file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83"},

@ -59,6 +59,7 @@ urllib3 = "^1.26.10"
utm = "^0.7.0"
websocket_client = "^1.3.3"
polyline = "^1.4.0"
sconscontrib = {git = "https://github.com/SCons/scons-contrib.git"}
[tool.poetry.group.dev.dependencies]

@ -386,6 +386,7 @@ std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *>
ps.setInterruptLoad(health.interrupt_load);
ps.setFanPower(health.fan_power);
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid));
ps.setSpiChecksumErrorCount(health.spi_checksum_error_count);
std::array<cereal::PandaState::PandaCanState::Builder, PANDA_CAN_CNT> cs = {ps.initCanState0(), ps.initCanState1(), ps.initCanState2()};

@ -1,11 +1,9 @@
#!/usr/bin/env python3
import datetime
import os
import struct
import usb1
import datetime
from panda import Panda
REQUEST_IN = usb1.ENDPOINT_IN | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE
MIN_DATE = datetime.datetime(year=2021, month=4, day=1)
MIN_DATE = datetime.datetime(year=2023, month=4, day=1)
def set_time(logger):
sys_time = datetime.datetime.today()
@ -14,24 +12,28 @@ def set_time(logger):
return
try:
ctx = usb1.USBContext()
dev = ctx.openByVendorIDAndProductID(0xbbaa, 0xddcc)
if dev is None:
logger.info("No panda found")
ps = Panda.list()
if len(ps) == 0:
logger.error("Failed to set time, no pandas found")
return
# Set system time from panda RTC time
dat = dev.controlRead(REQUEST_IN, 0xa0, 0, 0, 8)
a = struct.unpack("HBBBBBB", dat)
panda_time = datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6])
if panda_time > MIN_DATE:
logger.info(f"adjusting time from '{sys_time}' to '{panda_time}'")
os.system(f"TZ=UTC date -s '{panda_time}'")
for s in ps:
with Panda(serial=s) as p:
if not p.is_internal():
continue
# Set system time from panda RTC time
panda_time = p.get_datetime()
logger.info(f"adjusting time from '{sys_time}' to '{panda_time}'")
if panda_time > MIN_DATE:
logger.info(f"adjusting time from '{sys_time}' to '{panda_time}'")
os.system(f"TZ=UTC date -s '{panda_time}'")
break
except Exception:
logger.warn("Failed to fetch time from panda")
logger.exception("Failed to fetch time from panda")
if __name__ == "__main__":
import logging
logging.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.DEBUG)
set_time(logging)

@ -57,9 +57,12 @@ PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) {
uint8_t uid[uid_len] = {0};
uint32_t spi_mode = SPI_MODE_0;
uint32_t spi_speed = 30000000;
uint8_t spi_bits_per_word = 8;
// 50MHz is the max of the 845. note that some older
// revs of the comma three may not support this speed
uint32_t spi_speed = 50000000;
spi_fd = open(SPI_DEVICE.c_str(), O_RDWR);
if (spi_fd < 0) {
LOGE("failed opening SPI device %d", spi_fd);

@ -40,8 +40,9 @@ class TestBoardd(unittest.TestCase):
sm.update(1000)
num_pandas = len(sm['pandaStates'])
if TICI:
self.assertGreater(num_pandas, 1, "connect another panda for multipanda tests")
expected_pandas = 2 if TICI and "SINGLE_PANDA" not in os.environ else 1
self.assertEqual(num_pandas, expected_pandas, "connected pandas ({num_pandas}) doesn't match expected panda count ({expected_pandas}). \
connect another panda for multipanda tests.")
# boardd blocks on CarVin and CarParams
cp = car.CarParams.new_message()

@ -14,9 +14,9 @@ class TestPandad(unittest.TestCase):
def tearDown(self):
managed_processes['pandad'].stop()
def _wait_for_boardd(self):
def _wait_for_boardd(self, timeout=30):
sm = messaging.SubMaster(['peripheralState'])
for _ in range(30):
for _ in range(timeout):
sm.update(1000)
if sm.updated['peripheralState']:
break
@ -30,7 +30,7 @@ class TestPandad(unittest.TestCase):
time.sleep(1)
managed_processes['pandad'].start()
self._wait_for_boardd()
self._wait_for_boardd(60)
@phone_only
def test_in_bootstub(self):

@ -1248,6 +1248,7 @@ FW_VERSIONS = {
b'37805-5YF-A750\x00\x00',
b'37805-5YF-A850\x00\x00',
b'37805-5YF-A870\x00\x00',
b'37805-5YF-AD20\x00\x00',
b'37805-5YF-C210\x00\x00',
b'37805-5YF-C220\x00\x00',
b'37805-5YF-C410\000\000',
@ -1256,16 +1257,20 @@ FW_VERSIONS = {
(Ecu.vsa, 0x18da28f1, None): [
b'57114-TJB-A030\x00\x00',
b'57114-TJB-A040\x00\x00',
b'57114-TJB-A120\x00\x00',
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36802-TJB-A040\x00\x00',
b'36802-TJB-A050\x00\x00',
b'36802-TJB-A540\x00\x00',
],
(Ecu.fwdCamera, 0x18dab5f1, None): [
b'36161-TJB-A040\x00\x00',
b'36161-TJB-A530\x00\x00',
],
(Ecu.shiftByWire, 0x18da0bf1, None): [
b'54008-TJB-A520\x00\x00',
b'54008-TJB-A530\x00\x00',
],
(Ecu.transmission, 0x18da1ef1, None): [
b'28102-5YK-A610\x00\x00',
@ -1273,6 +1278,7 @@ FW_VERSIONS = {
b'28102-5YK-A630\x00\x00',
b'28102-5YK-A700\x00\x00',
b'28102-5YK-A711\x00\x00',
b'28102-5YK-A800\x00\x00',
b'28102-5YL-A620\x00\x00',
b'28102-5YL-A700\x00\x00',
b'28102-5YL-A711\x00\x00',
@ -1284,6 +1290,7 @@ FW_VERSIONS = {
b'78109-TJB-AB10\x00\x00',
b'78109-TJB-AD10\x00\x00',
b'78109-TJB-AF10\x00\x00',
b'78109-TJB-AQ20\x00\x00',
b'78109-TJB-AR10\x00\x00',
b'78109-TJB-AS10\000\000',
b'78109-TJB-AU10\x00\x00',
@ -1295,22 +1302,26 @@ FW_VERSIONS = {
],
(Ecu.srs, 0x18da53f1, None): [
b'77959-TJB-A040\x00\x00',
b'77959-TJB-A120\x00\x00',
b'77959-TJB-A210\x00\x00',
],
(Ecu.electricBrakeBooster, 0x18da2bf1, None): [
b'46114-TJB-A040\x00\x00',
b'46114-TJB-A050\x00\x00',
b'46114-TJB-A060\x00\x00',
b'46114-TJB-A120\x00\x00',
],
(Ecu.gateway, 0x18daeff1, None): [
b'38897-TJB-A040\x00\x00',
b'38897-TJB-A110\x00\x00',
b'38897-TJB-A120\x00\x00',
b'38897-TJB-A220\x00\x00',
],
(Ecu.eps, 0x18da30f1, None): [
b'39990-TJB-A030\x00\x00',
b'39990-TJB-A040\x00\x00',
b'39990-TJB-A130\x00\x00'
b'39990-TJB-A070\x00\x00',
b'39990-TJB-A130\x00\x00',
],
},
CAR.RIDGELINE: {

@ -64,9 +64,10 @@ def create_acc_accel_control(packer, bus, acc_type, acc_enabled, accel, acc_cont
values = {
"ACS_Sta_ADR": acc_control,
"ACS_StSt_Info": acc_control != 1,
"ACS_StSt_Info": acc_enabled,
"ACS_Typ_ACC": acc_type,
"ACS_Anhaltewunsch": acc_type == 1 and stopping,
"ACS_FreigSollB": acc_enabled,
"ACS_Sollbeschl": accel if acc_enabled else 3.01,
"ACS_zul_Regelabw": 0.2 if acc_enabled else 1.27,
"ACS_max_AendGrad": 3.0 if acc_enabled else 5.08,
@ -83,9 +84,9 @@ def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_distance
"ACA_Zeitluecke": 2,
"ACA_V_Wunsch": set_speed,
"ACA_gemZeitl": lead_distance,
# TODO: ACA_ID_StaACC, ACA_AnzDisplay, ACA_kmh_mph, ACA_PrioDisp, ACA_Aend_Zeitluecke
# display/display-prio handling probably needed to stop confusing the instrument cluster
# kmh_mph handling probably needed to resolve rounding errors in displayed setpoint
"ACA_PrioDisp": 3,
# TODO: restore dynamic pop-to-foreground/highlight behavior with ACA_PrioDisp and ACA_AnzDisplay
# TODO: ACA_kmh_mph handling probably needed to resolve rounding errors in displayed setpoint
}
return packer.make_can_msg("ACC_GRA_Anzeige", bus, values)

@ -0,0 +1,4 @@
#!/usr/bin/bash
set -e
RUBYOPT="-W0" irqtop -d1 -R

@ -71,7 +71,7 @@ const int DC_GAIN_MIN_WEIGHT_OX03C10 = 1; // always on is fine
const int DC_GAIN_MAX_WEIGHT_OX03C10 = 1;
const float TARGET_GREY_FACTOR_AR0231 = 1.0;
const float TARGET_GREY_FACTOR_OX03C10 = 0.02;
const float TARGET_GREY_FACTOR_OX03C10 = 0.01;
const float sensor_analog_gains_AR0231[] = {
1.0/8.0, 2.0/8.0, 2.0/7.0, 3.0/7.0, // 0, 1, 2, 3
@ -101,7 +101,7 @@ const float ANALOG_GAIN_COST_LOW_AR0231 = 0.1;
const float ANALOG_GAIN_COST_HIGH_AR0231 = 5.0;
const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0;
const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x11; // 2.5x
const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x0; // 1x
const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36;
const int ANALOG_GAIN_COST_DELTA_OX03C10 = -1;
const float ANALOG_GAIN_COST_LOW_OX03C10 = 0.4;

@ -18,6 +18,9 @@ float3 color_correct(float3 rgb) {
x += rgb.z * (float3)(-0.25277411, -0.05627105, 1.45875782);
#endif
#if IS_OX
return -0.507089*exp(-12.54124638*x)+0.9655*powr(x,0.5)-0.472597*x+0.507089;
#else
// tone mapping params
const float gamma_k = 0.75;
const float gamma_b = 0.125;
@ -28,6 +31,7 @@ float3 color_correct(float3 rgb) {
return (x > mp) ?
((rk * (x-mp) * (1-(gamma_k*mp+gamma_b)) * (1+1/(rk*(1-mp))) / (1+rk*(x-mp))) + gamma_k*mp + gamma_b) :
((rk * (x-mp) * (gamma_k*mp+gamma_b) * (1+1/(rk*mp)) / (1-rk*(x-mp))) + gamma_k*mp + gamma_b);
#endif
}
float get_vignetting_s(float r) {

@ -1,5 +1,7 @@
import time
from smbus2 import SMBus
from collections import namedtuple
from typing import List
# https://datasheets.maximintegrated.com/en/ds/MAX98089.pdf
@ -104,31 +106,44 @@ class Amplifier:
def __init__(self, debug=False):
self.debug = debug
def set_config(self, config):
with SMBus(self.AMP_I2C_BUS) as bus:
if self.debug:
print(f"Setting \"{config.name}\" to {config.value}:")
old_value = bus.read_byte_data(self.AMP_ADDRESS, config.register, force=True)
new_value = (old_value & (~config.mask)) | ((config.value << config.offset) & config.mask)
bus.write_byte_data(self.AMP_ADDRESS, config.register, new_value, force=True)
if self.debug:
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
def set_global_shutdown(self, amp_disabled):
self.set_config(AmpConfig("Global shutdown", 0b0 if amp_disabled else 0b1, 0x51, 7, 0b10000000))
def _get_shutdown_config(self, amp_disabled: bool) -> AmpConfig:
return AmpConfig("Global shutdown", 0b0 if amp_disabled else 0b1, 0x51, 7, 0b10000000)
def initialize_configuration(self, model):
self.set_global_shutdown(amp_disabled=True)
for config in BASE_CONFIG:
self.set_config(config)
for config in CONFIGS[model]:
self.set_config(config)
self.set_global_shutdown(amp_disabled=False)
def _set_configs(self, configs: List[AmpConfig]) -> None:
with SMBus(self.AMP_I2C_BUS) as bus:
for config in configs:
if self.debug:
print(f"Setting \"{config.name}\" to {config.value}:")
old_value = bus.read_byte_data(self.AMP_ADDRESS, config.register, force=True)
new_value = (old_value & (~config.mask)) | ((config.value << config.offset) & config.mask)
bus.write_byte_data(self.AMP_ADDRESS, config.register, new_value, force=True)
if self.debug:
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
def set_configs(self, configs: List[AmpConfig]) -> bool:
# retry in case panda is using the amp
for _ in range(10):
try:
self._set_configs(configs)
return True
except OSError:
print("Failed to set amp config, retrying...")
time.sleep(0.02)
return False
def set_global_shutdown(self, amp_disabled: bool) -> bool:
return self.set_configs([self._get_shutdown_config(amp_disabled), ])
def initialize_configuration(self, model: str) -> bool:
cfgs = [
self._get_shutdown_config(True),
*BASE_CONFIG,
*CONFIGS[model],
self._get_shutdown_config(False),
]
return self.set_configs(cfgs)
if __name__ == "__main__":

@ -8,7 +8,7 @@ from functools import cached_property
from pathlib import Path
from cereal import log
from common.gpio import gpio_set, gpio_init
from common.gpio import gpio_set, gpio_init, get_irq_for_action
from system.hardware.base import HardwareBase, ThermalConfig
from system.hardware.tici import iwlist
from system.hardware.tici.pins import GPIO
@ -63,8 +63,14 @@ MM_MODEM_ACCESS_TECHNOLOGY_LTE = 1 << 14
def sudo_write(val, path):
os.system(f"sudo su -c 'echo {val} > {path}'")
def affine_irq(val, irq):
sudo_write(str(val), f"/proc/irq/{irq}/smp_affinity_list")
def affine_irq(val, action):
irq = get_irq_for_action(action)
if len(irq) == 0:
print(f"No IRQs found for '{action}'")
return
for i in irq:
sudo_write(str(val), f"/proc/irq/{i}/smp_affinity_list")
class Tici(HardwareBase):
@ -432,12 +438,20 @@ class Tici(HardwareBase):
sudo_write(gov, f'/sys/devices/system/cpu/cpufreq/policy{n}/scaling_governor')
# *** IRQ config ***
affine_irq(5, 565) # kgsl-3d0
affine_irq(4, 126) # SPI goes on boardd core
affine_irq(4, 740) # xhci-hcd:usb1 goes on the boardd core
affine_irq(4, 1069) # xhci-hcd:usb3 goes on the boardd core
for irq in range(237, 246):
affine_irq(5, irq) # camerad
# GPU
affine_irq(5, "kgsl-3d0")
# boardd core
affine_irq(4, "spi_geni") # SPI
affine_irq(4, "xhci-hcd:usb3") # aux panda USB (or potentially anything else on USB)
if "tici" in self.model:
affine_irq(4, "xhci-hcd:usb1") # internal panda USB
# camerad core
camera_irqs = ("cci", "cpas_camnoc", "cpas-cdm", "csid", "ife", "csid", "csid-lite", "ife-lite")
for n in camera_irqs:
affine_irq(5, n)
def get_gpu_usage_percent(self):
try:
@ -461,9 +475,11 @@ class Tici(HardwareBase):
# *** IRQ config ***
# move these off the default core
affine_irq(1, 7) # msm_drm
affine_irq(1, 250) # msm_vidc
affine_irq(1, 8) # i2c_geni (sensord)
affine_irq(1, "msm_drm")
affine_irq(1, "msm_vidc")
affine_irq(1, "i2c_geni")
# mask off big cluster from default affinity
sudo_write("f", "/proc/irq/default_smp_affinity")
# *** GPU config ***

@ -4,8 +4,8 @@ import os
import unittest
import requests
AGNOS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)))
MANIFEST = os.path.join(AGNOS_DIR, "agnos.json")
TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)))
MANIFEST = os.path.join(TEST_DIR, "../agnos.json")
class TestAgnosUpdater(unittest.TestCase):

@ -0,0 +1,70 @@
#!/usr/bin/env python3
import time
import random
import unittest
import subprocess
from panda import Panda
from system.hardware import TICI
from system.hardware.tici.hardware import Tici
from system.hardware.tici.amplifier import Amplifier
class TestAmplifier(unittest.TestCase):
@classmethod
def setUpClass(cls):
if not TICI:
raise unittest.SkipTest
def setUp(self):
# clear dmesg
subprocess.check_call("sudo dmesg -C", shell=True)
self.panda = Panda()
self.panda.reset()
def tearDown(self):
self.panda.reset(reconnect=False)
def _check_for_i2c_errors(self, expected):
dmesg = subprocess.check_output("dmesg", shell=True, encoding='utf8')
i2c_lines = [l for l in dmesg.strip().splitlines() if 'i2c_geni a88000.i2c' in l]
i2c_str = '\n'.join(i2c_lines)
if not expected:
assert len(i2c_lines) == 0
else:
assert "i2c error :-107" in i2c_str or "Bus arbitration lost" in i2c_str
def test_init(self):
amp = Amplifier(debug=True)
r = amp.initialize_configuration(Tici().model)
assert r
self._check_for_i2c_errors(False)
def test_shutdown(self):
amp = Amplifier(debug=True)
for _ in range(10):
r = amp.set_global_shutdown(True)
r = amp.set_global_shutdown(False)
assert r
self._check_for_i2c_errors(False)
def test_init_while_siren_play(self):
for _ in range(5):
self.panda.set_siren(False)
time.sleep(0.1)
self.panda.set_siren(True)
time.sleep(random.randint(0, 5))
amp = Amplifier(debug=True)
r = amp.initialize_configuration(Tici().model)
assert r
# make sure we're a good test
self._check_for_i2c_errors(True)
if __name__ == "__main__":
unittest.main()

@ -1,6 +1,5 @@
#!/usr/bin/env python3
import os
import glob
import time
import unittest
import numpy as np
@ -8,6 +7,7 @@ from collections import namedtuple, defaultdict
import cereal.messaging as messaging
from cereal import log
from common.gpio import get_irq_for_action
from system.hardware import TICI
from selfdrive.manager.process_config import managed_processes
@ -113,13 +113,7 @@ class TestSensord(unittest.TestCase):
cls.events = read_sensor_events(cls.sample_secs)
# determine sensord's irq
cls.sensord_irq = None
for fn in glob.glob('/sys/kernel/irq/*/actions'):
with open(fn) as f:
if "sensord" in f.read():
cls.sensord_irq = int(fn.split('/')[-2])
break
assert cls.sensord_irq is not None
cls.sensord_irq = get_irq_for_action("sensord")[0]
finally:
# teardown won't run if this doesn't succeed
managed_processes["sensord"].stop()

@ -12,6 +12,7 @@
#include <QOpenGLWidget>
#include <QPushButton>
#include <QRubberBand>
#include <QScrollBar>
#include <QStylePainter>
#include <QToolBar>
#include <QToolTip>
@ -22,7 +23,7 @@ static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; }
// ChartsWidget
ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), QFrame(parent) {
ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_timer(this), QFrame(parent) {
setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
@ -99,6 +100,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), QFrame(parent)
align_timer.setSingleShot(true);
QObject::connect(&align_timer, &QTimer::timeout, this, &ChartsWidget::alignCharts);
QObject::connect(&auto_scroll_timer, &QTimer::timeout, this, &ChartsWidget::doAutoScroll);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll);
QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged);
QObject::connect(can, &AbstractStream::updated, this, &ChartsWidget::updateState);
@ -270,6 +272,36 @@ void ChartsWidget::updateLayout() {
}
}
void ChartsWidget::startAutoScroll() {
auto_scroll_timer.start(50);
}
void ChartsWidget::stopAutoScroll() {
auto_scroll_timer.stop();
auto_scroll_count = 0;
}
void ChartsWidget::doAutoScroll() {
QScrollBar *scroll = charts_scroll->verticalScrollBar();
if (auto_scroll_count < scroll->pageStep()) {
++auto_scroll_count;
}
int value = scroll->value();
QPoint pos = charts_scroll->viewport()->mapFromGlobal(QCursor::pos());
QRect area = charts_scroll->viewport()->rect();
if (pos.y() - area.top() < settings.chart_height / 2) {
scroll->setValue(value - auto_scroll_count);
} else if (area.bottom() - pos.y() < settings.chart_height / 2) {
scroll->setValue(value + auto_scroll_count);
}
bool vertical_unchanged = value == scroll->value();
if (vertical_unchanged) {
stopAutoScroll();
}
}
void ChartsWidget::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
updateLayout();
@ -335,7 +367,6 @@ bool ChartsWidget::event(QEvent *event) {
back_button = ev->button() == Qt::BackButton;
break;
}
case QEvent::NativeGesture: {
QNativeGestureEvent *ev = static_cast<QNativeGestureEvent *>(event);
back_button = (ev->value() == 180);
@ -361,7 +392,7 @@ bool ChartsWidget::event(QEvent *event) {
// ChartView
ChartView::ChartView(const std::pair<double, double> &x_range, QWidget *parent) : tip_label(this), QChartView(nullptr, parent) {
ChartView::ChartView(const std::pair<double, double> &x_range, ChartsWidget *parent) : charts_widget(parent), tip_label(this), QChartView(nullptr, parent) {
series_type = (SeriesType)settings.chart_series_type;
QChart *chart = new QChart();
chart->setBackgroundVisible(false);
@ -514,7 +545,7 @@ void ChartView::updatePlotArea(int left_pos, bool force) {
qreal left, top, right, bottom;
chart()->layout()->getContentsMargins(&left, &top, &right, &bottom);
QSizeF x_label_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, QString::number(axis_x->max(), 'f', 2));
x_label_size += QSizeF{5 * devicePixelRatioF(), 5 * devicePixelRatioF()};
x_label_size += QSizeF{5, 5};
int adjust_top = chart()->legend()->geometry().height() + style()->pixelMetric(QStyle::PM_LayoutTopMargin);
chart()->setPlotArea(rect().adjusted(align_to + left, adjust_top + top, -x_label_size.width() / 2 - right, -x_label_size.height() - bottom));
chart()->layout()->invalidate();
@ -704,11 +735,18 @@ void ChartView::mousePressEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton && move_icon->sceneBoundingRect().contains(event->pos())) {
QMimeData *mimeData = new QMimeData;
mimeData->setData(mime_type, QByteArray::number((qulonglong)this));
QPixmap pm = grab();
QPainter p(&pm);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(pm.rect(), QColor(0, 0, 0, 180));
p.end();
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(grab());
drag->setPixmap(pm);
drag->setHotSpot(event->pos());
drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::MoveAction);
charts_widget->stopAutoScroll();
} else if (event->button() == Qt::LeftButton && QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) {
if (!can->liveStreaming()) {
// Save current playback state when scrubbing
@ -735,13 +773,11 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
min = std::clamp(min, 0., can->totalSeconds());
max = std::clamp(max, 0., can->totalSeconds());
double min_rounded = std::floor(min * 10.0) / 10.0;
double max_rounded = std::floor(max * 10.0) / 10.0;
if (rubber->width() <= 0) {
// no rubber dragged, seek to mouse position
can->seekTo(min);
} else if (rubber->width() > 10) {
emit zoomIn(min_rounded, max_rounded);
emit zoomIn(min, max);
} else {
viewport()->update();
}
@ -827,6 +863,17 @@ void ChartView::hideTip() {
viewport()->update();
}
void ChartView::dragEnterEvent(QDragEnterEvent *event) {
can_drop = event->source() != this;
viewport()->update();
QChartView::dragEnterEvent(event);
}
void ChartView::dragLeaveEvent(QDragLeaveEvent *event) {
can_drop = false;
viewport()->update();
QChartView::dragLeaveEvent(event);
}
void ChartView::dragMoveEvent(QDragMoveEvent *event) {
if (event->mimeData()->hasFormat(mime_type)) {
event->setDropAction(event->source() == this ? Qt::MoveAction : Qt::CopyAction);
@ -834,6 +881,7 @@ void ChartView::dragMoveEvent(QDragMoveEvent *event) {
} else {
event->ignore();
}
charts_widget->startAutoScroll();
}
void ChartView::dropEvent(QDropEvent *event) {
@ -844,11 +892,20 @@ void ChartView::dropEvent(QDropEvent *event) {
} else {
ChartView *source_chart = (ChartView *)event->source();
for (auto &s : source_chart->sigs) {
addSeries(s.msg_id, s.sig);
source_chart->chart()->removeSeries(s.series);
chart()->addSeries(s.series);
s.series->attachAxis(axis_x);
s.series->attachAxis(axis_y);
}
sigs.append(source_chart->sigs);
updateAxisY();
updateTitle();
source_chart->sigs.clear();
emit source_chart->remove();
event->acceptProposedAction();
}
can_drop = false;
} else {
event->ignore();
}
@ -875,6 +932,10 @@ void ChartView::paintEvent(QPaintEvent *event) {
QPainter painter(viewport());
painter.setRenderHints(QPainter::Antialiasing);
painter.drawPixmap(QPoint(), chart_pixmap);
if (can_drop) {
painter.setPen(QPen(palette().color(QPalette::Highlight), 4));
painter.drawRect(viewport()->rect());
}
QRectF exposed_rect = mapToScene(event->region().boundingRect()).boundingRect();
drawForeground(&painter, exposed_rect);
} else {

@ -33,11 +33,12 @@ public:
void paintEvent(QPaintEvent *ev) override;
};
class ChartsWidget;
class ChartView : public QChartView {
Q_OBJECT
public:
ChartView(const std::pair<double, double> &x_range, QWidget *parent = nullptr);
ChartView(const std::pair<double, double> &x_range, ChartsWidget *parent = nullptr);
void addSeries(const MessageId &msg_id, const cabana::Signal *sig);
bool hasSeries(const MessageId &msg_id, const cabana::Signal *sig) const;
void updateSeries(const cabana::Signal *sig = nullptr);
@ -82,6 +83,8 @@ private:
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *ev) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void leaveEvent(QEvent *event) override;
@ -115,7 +118,9 @@ private:
bool is_scrubbing = false;
bool resume_after_scrub = false;
QPixmap chart_pixmap;
bool can_drop = false;
double tooltip_x = -1;
ChartsWidget *charts_widget;
friend class ChartsWidget;
};
@ -148,6 +153,9 @@ private:
void updateState();
void zoomIn(double min, double max);
void zoomReset();
void startAutoScroll();
void stopAutoScroll();
void doAutoScroll();
void updateToolBar();
void setMaxChartRange(int value);
void updateLayout();
@ -181,8 +189,11 @@ private:
QAction *columns_action;
int column_count = 1;
int current_column_count = 0;
int auto_scroll_count = 0;
QTimer auto_scroll_timer;
QTimer align_timer;
friend class ZoomCommand;
friend class ChartView;
};
class ZoomCommand : public QUndoCommand {

@ -19,6 +19,20 @@ static const QColor timeline_colors[] = {
[(int)TimelineType::AlertCritical] = QColor(199, 0, 57),
};
bool sortTimelineBasedOnEventPriority(const std::tuple<int, int, TimelineType> &left, const std::tuple<int, int, TimelineType> &right){
const static std::map<TimelineType, int> timelinePriority = {
{ TimelineType::None, 0 },
{ TimelineType::Engaged, 10 },
{ TimelineType::AlertInfo, 20 },
{ TimelineType::AlertWarning, 30 },
{ TimelineType::AlertCritical, 40 },
{ TimelineType::UserFlag, 35 }
};
return timelinePriority.at(std::get<2>(left)) < timelinePriority.at(std::get<2>(right));
}
VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
auto main_layout = new QVBoxLayout(this);
@ -129,6 +143,8 @@ void VideoWidget::updatePlayBtnState() {
Slider::Slider(QWidget *parent) : timer(this), thumbnail_label(this), QSlider(Qt::Horizontal, parent) {
timer.callOnTimeout([this]() {
timeline = can->getTimeline();
std::sort(timeline.begin(), timeline.end(), sortTimelineBasedOnEventPriority);
update();
});
setMouseTracking(true);
@ -189,6 +205,7 @@ void Slider::paintEvent(QPaintEvent *ev) {
p.fillRect(r, timeline_colors[(int)TimelineType::None]);
double min = minimum() / 1000.0;
double max = maximum() / 1000.0;
for (auto [begin, end, type] : timeline) {
if (begin > max || end < min)
continue;

@ -0,0 +1,100 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget parent="main_window" name="Main Window">
<Tab containers="1" tab_name="SOF / EOF">
<Container>
<DockSplitter orientation="-" sizes="0.500885;0.499115" count="2">
<DockArea name="...">
<plot flip_x="false" mode="TimeSeries" style="Lines" flip_y="false">
<range right="780.030468" bottom="35000000.000000" top="65000000.000000" left="0.108134"/>
<limitY max="6.5e+07" min="3.5e+07"/>
<curve color="#1f77b4" name="/driverEncodeIdx/timestampSof">
<transform alias="/driverEncodeIdx/timestampSof[Derivative]" name="Derivative">
<options lineEdit="1.0" radioChecked="radioCustom"/>
</transform>
</curve>
<curve color="#d62728" name="/roadEncodeIdx/timestampSof">
<transform alias="/roadEncodeIdx/timestampSof[Derivative]" name="Derivative">
<options lineEdit="1.0" radioChecked="radioCustom"/>
</transform>
</curve>
<curve color="#1ac938" name="/wideRoadEncodeIdx/timestampSof">
<transform alias="/wideRoadEncodeIdx/timestampSof[Derivative]" name="Derivative">
<options lineEdit="1.0" radioChecked="radioCustom"/>
</transform>
</curve>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" mode="TimeSeries" style="Lines" flip_y="false">
<range right="780.030468" bottom="35000000.000000" top="65000000.000000" left="0.108134"/>
<limitY max="6.5e+07" min="3.5e+07"/>
<curve color="#f14cc1" name="/driverEncodeIdx/timestampEof">
<transform alias="/driverEncodeIdx/timestampEof[Derivative]" name="Derivative">
<options lineEdit="1.0" radioChecked="radioCustom"/>
</transform>
</curve>
<curve color="#9467bd" name="/roadEncodeIdx/timestampEof">
<transform alias="/roadEncodeIdx/timestampEof[Derivative]" name="Derivative">
<options lineEdit="1.0" radioChecked="radioCustom"/>
</transform>
</curve>
<curve color="#17becf" name="/wideRoadEncodeIdx/timestampEof">
<transform alias="/wideRoadEncodeIdx/timestampEof[Derivative]" name="Derivative">
<options lineEdit="1.0" radioChecked="radioCustom"/>
</transform>
</curve>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab containers="1" tab_name="model timings">
<Container>
<DockSplitter orientation="-" sizes="0.5;0.5" count="2">
<DockArea name="...">
<plot flip_x="false" mode="TimeSeries" style="Lines" flip_y="false">
<range right="780.030468" bottom="0.018421" top="0.030813" left="0.108134"/>
<limitY/>
<curve color="#ff7f0e" name="/modelV2/modelExecutionTime"/>
</plot>
</DockArea>
<DockArea name="...">
<plot flip_x="false" mode="TimeSeries" style="Lines" flip_y="false">
<range right="780.030468" bottom="-0.100000" top="0.100000" left="0.108134"/>
<limitY/>
<curve color="#f14cc1" name="/modelV2/frameDropPerc"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<Tab containers="1" tab_name="sensor info">
<Container>
<DockSplitter orientation="-" sizes="1" count="1">
<DockArea name="...">
<plot flip_x="false" mode="TimeSeries" style="Lines" flip_y="false">
<range right="780.030468" bottom="1.900000" top="2.100000" left="0.108134"/>
<limitY/>
<curve color="#bcbd22" name="/driverCameraState/sensor"/>
<curve color="#1f77b4" name="/roadCameraState/sensor"/>
<curve color="#d62728" name="/wideRoadCameraState/sensor"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="0"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad Rlog"/>
<plugin ID="Cereal Subscriber"/>
</Plugins>
<customMathEquations/>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>

@ -0,0 +1,44 @@
<?xml version='1.0' encoding='UTF-8'?>
<root>
<tabbed_widget name="Main Window" parent="main_window">
<Tab containers="1" tab_name="tab1">
<Container>
<DockSplitter orientation="-" sizes="0.333333;0.333333;0.333333" count="3">
<DockArea name="...">
<plot style="Lines" flip_x="false" flip_y="false" mode="TimeSeries">
<range right="134.825489" top="4402341.574525" bottom="-107369.555525" left="0.000000"/>
<limitY/>
<curve name="/gpsLocationExternal/accuracy" color="#1f77b4"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" flip_x="false" flip_y="false" mode="TimeSeries">
<range right="134.825489" top="1.025000" bottom="-0.025000" left="0.000000"/>
<limitY/>
<curve name="/gpsLocationExternal/flags" color="#d62728"/>
</plot>
</DockArea>
<DockArea name="...">
<plot style="Lines" flip_x="false" flip_y="false" mode="TimeSeries">
<range right="134.825489" top="6.150000" bottom="-0.150000" left="0.000000"/>
<limitY/>
<curve name="/ubloxGnss/measurementReport/numMeas" color="#1ac938"/>
</plot>
</DockArea>
</DockSplitter>
</Container>
</Tab>
<currentTabIndex index="0"/>
</tabbed_widget>
<use_relative_time_offset enabled="1"/>
<!-- - - - - - - - - - - - - - - -->
<!-- - - - - - - - - - - - - - - -->
<Plugins>
<plugin ID="DataLoad Rlog"/>
<plugin ID="Cereal Subscriber"/>
</Plugins>
<customMathEquations/>
<snippets/>
<!-- - - - - - - - - - - - - - - -->
</root>
Loading…
Cancel
Save