Merge remote-tracking branch 'upstream/master' into new-nav-settings-button

pull/29068/head
Shane Smiskol 2 years ago
commit 52fcc9320e
  1. 2
      .python-version
  2. 2
      Dockerfile.openpilot_base
  3. 3
      RELEASES.md
  4. 1
      SConstruct
  5. 2
      cereal
  6. 2
      docs/CARS.md
  7. 2
      launch_env.sh
  8. 2
      mypy.ini
  9. 2
      opendbc
  10. 2
      panda
  11. 6275
      poetry.lock
  12. 37
      pyproject.toml
  13. 4
      selfdrive/athena/athenad.py
  14. 2
      selfdrive/boardd/pandad.py
  15. BIN
      selfdrive/boardd/tests/bootstub.panda_h7.bin
  16. 6
      selfdrive/car/car_helpers.py
  17. 2
      selfdrive/car/docs_definitions.py
  18. 31
      selfdrive/car/gm/interface.py
  19. 3
      selfdrive/car/interfaces.py
  20. 36
      selfdrive/car/tests/test_models.py
  21. 1
      selfdrive/car/torque_data/override.yaml
  22. 1
      selfdrive/car/torque_data/params.yaml
  23. 7
      selfdrive/car/volkswagen/values.py
  24. 4
      selfdrive/controls/controlsd.py
  25. 3
      selfdrive/controls/plannerd.py
  26. 205
      selfdrive/controls/radard.py
  27. 3
      selfdrive/debug/dump.py
  28. 3
      selfdrive/debug/internal/measure_torque_time_to_max.py
  29. 2
      selfdrive/debug/run_process_on_route.py
  30. 10
      selfdrive/locationd/calibrationd.py
  31. 6
      selfdrive/locationd/laikad.py
  32. 3
      selfdrive/locationd/paramsd.py
  33. 3
      selfdrive/locationd/test/_test_locationd_lib.py
  34. 10
      selfdrive/locationd/torqued.py
  35. 8
      selfdrive/navd/navd.py
  36. 2
      selfdrive/test/process_replay/compare_logs.py
  37. 4
      selfdrive/test/process_replay/helpers.py
  38. 5
      selfdrive/test/process_replay/process_replay.py
  39. 5
      selfdrive/test/process_replay/test_fuzzy.py
  40. 3
      selfdrive/ui/SConscript
  41. 203
      selfdrive/ui/qt/maps/map.cc
  42. 41
      selfdrive/ui/qt/maps/map.h
  43. 57
      selfdrive/ui/qt/maps/map_eta.cc
  44. 23
      selfdrive/ui/qt/maps/map_eta.h
  45. 146
      selfdrive/ui/qt/maps/map_instructions.cc
  46. 27
      selfdrive/ui/qt/maps/map_instructions.h
  47. 9
      selfdrive/ui/qt/maps/map_settings.cc
  48. 4
      selfdrive/ui/translations/main_de.ts
  49. 4
      selfdrive/ui/translations/main_ja.ts
  50. 4
      selfdrive/ui/translations/main_ko.ts
  51. 4
      selfdrive/ui/translations/main_pt-BR.ts
  52. 4
      selfdrive/ui/translations/main_zh-CHS.ts
  53. 4
      selfdrive/ui/translations/main_zh-CHT.ts
  54. 44
      system/hardware/tici/agnos.json
  55. 2
      system/sensord/rawgps/compare.py

@ -1 +1 @@
3.8.10 3.11.4

@ -13,7 +13,7 @@ ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8 ENV LC_ALL en_US.UTF-8
ENV POETRY_VIRTUALENVS_CREATE=false ENV POETRY_VIRTUALENVS_CREATE=false
ENV PYENV_VERSION=3.8.10 ENV PYENV_VERSION=3.11.4
ENV PYENV_ROOT="/root/.pyenv" ENV PYENV_ROOT="/root/.pyenv"
ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH" ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"

@ -8,7 +8,8 @@ Version 0.9.4 (2023-XX-XX)
* Navigation settings moved to home screen and map * Navigation settings moved to home screen and map
* UI alerts rework * UI alerts rework
* Border color always shows engagement status. Blue means disengaged, green means engaged, and grey means engaged with human overriding * Border color always shows engagement status. Blue means disengaged, green means engaged, and grey means engaged with human overriding
* Alerts are shown inside the border. Black/grey means info, orange means warning, and red means critical alert * Alerts are shown inside the border. Black/grey means info, orange means warning, and red means critical alert
* Bookmarked segments are preserved on the device's storage
* Ford Focus 2018 support * Ford Focus 2018 support
* Kia Carnival 2023 support thanks to sunnyhaibin! * Kia Carnival 2023 support thanks to sunnyhaibin!

@ -263,6 +263,7 @@ py_include = sysconfig.get_paths()['include']
envCython = env.Clone() envCython = env.Clone()
envCython["CPPPATH"] += [py_include, np.get_include()] envCython["CPPPATH"] += [py_include, np.get_include()]
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-shadow", "-Wno-deprecated-declarations"] envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-shadow", "-Wno-deprecated-declarations"]
envCython["CCFLAGS"].remove("-Werror")
envCython["LIBS"] = [] envCython["LIBS"] = []
if arch == "Darwin": if arch == "Darwin":

@ -1 +1 @@
Subproject commit 6f7102581f57eb5074b816cc2cfd984218916773 Subproject commit aed9fd278a704816aba11f4473aafefc281ed2bc

@ -183,7 +183,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Škoda|Kodiaq 2017-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Kodiaq 2017-23">Buy Here</a></sub></details>|| |Škoda|Kodiaq 2017-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Kodiaq 2017-23">Buy Here</a></sub></details>||
|Škoda|Octavia 2015, 2018-19[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Octavia 2015, 2018-19">Buy Here</a></sub></details>|| |Škoda|Octavia 2015, 2018-19[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Octavia 2015, 2018-19">Buy Here</a></sub></details>||
|Škoda|Octavia RS 2016[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Octavia RS 2016">Buy Here</a></sub></details>|| |Škoda|Octavia RS 2016[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Octavia RS 2016">Buy Here</a></sub></details>||
|Škoda|Scala 2020[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Scala 2020">Buy Here</a></sub></details>[<sup>13</sup>](#footnotes)|| |Škoda|Scala 2020-23[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Scala 2020-23">Buy Here</a></sub></details>[<sup>13</sup>](#footnotes)||
|Škoda|Superb 2015-22[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Superb 2015-22">Buy Here</a></sub></details>|| |Škoda|Superb 2015-22[<sup>11</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Škoda&model=Superb 2015-22">Buy Here</a></sub></details>||
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota connector<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Alphard 2019-20">Buy Here</a></sub></details>|| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota connector<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Alphard 2019-20">Buy Here</a></sub></details>||
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota connector<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Alphard Hybrid 2021">Buy Here</a></sub></details>|| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota connector<br>- 1 comma power v2<br>- 1 comma three<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=Toyota&model=Alphard Hybrid 2021">Buy Here</a></sub></details>||

@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1 export VECLIB_MAXIMUM_THREADS=1
if [ -z "$AGNOS_VERSION" ]; then if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="7.1" export AGNOS_VERSION="8.2"
fi fi
if [ -z "$PASSIVE" ]; then if [ -z "$PASSIVE" ]; then

@ -1,5 +1,5 @@
[mypy] [mypy]
python_version = 3.8 python_version = 3.11
plugins = numpy.typing.mypy_plugin plugins = numpy.typing.mypy_plugin
files = body, common, docs, scripts, selfdrive, site_scons, system, tools files = body, common, docs, scripts, selfdrive, site_scons, system, tools
exclude = ^(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/) exclude = ^(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)

@ -1 +1 @@
Subproject commit 3ef35ed2298a3a9d199f9145409547710065884c Subproject commit 4231b0f12d8cf10d0554c4eb513ac984defc1f90

@ -1 +1 @@
Subproject commit 5d873444b2cf801ba73f4a457993260df3a412b8 Subproject commit dd78b2bf6c9d63ef59e81d0c400e85c8b477a8be

6275
poetry.lock generated

File diff suppressed because it is too large Load Diff

@ -10,13 +10,13 @@ documentation = "https://docs.comma.ai"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~3.8" python = "~3.11"
atomicwrites = "^1.4.0" atomicwrites = "^1.4.0"
casadi = "==3.6.3" casadi = "==3.6.3"
cffi = "^1.15.1" cffi = "^1.15.1"
crcmod = "^1.7" crcmod = "^1.7"
cryptography = "^37.0.4" cryptography = "^37.0.4"
Cython = "^0.29.30" Cython = "^3.0.0"
flake8 = "^4.0.1" flake8 = "^4.0.1"
Flask = "^2.1.2" Flask = "^2.1.2"
future-fstrings = "^1.2.0" # for acados future-fstrings = "^1.2.0" # for acados
@ -27,14 +27,14 @@ Jinja2 = "^3.1.2"
json-rpc = "^1.13.0" json-rpc = "^1.13.0"
libusb1 = "^3.0.0" libusb1 = "^3.0.0"
nose = "^1.3.7" nose = "^1.3.7"
numpy = "^1.23.0" numpy = "==1.23.0" # locked pending deprecation fixes in xx
onnx = "^1.14.0" onnx = "^1.14.0"
onnxruntime-gpu = { version = "^1.15.1", platform = "linux", markers = "platform_machine == 'x86_64'" } onnxruntime-gpu = { version = "^1.15.1", platform = "linux", markers = "platform_machine == 'x86_64'" }
pillow = "^9.2.0" pillow = "^9.2.0"
poetry = "==1.2.2" poetry = "==1.2.2"
protobuf = "==3.20.3" protobuf = "==3.20.3"
psutil = "^5.9.1" psutil = "^5.9.1"
pycapnp = "==1.1.0" pycapnp = "^1.3.0"
pycryptodome = "^3.15.0" pycryptodome = "^3.15.0"
PyJWT = "^2.5.0" PyJWT = "^2.5.0"
pyopencl = "^2022.2.4" pyopencl = "^2022.2.4"
@ -43,7 +43,7 @@ python-dateutil = "^2.8.2"
PyYAML = "^6.0" PyYAML = "^6.0"
pyzmq = "^23.2.0" pyzmq = "^23.2.0"
requests = "^2.28.1" requests = "^2.28.1"
scons = "^4.3.0" scons = "^4.5.2"
sentry-sdk = "^1.6.0" sentry-sdk = "^1.6.0"
setproctitle = "^1.2.3" setproctitle = "^1.2.3"
six = "^1.16.0" six = "^1.16.0"
@ -51,8 +51,8 @@ smbus2 = "^0.4.2"
sounddevice = "^0.4.5" sounddevice = "^0.4.5"
spidev = { version = "^3.6", platform = "linux" } spidev = { version = "^3.6", platform = "linux" }
spidev2 = { version = "^0.9.0", platform = "linux" } spidev2 = { version = "^0.9.0", platform = "linux" }
sympy = "^1.10.1" sympy = "^1.11.1"
timezonefinder = "^6.0.1" timezonefinder = "^6.2.0"
tqdm = "^4.64.0" tqdm = "^4.64.0"
urllib3 = "^1.26.10" urllib3 = "^1.26.10"
utm = "^0.7.0" utm = "^0.7.0"
@ -62,10 +62,10 @@ sconscontrib = {git = "https://github.com/SCons/scons-contrib.git"}
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
av = "^9.2.0" av = "^10.0.0"
azure-storage-blob = "~2.1" azure-storage-blob = "~2.1"
breathe = "^4.34.0" breathe = "^4.34.0"
carla = { version = "==0.9.13", platform = "linux", markers = "platform_machine == 'x86_64'" } carla = { url = "https://github.com/commaai/carla/releases/download/3.11.4/carla-0.9.14-cp311-cp311-linux_x86_64.whl", platform = "linux", markers = "platform_machine == 'x86_64'" }
control = "^0.9.2" control = "^0.9.2"
coverage = "^6.4.1" coverage = "^6.4.1"
dictdiffer = "^0.9.0" dictdiffer = "^0.9.0"
@ -79,24 +79,23 @@ lxml = "^4.9.1"
markdown-it-py = "^2.1.0" markdown-it-py = "^2.1.0"
matplotlib = "^3.5.2" matplotlib = "^3.5.2"
mpld3 = "^0.5.8" mpld3 = "^0.5.8"
mypy = "^0.961" mypy = "^1.4.0"
myst-parser = "^0.18.0" myst-parser = "^0.18.0"
natsort = "^8.1.0" natsort = "^8.1.0"
numpy = "^1.23.0" opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu118-cp311/opencv_python_headless-4.5.5.64-cp311-cp311-manylinux_2_31_x86_64.whl", platform = "linux" }
opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu118/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl", platform = "linux" }
pandas = "^1.4.3" pandas = "^1.4.3"
parameterized = "^0.8.1" parameterized = "^0.8.1"
paramiko = "^2.11.0" paramiko = "^2.11.0"
pprofile = "^2.1.0" pprofile = "^2.1.0"
pre-commit = "^2.19.0" pre-commit = "^2.19.0"
pycurl = "^7.45.1" pycurl = "^7.45.1"
pygame = "^2.1.2" pygame = "^2.4.0"
pylint = "^2.17.4" pylint = "^2.17.4"
pyprof2calltree = "^1.4.5" pyprof2calltree = "^1.4.5"
pytest = "^7.1.2" pytest = "^7.1.2"
pytest-xdist = "^2.5.0" pytest-xdist = "^2.5.0"
reverse_geocoder = "^1.5.1" reverse_geocoder = "^1.5.1"
scipy = "^1.8.1" scipy = "==1.9.3" # pinned until xx refs changes can be checked
sphinx = "^5.0.2" sphinx = "^5.0.2"
sphinx-rtd-theme = "^1.0.0" sphinx-rtd-theme = "^1.0.0"
sphinx-sitemap = "^2.2.0" sphinx-sitemap = "^2.2.0"
@ -118,7 +117,6 @@ optional = true
aenum = "^3.1.11" aenum = "^3.1.11"
aiohttp = "^3.8.1" aiohttp = "^3.8.1"
albumentations = "^1.2.1" albumentations = "^1.2.1"
apex = { url = "https://github.com/commaai/apex/releases/download/pytorch2.0.0%2Bcu11.8/apex-0.1-cp38-cp38-linux_x86_64.whl" }
azure-cli-core = "^2.38.0" azure-cli-core = "^2.38.0"
azure-common = "^1.1.28" azure-common = "^1.1.28"
azure-core = "^1.24.2" azure-core = "^1.24.2"
@ -164,20 +162,21 @@ redis = "^4.3.4"
s2sphere = "^0.2.5" s2sphere = "^0.2.5"
scikit-image = "^0.19.3" scikit-image = "^0.19.3"
scikit-learn = "^1.1.1" scikit-learn = "^1.1.1"
segmentation-models-pytorch = "==0.3.2" segmentation-models-pytorch = "==0.3.3"
simplejson = "^3.17.6" simplejson = "^3.17.6"
SQLAlchemy = "^1.4.39" SQLAlchemy = "^1.4.39"
torch = { url = "https://download.pytorch.org/whl/cu118/torch-2.0.0%2Bcu118-cp38-cp38-linux_x86_64.whl" } torch = { url = "https://download.pytorch.org/whl/cu118/torch-2.0.1%2Bcu118-cp311-cp311-linux_x86_64.whl" }
torchsummary = "^1.5.1" torchsummary = "^1.5.1"
torchvision = { url = "https://download.pytorch.org/whl/cu118/torchvision-0.15.1%2Bcu118-cp38-cp38-linux_x86_64.whl" } torchvision = { url = "https://download.pytorch.org/whl/cu118/torchvision-0.15.2%2Bcu118-cp311-cp311-linux_x86_64.whl" }
triton = "^2.0.0" triton = "^2.0.0"
Werkzeug = "^2.1.2" Werkzeug = "^2.1.2"
zerorpc = { git = "https://github.com/commaai/zerorpc-python.git", branch = "master" } zerorpc = { git = "https://github.com/commaai/zerorpc-python.git", branch = "master" }
omegaconf = "^2.3.0" omegaconf = "^2.3.0"
osmnx = "==1.2.2" osmnx = "==1.2.2"
tritonclient = {version = "2.28.0", extras = ["http"]} tritonclient = {version = "2.28.0", extras = ["http"]}
tensorrt = "^8.6.0"
transformers = "^4.29.2" transformers = "^4.29.2"
timm = "==0.9.2"
PyNvCodec = { git = "https://github.com/NVIDIA/VideoProcessingFramework.git", rev = "3347e55" }
[build-system] [build-system]

@ -798,7 +798,9 @@ def main(exit_event: Optional[threading.Event] = None):
except (ConnectionError, TimeoutError, WebSocketException): except (ConnectionError, TimeoutError, WebSocketException):
conn_retries += 1 conn_retries += 1
params.remove("LastAthenaPingTime") params.remove("LastAthenaPingTime")
except socket.timeout: # TODO: socket.timeout and TimeoutError are now the same exception since python3.10
# Remove the socket.timeout case once we have fully moved to python3.11
except socket.timeout: # pylint: disable=duplicate-except
params.remove("LastAthenaPingTime") params.remove("LastAthenaPingTime")
except Exception: except Exception:
cloudlog.exception("athenad.main.exception") cloudlog.exception("athenad.main.exception")

@ -169,7 +169,7 @@ def main() -> NoReturn:
# sort pandas to have deterministic order # sort pandas to have deterministic order
pandas.sort(key=cmp_to_key(panda_sort_cmp)) pandas.sort(key=cmp_to_key(panda_sort_cmp))
panda_serials = list(map(lambda p: p.get_usb_serial(), pandas)) # type: ignore panda_serials = list(map(lambda p: p.get_usb_serial(), pandas))
# log panda fw versions # log panda fw versions
params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas)) params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas))

@ -89,9 +89,9 @@ def fingerprint(logcan, sendcan, num_pandas):
cached_params = params.get("CarParamsCache") cached_params = params.get("CarParamsCache")
if cached_params is not None: if cached_params is not None:
cached_params = car.CarParams.from_bytes(cached_params) with car.CarParams.from_bytes(cached_params) as cached_params:
if cached_params.carName == "mock": if cached_params.carName == "mock":
cached_params = None cached_params = None
if cached_params is not None and len(cached_params.carFw) > 0 and \ if cached_params is not None and len(cached_params.carFw) > 0 and \
cached_params.carVin is not VIN_UNKNOWN and not disable_fw_cache: cached_params.carVin is not VIN_UNKNOWN and not disable_fw_cache:

@ -149,7 +149,7 @@ class CarParts:
return copy.deepcopy(self) return copy.deepcopy(self)
@classmethod @classmethod
def common(cls, add: List[EnumBase] = None, remove: List[EnumBase] = None): def common(cls, add: Optional[List[EnumBase]] = None, remove: Optional[List[EnumBase]] = None):
p = [part for part in (add or []) + DEFAULT_CAR_PARTS if part not in (remove or [])] p = [part for part in (add or []) + DEFAULT_CAR_PARTS if part not in (remove or [])]
return cls(p) return cls(p)

@ -19,6 +19,12 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D
CruiseButtons.MAIN: ButtonType.altButton3, CruiseButtons.CANCEL: ButtonType.cancel} CruiseButtons.MAIN: ButtonType.altButton3, CruiseButtons.CANCEL: ButtonType.cancel}
NON_LINEAR_TORQUE_PARAMS = {
CAR.BOLT_EUV: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178],
CAR.ACADIA: [4.78003305, 1.0, 0.3122, 0.05591772]
}
class CarInterface(CarInterfaceBase): class CarInterface(CarInterfaceBase):
@staticmethod @staticmethod
def get_pid_accel_limits(CP, current_speed, cruise_speed): def get_pid_accel_limits(CP, current_speed, cruise_speed):
@ -31,23 +37,14 @@ class CarInterface(CarInterfaceBase):
sigmoid = desired_angle / (1 + fabs(desired_angle)) sigmoid = desired_angle / (1 + fabs(desired_angle))
return 0.10006696 * sigmoid * (v_ego + 3.12485927) return 0.10006696 * sigmoid * (v_ego + 3.12485927)
@staticmethod
def get_steer_feedforward_acadia(desired_angle, v_ego):
desired_angle *= 0.09760208
sigmoid = desired_angle / (1 + fabs(desired_angle))
return 0.04689655 * sigmoid * (v_ego + 10.028217)
def get_steer_feedforward_function(self): def get_steer_feedforward_function(self):
if self.CP.carFingerprint == CAR.VOLT: if self.CP.carFingerprint == CAR.VOLT:
return self.get_steer_feedforward_volt return self.get_steer_feedforward_volt
elif self.CP.carFingerprint == CAR.ACADIA:
return self.get_steer_feedforward_acadia
else: else:
return CarInterfaceBase.get_steer_feedforward_default return CarInterfaceBase.get_steer_feedforward_default
@staticmethod def torque_from_lateral_accel_siglin(self, lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning,
def torque_from_lateral_accel_bolt(lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning, lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float:
lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float:
friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation)
def sig(val): def sig(val):
@ -57,14 +54,15 @@ class CarInterface(CarInterfaceBase):
# An important thing to consider is that the slope at 0 should be > 0 (ideally >1) # An important thing to consider is that the slope at 0 should be > 0 (ideally >1)
# This has big effect on the stability about 0 (noise when going straight) # This has big effect on the stability about 0 (noise when going straight)
# ToDo: To generalize to other GMs, explore tanh function as the nonlinear # ToDo: To generalize to other GMs, explore tanh function as the nonlinear
a, b, c, _ = [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178] # weights computed offline non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS.get(self.CP.carFingerprint)
assert non_linear_torque_params, "The params are not defined"
a, b, c, _ = non_linear_torque_params
steer_torque = (sig(lateral_accel_value * a) * b) + (lateral_accel_value * c) steer_torque = (sig(lateral_accel_value * a) * b) + (lateral_accel_value * c)
return float(steer_torque) + friction return float(steer_torque) + friction
def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
if self.CP.carFingerprint == CAR.BOLT_EUV: if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS:
return self.torque_from_lateral_accel_bolt return self.torque_from_lateral_accel_siglin
else: else:
return self.torque_from_lateral_accel_linear return self.torque_from_lateral_accel_linear
@ -169,7 +167,8 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.86 ret.wheelbase = 2.86
ret.steerRatio = 14.4 # end to end is 13.46 ret.steerRatio = 14.4 # end to end is 13.46
ret.centerToFront = ret.wheelbase * 0.4 ret.centerToFront = ret.wheelbase * 0.4
ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_acadia() ret.steerActuatorDelay = 0.2
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.BUICK_LACROSSE: elif candidate == CAR.BUICK_LACROSSE:
ret.mass = 1712. + STD_CARGO_KG ret.mass = 1712. + STD_CARGO_KG

@ -131,8 +131,7 @@ class CarInterfaceBase(ABC):
def get_steer_feedforward_function(self): def get_steer_feedforward_function(self):
return self.get_steer_feedforward_default return self.get_steer_feedforward_default
@staticmethod def torque_from_lateral_accel_linear(self, lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning,
def torque_from_lateral_accel_linear(lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning,
lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float: lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float:
# The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction) # The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction)
friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation)

@ -201,6 +201,42 @@ class TestCarModelBase(unittest.TestCase):
self.assertTrue(self.safety.addr_checks_valid()) self.assertTrue(self.safety.addr_checks_valid())
self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}") self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}")
def test_panda_safety_tx_cases(self, data=None):
"""Asserts we can tx common messages"""
if self.CP.notCar:
self.skipTest("Skipping test for notCar")
def test_car_controller(car_control):
now_nanos = 0
msgs_sent = 0
CI = self.CarInterface(self.CP, self.CarController, self.CarState)
for _ in range(round(10.0 / DT_CTRL)): # make sure we hit the slowest messages
CI.update(car_control, [])
_, sendcan = CI.apply(car_control, now_nanos)
now_nanos += DT_CTRL * 1e9
msgs_sent += len(sendcan)
for addr, _, dat, bus in sendcan:
to_send = libpanda_py.make_CANPacket(addr, bus % 4, dat)
self.assertTrue(self.safety.safety_tx_hook(to_send), (addr, dat, bus))
# Make sure we attempted to send messages
self.assertGreater(msgs_sent, 50)
# Make sure we can send all messages while inactive
CC = car.CarControl.new_message()
test_car_controller(CC)
# Test cancel + general messages (controls_allowed=False & cruise_engaged=True)
self.safety.set_cruise_engaged_prev(True)
CC = car.CarControl.new_message(cruiseControl={'cancel': True})
test_car_controller(CC)
# Test resume + general messages (controls_allowed=True & cruise_engaged=True)
self.safety.set_controls_allowed(True)
CC = car.CarControl.new_message(cruiseControl={'resume': True})
test_car_controller(CC)
def test_panda_safety_carstate(self): def test_panda_safety_carstate(self):
""" """
Assert that panda safety matches openpilot's carState Assert that panda safety matches openpilot's carState

@ -49,6 +49,7 @@ KIA NIRO HYBRID 2ND GEN: [2.42, 2.5, 0.12]
KIA NIRO EV 2ND GEN: [2.05, 2.5, 0.14] KIA NIRO EV 2ND GEN: [2.05, 2.5, 0.14]
GENESIS GV80 2023: [2.5, 2.5, 0.1] GENESIS GV80 2023: [2.5, 2.5, 0.1]
KIA CARNIVAL 4TH GEN: [1.75, 1.75, 0.15] KIA CARNIVAL 4TH GEN: [1.75, 1.75, 0.15]
GMC ACADIA DENALI 2018: [1.6, 1.6, 0.2]
# Dashcam or fallback configured as ideal car # Dashcam or fallback configured as ideal car
mock: [10.0, 10, 0.0] mock: [10.0, 10, 0.0]

@ -10,7 +10,6 @@ CHRYSLER PACIFICA HYBRID 2017: [1.79422, 1.06831764583744, 0.116237]
CHRYSLER PACIFICA HYBRID 2018: [2.08887, 1.2943025830995154, 0.114818] CHRYSLER PACIFICA HYBRID 2018: [2.08887, 1.2943025830995154, 0.114818]
CHRYSLER PACIFICA HYBRID 2019: [1.90120, 1.1958788168371808, 0.131520] CHRYSLER PACIFICA HYBRID 2019: [1.90120, 1.1958788168371808, 0.131520]
GENESIS G70 2018: [3.8520195946707947, 2.354697063349854, 0.06830285485626221] GENESIS G70 2018: [3.8520195946707947, 2.354697063349854, 0.06830285485626221]
GMC ACADIA DENALI 2018: [1.3181430320331884, 1.1853735340610179, 0.3450592280031644]
HONDA ACCORD 2018: [1.7135052593468778, 0.3461280068322071, 0.21579936052863807] HONDA ACCORD 2018: [1.7135052593468778, 0.3461280068322071, 0.21579936052863807]
HONDA ACCORD HYBRID 2018: [1.6651615004829625, 0.30322180951193245, 0.2083000440586149] HONDA ACCORD HYBRID 2018: [1.6651615004829625, 0.30322180951193245, 0.2083000440586149]
HONDA CIVIC (BOSCH) 2019: [1.691708637466905, 0.40132900729454185, 0.25460295304024094] HONDA CIVIC (BOSCH) 2019: [1.691708637466905, 0.40132900729454185, 0.25460295304024094]

@ -262,7 +262,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = {
CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]),
CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"), CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"),
CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2017-23"), CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2017-23"),
CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_MQB_A0]), CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]),
CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"), CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"),
CAR.SKODA_OCTAVIA_MK3: [ CAR.SKODA_OCTAVIA_MK3: [
VWCarInfo("Škoda Octavia 2015, 2018-19"), VWCarInfo("Škoda Octavia 2015, 2018-19"),
@ -1280,19 +1280,24 @@ FW_VERSIONS = {
CAR.SKODA_SCALA_MK1: { CAR.SKODA_SCALA_MK1: {
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xf1\x8704C906025AK\xf1\x897053', b'\xf1\x8704C906025AK\xf1\x897053',
b'\xf1\x8705C906032M \xf1\x892365',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x870CW300020 \xf1\x891907',
b'\xf1\x870CW300050 \xf1\x891709', b'\xf1\x870CW300050 \xf1\x891709',
], ],
(Ecu.srs, 0x715, None): [ (Ecu.srs, 0x715, None): [
b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14', b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14',
b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\022111104111104112104040404111111112H14', b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\022111104111104112104040404111111112H14',
b'\xf1\x872Q0959655AS\xf1\x890411\xf1\x82\x1311150411110411210404040417151215391413',
], ],
(Ecu.eps, 0x712, None): [ (Ecu.eps, 0x712, None): [
b'\xf1\x872Q1909144M \xf1\x896041', b'\xf1\x872Q1909144M \xf1\x896041',
b'\xf1\x872Q1909144AB\xf1\x896050',
], ],
(Ecu.fwdRadar, 0x757, None): [ (Ecu.fwdRadar, 0x757, None): [
b'\xf1\x872Q0907572R \xf1\x890372', b'\xf1\x872Q0907572R \xf1\x890372',
b'\xf1\x872Q0907572AA\xf1\x890396',
], ],
}, },
CAR.SKODA_SUPERB_MK3: { CAR.SKODA_SUPERB_MK3: {

@ -206,8 +206,8 @@ class Controls:
if REPLAY: if REPLAY:
controls_state = Params().get("ReplayControlsState") controls_state = Params().get("ReplayControlsState")
if controls_state is not None: if controls_state is not None:
controls_state = log.ControlsState.from_bytes(controls_state) with log.ControlsState.from_bytes(controls_state) as controls_state:
self.v_cruise_helper.v_cruise_kph = controls_state.vCruise self.v_cruise_helper.v_cruise_kph = controls_state.vCruise
if any(ps.controlsAllowed for ps in self.sm['pandaStates']): if any(ps.controlsAllowed for ps in self.sm['pandaStates']):
self.state = State.enabled self.state = State.enabled

@ -32,7 +32,8 @@ def plannerd_thread(sm=None, pm=None):
cloudlog.info("plannerd is waiting for CarParams") cloudlog.info("plannerd is waiting for CarParams")
params = Params() params = Params()
CP = car.CarParams.from_bytes(params.get("CarParams", block=True)) with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg:
CP = msg
cloudlog.info("plannerd got CarParams: %s", CP.carName) cloudlog.info("plannerd got CarParams: %s", CP.carName)
debug_mode = bool(int(os.getenv("DEBUG", "0"))) debug_mode = bool(int(os.getenv("DEBUG", "0")))

@ -1,10 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import importlib import importlib
import math import math
from collections import defaultdict, deque from collections import deque
from typing import Optional, Dict, Any
import cereal.messaging as messaging import capnp
from cereal import car from cereal import messaging, log, car
from common.numpy_fast import interp from common.numpy_fast import interp
from common.params import Params from common.params import Params
from common.realtime import Ratekeeper, Priority, config_realtime_process from common.realtime import Ratekeeper, Priority, config_realtime_process
@ -17,33 +18,39 @@ from common.kalman.simple_kalman import KF1D
_LEAD_ACCEL_TAU = 1.5 _LEAD_ACCEL_TAU = 1.5
# radar tracks # radar tracks
SPEED, ACCEL = 0, 1 # Kalman filter states enum SPEED, ACCEL = 0, 1 # Kalman filter states enum
# stationary qualification parameters # stationary qualification parameters
v_ego_stationary = 4. # no stationary object flag below this speed V_EGO_STATIONARY = 4. # no stationary object flag below this speed
RADAR_TO_CENTER = 2.7 # (deprecated) RADAR is ~ 2.7m ahead from center of car RADAR_TO_CENTER = 2.7 # (deprecated) RADAR is ~ 2.7m ahead from center of car
RADAR_TO_CAMERA = 1.52 # RADAR is ~ 1.5m ahead from center of mesh frame RADAR_TO_CAMERA = 1.52 # RADAR is ~ 1.5m ahead from center of mesh frame
def get_RadarState_from_vision(lead_msg, v_ego, model_v_ego): class KalmanParams:
lead_v_rel_pred = lead_msg.v[0] - model_v_ego def __init__(self, dt: float):
return { # Lead Kalman Filter params, calculating K from A, C, Q, R requires the control library.
"dRel": float(lead_msg.x[0] - RADAR_TO_CAMERA), # hardcoding a lookup table to compute K for values of radar_ts between 0.01s and 0.2s
"yRel": float(-lead_msg.y[0]), assert dt > .01 and dt < .2, "Radar time step must be between .01s and 0.2s"
"vRel": float(lead_v_rel_pred), self.A = [[1.0, dt], [0.0, 1.0]]
"vLead": float(v_ego + lead_v_rel_pred), self.C = [1.0, 0.0]
"vLeadK": float(v_ego + lead_v_rel_pred), #Q = np.matrix([[10., 0.0], [0.0, 100.]])
"aLeadK": 0.0, #R = 1e3
"aLeadTau": 0.3, #K = np.matrix([[ 0.05705578], [ 0.03073241]])
"fcw": False, dts = [dt * 0.01 for dt in range(1, 21)]
"modelProb": float(lead_msg.prob), K0 = [0.12287673, 0.14556536, 0.16522756, 0.18281627, 0.1988689, 0.21372394,
"radar": False, 0.22761098, 0.24069424, 0.253096, 0.26491023, 0.27621103, 0.28705801,
"status": True 0.29750003, 0.30757767, 0.31732515, 0.32677158, 0.33594201, 0.34485814,
} 0.35353899, 0.36200124]
K1 = [0.29666309, 0.29330885, 0.29042818, 0.28787125, 0.28555364, 0.28342219,
0.28144091, 0.27958406, 0.27783249, 0.27617149, 0.27458948, 0.27307714,
0.27162685, 0.27023228, 0.26888809, 0.26758976, 0.26633338, 0.26511557,
0.26393339, 0.26278425]
self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]]
class Track():
def __init__(self, v_lead, kalman_params): class Track:
def __init__(self, v_lead: float, kalman_params: KalmanParams):
self.cnt = 0 self.cnt = 0
self.aLeadTau = _LEAD_ACCEL_TAU self.aLeadTau = _LEAD_ACCEL_TAU
self.K_A = kalman_params.A self.K_A = kalman_params.A
@ -51,7 +58,7 @@ class Track():
self.K_K = kalman_params.K self.K_K = kalman_params.K
self.kf = KF1D([[v_lead], [0.0]], self.K_A, self.K_C, self.K_K) self.kf = KF1D([[v_lead], [0.0]], self.K_A, self.K_C, self.K_K)
def update(self, d_rel, y_rel, v_rel, v_lead, measured): def update(self, d_rel: float, y_rel: float, v_rel: float, v_lead: float, measured: float):
# relative values, copy # relative values, copy
self.dRel = d_rel # LONG_DIST self.dRel = d_rel # LONG_DIST
self.yRel = y_rel # -LAT_DIST self.yRel = y_rel # -LAT_DIST
@ -78,13 +85,12 @@ class Track():
# Weigh y higher since radar is inaccurate in this dimension # Weigh y higher since radar is inaccurate in this dimension
return [self.dRel, self.yRel*2, self.vRel] return [self.dRel, self.yRel*2, self.vRel]
def reset_a_lead(self, aLeadK, aLeadTau): def reset_a_lead(self, aLeadK: float, aLeadTau: float):
self.kf = KF1D([[self.vLead], [aLeadK]], self.K_A, self.K_C, self.K_K) self.kf = KF1D([[self.vLead], [aLeadK]], self.K_A, self.K_C, self.K_K)
self.aLeadK = aLeadK self.aLeadK = aLeadK
self.aLeadTau = aLeadTau self.aLeadTau = aLeadTau
def get_RadarState(self, model_prob: float = 0.0):
def get_RadarState(self, model_prob=0.0):
return { return {
"dRel": float(self.dRel), "dRel": float(self.dRel),
"yRel": float(self.yRel), "yRel": float(self.yRel),
@ -99,47 +105,25 @@ class Track():
"aLeadTau": float(self.aLeadTau) "aLeadTau": float(self.aLeadTau)
} }
def __str__(self): def potential_low_speed_lead(self, v_ego: float):
ret = f"x: {self.dRel:4.1f} y: {self.yRel:4.1f} v: {self.vRel:4.1f} a: {self.aLeadK:4.1f}"
return ret
def potential_low_speed_lead(self, v_ego):
# stop for stuff in front of you and low speed, even without model confirmation # stop for stuff in front of you and low speed, even without model confirmation
# Radar points closer than 0.75, are almost always glitches on toyota radars # Radar points closer than 0.75, are almost always glitches on toyota radars
return abs(self.yRel) < 1.0 and (v_ego < v_ego_stationary) and (0.75 < self.dRel < 25) return abs(self.yRel) < 1.0 and (v_ego < V_EGO_STATIONARY) and (0.75 < self.dRel < 25)
def is_potential_fcw(self, model_prob): def is_potential_fcw(self, model_prob: float):
return model_prob > .9 return model_prob > .9
def __str__(self):
class KalmanParams(): ret = f"x: {self.dRel:4.1f} y: {self.yRel:4.1f} v: {self.vRel:4.1f} a: {self.aLeadK:4.1f}"
def __init__(self, dt): return ret
# Lead Kalman Filter params, calculating K from A, C, Q, R requires the control library.
# hardcoding a lookup table to compute K for values of radar_ts between 0.01s and 0.2s
assert dt > .01 and dt < .2, "Radar time step must be between .01s and 0.2s"
self.A = [[1.0, dt], [0.0, 1.0]]
self.C = [1.0, 0.0]
#Q = np.matrix([[10., 0.0], [0.0, 100.]])
#R = 1e3
#K = np.matrix([[ 0.05705578], [ 0.03073241]])
dts = [dt * 0.01 for dt in range(1, 21)]
K0 = [0.12287673, 0.14556536, 0.16522756, 0.18281627, 0.1988689, 0.21372394,
0.22761098, 0.24069424, 0.253096, 0.26491023, 0.27621103, 0.28705801,
0.29750003, 0.30757767, 0.31732515, 0.32677158, 0.33594201, 0.34485814,
0.35353899, 0.36200124]
K1 = [0.29666309, 0.29330885, 0.29042818, 0.28787125, 0.28555364, 0.28342219,
0.28144091, 0.27958406, 0.27783249, 0.27617149, 0.27458948, 0.27307714,
0.27162685, 0.27023228, 0.26888809, 0.26758976, 0.26633338, 0.26511557,
0.26393339, 0.26278425]
self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]]
def laplacian_pdf(x, mu, b): def laplacian_pdf(x: float, mu: float, b: float):
b = max(b, 1e-4) b = max(b, 1e-4)
return math.exp(-abs(x-mu)/b) return math.exp(-abs(x-mu)/b)
def match_vision_to_track(v_ego, lead, tracks): def match_vision_to_track(v_ego: float, lead: capnp._DynamicStructReader, tracks: Dict[int, Track]):
offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA
def prob(c): def prob(c):
@ -162,7 +146,24 @@ def match_vision_to_track(v_ego, lead, tracks):
return None return None
def get_lead(v_ego, ready, tracks, lead_msg, model_v_ego, low_speed_override=True): def get_RadarState_from_vision(lead_msg: capnp._DynamicStructReader, v_ego: float, model_v_ego: float):
lead_v_rel_pred = lead_msg.v[0] - model_v_ego
return {
"dRel": float(lead_msg.x[0] - RADAR_TO_CAMERA),
"yRel": float(-lead_msg.y[0]),
"vRel": float(lead_v_rel_pred),
"vLead": float(v_ego + lead_v_rel_pred),
"vLeadK": float(v_ego + lead_v_rel_pred),
"aLeadK": 0.0,
"aLeadTau": 0.3,
"fcw": False,
"modelProb": float(lead_msg.prob),
"radar": False,
"status": True
}
def get_lead(v_ego: float, ready: bool, tracks: Dict[int, Track], lead_msg: capnp._DynamicStructReader, model_v_ego: float, low_speed_override: bool = True) -> Dict[str, Any]:
# Determine leads, this is where the essential logic happens # Determine leads, this is where the essential logic happens
if len(tracks) > 0 and ready and lead_msg.prob > .5: if len(tracks) > 0 and ready and lead_msg.prob > .5:
track = match_vision_to_track(v_ego, lead_msg, tracks) track = match_vision_to_track(v_ego, lead_msg, tracks)
@ -187,22 +188,30 @@ def get_lead(v_ego, ready, tracks, lead_msg, model_v_ego, low_speed_override=Tru
return lead_dict return lead_dict
class RadarD(): class RadarD:
def __init__(self, radar_ts, delay=0): def __init__(self, radar_ts: float, delay: int = 0):
self.current_time = 0 self.current_time = 0.0
self.tracks = defaultdict(dict) self.tracks: Dict[int, Track] = {}
self.kalman_params = KalmanParams(radar_ts) self.kalman_params = KalmanParams(radar_ts)
# v_ego self.v_ego = 0.0
self.v_ego = 0. self.v_ego_hist = deque([0.0], maxlen=delay+1)
self.v_ego_hist = deque([0], maxlen=delay+1)
self.radar_state: Optional[capnp._DynamicStructBuilder] = None
self.radar_state_valid = False
self.ready = False self.ready = False
def update(self, sm, rr): def update(self, sm: messaging.SubMaster, rr: Optional[car.RadarData]):
self.current_time = 1e-9*max(sm.logMonoTime.values()) self.current_time = 1e-9*max(sm.logMonoTime.values())
radar_points = []
radar_errors = []
if rr is not None:
radar_points = rr.points
radar_errors = rr.errors
if sm.updated['carState']: if sm.updated['carState']:
self.v_ego = sm['carState'].vEgo self.v_ego = sm['carState'].vEgo
self.v_ego_hist.append(self.v_ego) self.v_ego_hist.append(self.v_ego)
@ -210,7 +219,7 @@ class RadarD():
self.ready = True self.ready = True
ar_pts = {} ar_pts = {}
for pt in rr.points: for pt in radar_points:
ar_pts[pt.trackId] = [pt.dRel, pt.yRel, pt.vRel, pt.measured] ar_pts[pt.trackId] = [pt.dRel, pt.yRel, pt.vRel, pt.measured]
# *** remove missing points from meta data *** # *** remove missing points from meta data ***
@ -231,12 +240,11 @@ class RadarD():
self.tracks[ids].update(rpt[0], rpt[1], rpt[2], v_lead, rpt[3]) self.tracks[ids].update(rpt[0], rpt[1], rpt[2], v_lead, rpt[3])
# *** publish radarState *** # *** publish radarState ***
dat = messaging.new_message('radarState') self.radar_state_valid = sm.all_checks() and len(radar_errors) == 0
dat.valid = sm.all_checks() and len(rr.errors) == 0 self.radar_state = log.RadarState.new_message()
radarState = dat.radarState self.radar_state.mdMonoTime = sm.logMonoTime['modelV2']
radarState.mdMonoTime = sm.logMonoTime['modelV2'] self.radar_state.radarErrors = list(radar_errors)
radarState.radarErrors = list(rr.errors) self.radar_state.carStateMonoTime = sm.logMonoTime['carState']
radarState.carStateMonoTime = sm.logMonoTime['carState']
if len(sm['modelV2'].temporalPose.trans): if len(sm['modelV2'].temporalPose.trans):
model_v_ego = sm['modelV2'].temporalPose.trans[0] model_v_ego = sm['modelV2'].temporalPose.trans[0]
@ -244,18 +252,38 @@ class RadarD():
model_v_ego = self.v_ego model_v_ego = self.v_ego
leads_v3 = sm['modelV2'].leadsV3 leads_v3 = sm['modelV2'].leadsV3
if len(leads_v3) > 1: if len(leads_v3) > 1:
radarState.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, low_speed_override=True) self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, low_speed_override=True)
radarState.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, low_speed_override=False) self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, low_speed_override=False)
return dat
def publish(self, pm: messaging.PubMaster, lag_ms: float):
assert self.radar_state is not None
radar_msg = messaging.new_message("radarState")
radar_msg.valid = self.radar_state_valid
radar_msg.radarState = self.radar_state
radar_msg.radarState.cumLagMs = lag_ms
pm.send("radarState", radar_msg)
# publish tracks for UI debugging (keep last)
tracks_msg = messaging.new_message('liveTracks', len(self.tracks))
for index, tid in enumerate(sorted(self.tracks.keys())):
tracks_msg.liveTracks[index] = {
"trackId": tid,
"dRel": float(self.tracks[tid].dRel),
"yRel": float(self.tracks[tid].yRel),
"vRel": float(self.tracks[tid].vRel),
}
pm.send('liveTracks', tracks_msg)
# fuses camera and radar data for best lead detection # fuses camera and radar data for best lead detection
def radard_thread(sm=None, pm=None, can_sock=None): def radard_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None, can_sock: Optional[messaging.SubSocket] = None):
config_realtime_process(5, Priority.CTRL_LOW) config_realtime_process(5, Priority.CTRL_LOW)
# wait for stats about the car to come in from controls # wait for stats about the car to come in from controls
cloudlog.info("radard is waiting for CarParams") cloudlog.info("radard is waiting for CarParams")
CP = car.CarParams.from_bytes(Params().get("CarParams", block=True)) with car.CarParams.from_bytes(Params().get("CarParams", block=True)) as msg:
CP = msg
cloudlog.info("radard got CarParams") cloudlog.info("radard got CarParams")
# import the radar from the fingerprint # import the radar from the fingerprint
@ -284,28 +312,13 @@ def radard_thread(sm=None, pm=None, can_sock=None):
sm.update(0) sm.update(0)
dat = RD.update(sm, rr) RD.update(sm, rr)
dat.radarState.cumLagMs = -rk.remaining*1000. RD.publish(pm, -rk.remaining*1000.0)
pm.send('radarState', dat)
# *** publish tracks for UI debugging (keep last) ***
tracks = RD.tracks
dat = messaging.new_message('liveTracks', len(tracks))
for cnt, ids in enumerate(sorted(tracks.keys())):
dat.liveTracks[cnt] = {
"trackId": ids,
"dRel": float(tracks[ids].dRel),
"yRel": float(tracks[ids].yRel),
"vRel": float(tracks[ids].vRel),
}
pm.send('liveTracks', dat)
rk.monitor_time() rk.monitor_time()
def main(sm=None, pm=None, can_sock=None): def main(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None, can_sock: messaging.SubSocket = None):
radard_thread(sm, pm, can_sock) radard_thread(sm, pm, can_sock)

@ -41,7 +41,8 @@ if __name__ == "__main__":
polld = poller.poll(100) polld = poller.poll(100)
for sock in polld: for sock in polld:
msg = sock.receive() msg = sock.receive()
evt = log.Event.from_bytes(msg) with log.Event.from_bytes(msg) as log_evt:
evt = log_evt
if not args.no_print: if not args.no_print:
if args.pipe: if args.pipe:

@ -34,7 +34,8 @@ if __name__ == "__main__":
polld = poller.poll(1000) polld = poller.poll(1000)
for sock in polld: for sock in polld:
msg = sock.receive() msg = sock.receive()
evt = log.Event.from_bytes(msg) with log.Event.from_bytes(msg) as log_evt:
evt = log_evt
for item in evt.can: for item in evt.can:
if item.address == 0xe4 and item.src == 128: if item.address == 0xe4 and item.src == 128:

@ -25,7 +25,7 @@ if __name__ == "__main__":
# Remove message generated by the process under test and merge in the new messages # Remove message generated by the process under test and merge in the new messages
produces = {o.which() for o in outputs} produces = {o.which() for o in outputs}
inputs = [i for i in inputs if i.which() not in produces] inputs = [i for i in inputs if i.which() not in produces]
outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime) # type: ignore outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime)
fn = f"{args.route}_{args.process}.bz2" fn = f"{args.route}_{args.process}.bz2"
save_log(fn, outputs) save_log(fn, outputs)

@ -73,11 +73,11 @@ class Calibrator:
if param_put and calibration_params: if param_put and calibration_params:
try: try:
msg = log.Event.from_bytes(calibration_params) with log.Event.from_bytes(calibration_params) as msg:
rpy_init = np.array(msg.liveCalibration.rpyCalib) rpy_init = np.array(msg.liveCalibration.rpyCalib)
valid_blocks = msg.liveCalibration.validBlocks valid_blocks = msg.liveCalibration.validBlocks
wide_from_device_euler = np.array(msg.liveCalibration.wideFromDeviceEuler) wide_from_device_euler = np.array(msg.liveCalibration.wideFromDeviceEuler)
height = np.array(msg.liveCalibration.height) height = np.array(msg.liveCalibration.height)
except Exception: except Exception:
cloudlog.exception("Error reading cached CalibrationParams") cloudlog.exception("Error reading cached CalibrationParams")

@ -116,9 +116,9 @@ class Laikad:
nav_dict = {} nav_dict = {}
try: try:
ephem_cache = ephemeris_structs.EphemerisCache.from_bytes(cache_bytes) with ephemeris_structs.EphemerisCache.from_bytes(cache_bytes) as ephem_cache:
glonass_navs = [GLONASSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.glonassEphemerides] glonass_navs = [GLONASSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.glonassEphemerides]
gps_navs = [GPSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.gpsEphemerides] gps_navs = [GPSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.gpsEphemerides]
for e in sum([glonass_navs, gps_navs], []): for e in sum([glonass_navs, gps_navs], []):
if e.prn not in nav_dict: if e.prn not in nav_dict:
nav_dict[e.prn] = [] nav_dict[e.prn] = []

@ -129,7 +129,8 @@ def main(sm=None, pm=None):
params_reader = Params() params_reader = Params()
# wait for stats about the car to come in from controls # wait for stats about the car to come in from controls
cloudlog.info("paramsd is waiting for CarParams") cloudlog.info("paramsd is waiting for CarParams")
CP = car.CarParams.from_bytes(params_reader.get("CarParams", block=True)) with car.CarParams.from_bytes(params_reader.get("CarParams", block=True)) as msg:
CP = msg
cloudlog.info("paramsd got CarParams") cloudlog.info("paramsd got CarParams")
min_sr, max_sr = 0.5 * CP.steerRatio, 2.0 * CP.steerRatio min_sr, max_sr = 0.5 * CP.steerRatio, 2.0 * CP.steerRatio

@ -40,7 +40,8 @@ void localizer_handle_msg_bytes(Localizer_t localizer, const char *data, size_t
def localizer_get_msg(self, t=0, inputsOK=True, sensorsOK=True, gpsOK=True, msgValid=True): def localizer_get_msg(self, t=0, inputsOK=True, sensorsOK=True, gpsOK=True, msgValid=True):
self.lib.localizer_get_message_bytes(self.localizer, inputsOK, sensorsOK, gpsOK, msgValid, self.ffi.addressof(self.msg_buff, 0), self.buff_size) self.lib.localizer_get_message_bytes(self.localizer, inputsOK, sensorsOK, gpsOK, msgValid, self.ffi.addressof(self.msg_buff, 0), self.buff_size)
return log.Event.from_bytes(self.ffi.buffer(self.msg_buff), nesting_limit=self.buff_size // 8) with log.Event.from_bytes(self.ffi.buffer(self.msg_buff), nesting_limit=self.buff_size // 8) as log_evt:
return log_evt
def test_liblocalizer(self): def test_liblocalizer(self):
msg = messaging.new_message('liveCalibration') msg = messaging.new_message('liveCalibration')

@ -139,8 +139,10 @@ class TorqueEstimator:
torque_cache = params.get("LiveTorqueParameters") torque_cache = params.get("LiveTorqueParameters")
if params_cache is not None and torque_cache is not None: if params_cache is not None and torque_cache is not None:
try: try:
cache_ltp = log.Event.from_bytes(torque_cache).liveTorqueParameters with log.Event.from_bytes(torque_cache) as log_evt:
cache_CP = car.CarParams.from_bytes(params_cache) cache_ltp = log_evt.liveTorqueParameters
with car.CarParams.from_bytes(params_cache) as msg:
cache_CP = msg
if self.get_restore_key(cache_CP, cache_ltp.version) == self.get_restore_key(CP, VERSION): if self.get_restore_key(cache_CP, cache_ltp.version) == self.get_restore_key(CP, VERSION):
if cache_ltp.liveValid: if cache_ltp.liveValid:
initial_params = { initial_params = {
@ -262,8 +264,8 @@ def main(sm=None, pm=None):
pm = messaging.PubMaster(['liveTorqueParameters']) pm = messaging.PubMaster(['liveTorqueParameters'])
params = Params() params = Params()
CP = car.CarParams.from_bytes(params.get("CarParams", block=True)) with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP:
estimator = TorqueEstimator(CP) estimator = TorqueEstimator(CP)
def cache_params(sig, frame): def cache_params(sig, frame):
signal.signal(sig, signal.SIG_DFL) signal.signal(sig, signal.SIG_DFL)

@ -102,6 +102,7 @@ class RouteEngine:
new_destination = coordinate_from_param("NavDestination", self.params) new_destination = coordinate_from_param("NavDestination", self.params)
if new_destination is None: if new_destination is None:
self.clear_route() self.clear_route()
self.reset_recompute_limits()
return return
should_recompute = self.should_recompute() should_recompute = self.should_recompute()
@ -265,8 +266,7 @@ class RouteEngine:
if distance_to_maneuver_along_geometry < -MANEUVER_TRANSITION_THRESHOLD: if distance_to_maneuver_along_geometry < -MANEUVER_TRANSITION_THRESHOLD:
if self.step_idx + 1 < len(self.route): if self.step_idx + 1 < len(self.route):
self.step_idx += 1 self.step_idx += 1
self.recompute_backoff = 0 self.reset_recompute_limits()
self.recompute_countdown = 0
else: else:
cloudlog.warning("Destination reached") cloudlog.warning("Destination reached")
Params().remove("NavDestination") Params().remove("NavDestination")
@ -293,6 +293,10 @@ class RouteEngine:
self.step_idx = None self.step_idx = None
self.nav_destination = None self.nav_destination = None
def reset_recompute_limits(self):
self.recompute_backoff = 0
self.recompute_countdown = 0
def should_recompute(self): def should_recompute(self):
if self.step_idx is None or self.route is None: if self.step_idx is None or self.route is None:
return True return True

@ -60,7 +60,7 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non
default_tolerance = EPSILON if tolerance is None else tolerance default_tolerance = EPSILON if tolerance is None else tolerance
log1, log2 = ( log1, log2 = (
sorted((m for m in log if m.which() not in ignore_msgs), key=lambda m: (m.logMonoTime, m.which())) [m for m in log if m.which() not in ignore_msgs]
for log in (log1, log2) for log in (log1, log2)
) )

@ -7,7 +7,7 @@ from typing import List, Optional
from common.params import Params from common.params import Params
class OpenpilotPrefix(object): class OpenpilotPrefix(object):
def __init__(self, prefix: str = None, clean_dirs_on_exit: bool = True): def __init__(self, prefix: Optional[str] = None, clean_dirs_on_exit: bool = True):
self.prefix = prefix if prefix else str(uuid.uuid4()) self.prefix = prefix if prefix else str(uuid.uuid4())
self.msgq_path = os.path.join('/dev/shm', self.prefix) self.msgq_path = os.path.join('/dev/shm', self.prefix)
self.clean_dirs_on_exit = clean_dirs_on_exit self.clean_dirs_on_exit = clean_dirs_on_exit
@ -24,7 +24,7 @@ class OpenpilotPrefix(object):
self.clean_dirs() self.clean_dirs()
del os.environ['OPENPILOT_PREFIX'] del os.environ['OPENPILOT_PREFIX']
return False return False
def clean_dirs(self): def clean_dirs(self):
symlink_path = Params().get_param_path() symlink_path = Params().get_param_path()
if os.path.exists(symlink_path): if os.path.exists(symlink_path):

@ -39,6 +39,7 @@ class ReplayContext:
self.pubs = cfg.pubs self.pubs = cfg.pubs
self.main_pub = cfg.main_pub self.main_pub = cfg.main_pub
self.main_pub_drained = cfg.main_pub_drained self.main_pub_drained = cfg.main_pub_drained
self.unlocked_pubs = cfg.unlocked_pubs
assert(len(self.pubs) != 0 or self.main_pub is not None) assert(len(self.pubs) != 0 or self.main_pub is not None)
def __enter__(self): def __enter__(self):
@ -55,7 +56,8 @@ class ReplayContext:
if self.main_pub is None: if self.main_pub is None:
self.events = OrderedDict() self.events = OrderedDict()
for pub in self.pubs: pubs_with_events = [pub for pub in self.pubs if pub not in self.unlocked_pubs]
for pub in pubs_with_events:
self.events[pub] = messaging.fake_event_handle(pub, enable=True) self.events[pub] = messaging.fake_event_handle(pub, enable=True)
else: else:
self.events = {self.main_pub: messaging.fake_event_handle(self.main_pub, enable=True)} self.events = {self.main_pub: messaging.fake_event_handle(self.main_pub, enable=True)}
@ -117,6 +119,7 @@ class ProcessConfig:
main_pub_drained: bool = True main_pub_drained: bool = True
vision_pubs: List[str] = field(default_factory=list) vision_pubs: List[str] = field(default_factory=list)
ignore_alive_pubs: List[str] = field(default_factory=list) ignore_alive_pubs: List[str] = field(default_factory=list)
unlocked_pubs: List[str] = field(default_factory=list)
class ProcessContainer: class ProcessContainer:

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import copy
from hypothesis import given, HealthCheck, Phase, settings from hypothesis import given, HealthCheck, Phase, settings
import hypothesis.strategies as st import hypothesis.strategies as st
from parameterized import parameterized from parameterized import parameterized
@ -13,7 +14,7 @@ import selfdrive.test.process_replay.process_replay as pr
# that openpilot makes causing error with NaN, inf, int size, array indexing ... # that openpilot makes causing error with NaN, inf, int size, array indexing ...
# TODO: Make each one testable # TODO: Make each one testable
NOT_TESTED = ['controlsd', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'laikad', 'dmonitoringmodeld', 'modeld'] NOT_TESTED = ['controlsd', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'laikad', 'dmonitoringmodeld', 'modeld']
TEST_CASES = [(cfg.proc_name, cfg) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED] TEST_CASES = [(cfg.proc_name, copy.deepcopy(cfg)) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED]
class TestFuzzProcesses(unittest.TestCase): class TestFuzzProcesses(unittest.TestCase):
@ -24,7 +25,7 @@ class TestFuzzProcesses(unittest.TestCase):
msgs = FuzzyGenerator.get_random_event_msg(data.draw, events=cfg.pubs, real_floats=True) msgs = FuzzyGenerator.get_random_event_msg(data.draw, events=cfg.pubs, real_floats=True)
lr = [log.Event.new_message(**m).as_reader() for m in msgs] lr = [log.Event.new_message(**m).as_reader() for m in msgs]
cfg.timeout = 5 cfg.timeout = 5
pr.replay_process(cfg, lr, TOYOTA.COROLLA_TSS2, TOYOTA.COROLLA_TSS2, disable_progress=True) pr.replay_process(cfg, lr, fingerprint=TOYOTA.COROLLA_TSS2, disable_progress=True)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -29,7 +29,8 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/
qt_env['CPPDEFINES'] = [] qt_env['CPPDEFINES'] = []
if maps: if maps:
base_libs += ['qmapboxgl'] base_libs += ['qmapboxgl']
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc"] widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc",
"qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"]
qt_env['CPPDEFINES'] += ["ENABLE_MAPS"] qt_env['CPPDEFINES'] += ["ENABLE_MAPS"]
widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs)

@ -3,7 +3,6 @@
#include <eigen3/Eigen/Dense> #include <eigen3/Eigen/Dense>
#include <QDebug> #include <QDebug>
#include <QDir>
#include "common/transformations/coordinates.hpp" #include "common/transformations/coordinates.hpp"
#include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/qt/maps/map_helpers.h"
@ -12,7 +11,6 @@
const int PAN_TIMEOUT = 100; const int PAN_TIMEOUT = 100;
const float MANEUVER_TRANSITION_THRESHOLD = 10;
const float MAX_ZOOM = 17; const float MAX_ZOOM = 17;
const float MIN_ZOOM = 14; const float MIN_ZOOM = 14;
@ -20,8 +18,6 @@ const float MAX_PITCH = 50;
const float MIN_PITCH = 0; const float MIN_PITCH = 0;
const float MAP_SCALE = 2; const float MAP_SCALE = 2;
const QString ICON_SUFFIX = ".png";
MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05) { MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05) {
QObject::connect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState); QObject::connect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState);
@ -121,11 +117,6 @@ void MapWindow::initLayers() {
pin["type"] = "symbol"; pin["type"] = "symbol";
pin["source"] = "pinSource"; pin["source"] = "pinSource";
m_map->addLayer(pin); m_map->addLayer(pin);
// FIXME: solve, workaround to remove animation on visibility property
QVariantMap transition;
transition["duration"] = 0; // ms
m_map->setPaintProperty("pinLayer", "icon-opacity-transition", transition);
m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport"); m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport");
m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker"); m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker");
m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true); m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true);
@ -214,7 +205,7 @@ void MapWindow::updateState(const UIState &s) {
if (!locationd_valid) { if (!locationd_valid) {
setError(tr("Waiting for GPS")); setError(tr("Waiting for GPS"));
} else if (routing_problem) { } else if (routing_problem) {
setError(tr("Waiting for internet")); setError(tr("Waiting for route"));
} else { } else {
setError(""); setError("");
} }
@ -246,6 +237,7 @@ void MapWindow::updateState(const UIState &s) {
// an invalid navInstruction packet with a nav destination is only possible if: // an invalid navInstruction packet with a nav destination is only possible if:
// - API exception/no internet // - API exception/no internet
// - route response is empty // - route response is empty
// - any time navd is waiting for recompute_countdown
auto dest = coordinate_from_param("NavDestination"); auto dest = coordinate_from_param("NavDestination");
routing_problem = !sm.valid("navInstruction") && dest.has_value(); routing_problem = !sm.valid("navInstruction") && dest.has_value();
@ -309,6 +301,10 @@ void MapWindow::initializeGL() {
m_map->setStyleUrl("mapbox://styles/commaai/clj7g5vrp007b01qzb5ro0i4j"); m_map->setStyleUrl("mapbox://styles/commaai/clj7g5vrp007b01qzb5ro0i4j");
QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) { QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) {
// set global animation duration to 0 ms so visibility changes are instant
if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingStyle) {
m_map->setTransitionOptions(0, 0);
}
if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingMap) { if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingMap) {
loaded_once = true; loaded_once = true;
} }
@ -415,7 +411,7 @@ void MapWindow::offroadTransition(bool offroad) {
} }
void MapWindow::updateDestinationMarker() { void MapWindow::updateDestinationMarker() {
m_map->setPaintProperty("pinLayer", "icon-opacity", 0); m_map->setPaintProperty("pinLayer", "visibility", "none");
auto nav_dest = coordinate_from_param("NavDestination"); auto nav_dest = coordinate_from_param("NavDestination");
if (nav_dest.has_value()) { if (nav_dest.has_value()) {
@ -425,189 +421,6 @@ void MapWindow::updateDestinationMarker() {
pinSource["type"] = "geojson"; pinSource["type"] = "geojson";
pinSource["data"] = QVariant::fromValue<QMapbox::Feature>(feature); pinSource["data"] = QVariant::fromValue<QMapbox::Feature>(feature);
m_map->updateSource("pinSource", pinSource); m_map->updateSource("pinSource", pinSource);
m_map->setPaintProperty("pinLayer", "icon-opacity", 1); m_map->setPaintProperty("pinLayer", "visibility", "visible");
}
}
MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) {
is_rhd = Params().getBool("IsRhdDetected");
QHBoxLayout *main_layout = new QHBoxLayout(this);
main_layout->setContentsMargins(11, 50, 11, 11);
main_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop);
QWidget *right_container = new QWidget(this);
right_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QVBoxLayout *layout = new QVBoxLayout(right_container);
layout->addWidget(distance = new QLabel);
distance->setStyleSheet(R"(font-size: 90px;)");
layout->addWidget(primary = new QLabel);
primary->setStyleSheet(R"(font-size: 60px;)");
primary->setWordWrap(true);
layout->addWidget(secondary = new QLabel);
secondary->setStyleSheet(R"(font-size: 50px;)");
secondary->setWordWrap(true);
layout->addLayout(lane_layout = new QHBoxLayout);
main_layout->addWidget(right_container);
setStyleSheet("color:white");
QPalette pal = palette();
pal.setColor(QPalette::Background, QColor(0, 0, 0, 150));
setAutoFillBackground(true);
setPalette(pal);
buildPixmapCache();
}
void MapInstructions::buildPixmapCache() {
QDir dir("../assets/navigation");
for (QString fn : dir.entryList({"*" + ICON_SUFFIX}, QDir::Files)) {
QPixmap pm(dir.filePath(fn));
QString key = fn.left(fn.size() - ICON_SUFFIX.length());
pm = pm.scaledToWidth(200, Qt::SmoothTransformation);
// Maneuver icons
pixmap_cache[key] = pm;
// lane direction icons
if (key.contains("turn_")) {
pixmap_cache["lane_" + key] = pm.scaled({125, 125}, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
// for rhd, reflect direction and then flip
if (key.contains("_left")) {
pixmap_cache["rhd_" + key.replace("_left", "_right")] = pm.transformed(QTransform().scale(-1, 1));
} else if (key.contains("_right")) {
pixmap_cache["rhd_" + key.replace("_right", "_left")] = pm.transformed(QTransform().scale(-1, 1));
}
} }
} }
QString MapInstructions::getDistance(float d) {
d = std::max(d, 0.0f);
if (uiState()->scene.is_metric) {
return (d > 500) ? QString::number(d / 1000, 'f', 1) + tr(" km")
: QString::number(50 * int(d / 50)) + tr(" m");
} else {
float feet = d * METER_TO_FOOT;
return (feet > 500) ? QString::number(d * METER_TO_MILE, 'f', 1) + tr(" mi")
: QString::number(50 * int(feet / 50)) + tr(" ft");
}
}
void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruction) {
setUpdatesEnabled(false);
// Show instruction text
QString primary_str = QString::fromStdString(instruction.getManeuverPrimaryText());
QString secondary_str = QString::fromStdString(instruction.getManeuverSecondaryText());
primary->setText(primary_str);
secondary->setVisible(secondary_str.length() > 0);
secondary->setText(secondary_str);
distance->setText(getDistance(instruction.getManeuverDistance()));
// Show arrow with direction
QString type = QString::fromStdString(instruction.getManeuverType());
QString modifier = QString::fromStdString(instruction.getManeuverModifier());
if (!type.isEmpty()) {
QString fn = "direction_" + type;
if (!modifier.isEmpty()) {
fn += "_" + modifier;
}
fn = fn.replace(' ', '_');
bool rhd = is_rhd && (fn.contains("_left") || fn.contains("_right"));
icon_01->setPixmap(pixmap_cache[!rhd ? fn : "rhd_" + fn]);
icon_01->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
icon_01->setVisible(true);
}
// Show lanes
auto lanes = instruction.getLanes();
for (int i = 0; i < lanes.size(); ++i) {
bool active = lanes[i].getActive();
// TODO: only use active direction if active
bool left = false, straight = false, right = false;
for (auto const &direction: lanes[i].getDirections()) {
left |= direction == cereal::NavInstruction::Direction::LEFT;
right |= direction == cereal::NavInstruction::Direction::RIGHT;
straight |= direction == cereal::NavInstruction::Direction::STRAIGHT;
}
// TODO: Make more images based on active direction and combined directions
QString fn = "lane_direction_";
if (left) {
fn += "turn_left";
} else if (right) {
fn += "turn_right";
} else if (straight) {
fn += "turn_straight";
}
if (!active) {
fn += "_inactive";
}
QLabel *label = (i < lane_labels.size()) ? lane_labels[i] : lane_labels.emplace_back(new QLabel);
if (!label->parentWidget()) {
lane_layout->addWidget(label);
}
label->setPixmap(pixmap_cache[fn]);
label->setVisible(true);
}
for (int i = lanes.size(); i < lane_labels.size(); ++i) {
lane_labels[i]->setVisible(false);
}
setUpdatesEnabled(true);
setVisible(true);
}
MapETA::MapETA(QWidget *parent) : QWidget(parent) {
setVisible(false);
setAttribute(Qt::WA_TranslucentBackground);
eta_doc.setUndoRedoEnabled(false);
eta_doc.setDefaultStyleSheet("body {font-family:Inter;font-size:60px;color:white;} b{font-size:70px;font-weight:600}");
}
void MapETA::paintEvent(QPaintEvent *event) {
if (!eta_doc.isEmpty()) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 150));
QSizeF txt_size = eta_doc.size();
p.drawRoundedRect((width() - txt_size.width()) / 2 - UI_BORDER_SIZE, 0, txt_size.width() + UI_BORDER_SIZE * 2, height() + 25, 25, 25);
p.translate((width() - txt_size.width()) / 2, (height() - txt_size.height()) / 2);
eta_doc.drawContents(&p);
}
}
void MapETA::updateETA(float s, float s_typical, float d) {
// ETA
auto eta_t = QDateTime::currentDateTime().addSecs(s).time();
auto eta = format_24h ? std::array{eta_t.toString("HH:mm"), tr("eta")}
: std::array{eta_t.toString("h:mm a").split(' ')[0], eta_t.toString("a")};
// Remaining time
auto remaining = s < 3600 ? std::array{QString::number(int(s / 60)), tr("min")}
: std::array{QString("%1:%2").arg((int)s / 3600).arg(((int)s % 3600) / 60, 2, 10, QLatin1Char('0')), tr("hr")};
QString color = "#25DA6E";
if (s / s_typical > 1.5) color = "#DA3025";
else if (s / s_typical > 1.2) color = "#DAA725";
// Distance
float num = uiState()->scene.is_metric ? (d / 1000.0) : (d * METER_TO_MILE);
auto distance = std::array{QString::number(num, 'f', num < 100 ? 1 : 0),
uiState()->scene.is_metric ? tr("km") : tr("mi")};
eta_doc.setHtml(QString(R"(<body><b>%1</b>%2 <span style="color:%3"><b>%4</b>%5</span> <b>%6</b>%7</body>)")
.arg(eta[0], eta[1], color, remaining[0], remaining[1], distance[0], distance[1]));
setVisible(d >= MANEUVER_TRANSITION_THRESHOLD);
update();
}

@ -4,8 +4,6 @@
#include <QGeoCoordinate> #include <QGeoCoordinate>
#include <QGestureEvent> #include <QGestureEvent>
#include <QHash>
#include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QMap> #include <QMap>
#include <QMapboxGL> #include <QMapboxGL>
@ -15,7 +13,6 @@
#include <QPushButton> #include <QPushButton>
#include <QScopedPointer> #include <QScopedPointer>
#include <QString> #include <QString>
#include <QTextDocument>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QWheelEvent> #include <QWheelEvent>
@ -23,42 +20,8 @@
#include "common/params.h" #include "common/params.h"
#include "common/util.h" #include "common/util.h"
#include "selfdrive/ui/ui.h" #include "selfdrive/ui/ui.h"
#include "selfdrive/ui/qt/maps/map_eta.h"
class MapInstructions : public QWidget { #include "selfdrive/ui/qt/maps/map_instructions.h"
Q_OBJECT
private:
QLabel *distance;
QLabel *primary;
QLabel *secondary;
QLabel *icon_01;
QHBoxLayout *lane_layout;
bool is_rhd = false;
std::vector<QLabel *> lane_labels;
QHash<QString, QPixmap> pixmap_cache;
public:
MapInstructions(QWidget * parent=nullptr);
void buildPixmapCache();
QString getDistance(float d);
void updateInstructions(cereal::NavInstruction::Reader instruction);
};
class MapETA : public QWidget {
Q_OBJECT
public:
MapETA(QWidget * parent=nullptr);
void updateETA(float seconds, float seconds_typical, float distance);
private:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override { format_24h = param.getBool("NavSettingTime24h"); }
bool format_24h = false;
QTextDocument eta_doc;
Params param;
};
class MapWindow : public QOpenGLWidget { class MapWindow : public QOpenGLWidget {
Q_OBJECT Q_OBJECT

@ -0,0 +1,57 @@
#include "selfdrive/ui/qt/maps/map_eta.h"
#include <QDateTime>
#include <QPainter>
#include "selfdrive/ui/ui.h"
const float MANEUVER_TRANSITION_THRESHOLD = 10;
MapETA::MapETA(QWidget *parent) : QWidget(parent) {
setVisible(false);
setAttribute(Qt::WA_TranslucentBackground);
eta_doc.setUndoRedoEnabled(false);
eta_doc.setDefaultStyleSheet("body {font-family:Inter;font-size:70px;color:white;} b{font-weight:600;} td{padding:0 3px;}");
}
void MapETA::paintEvent(QPaintEvent *event) {
if (!eta_doc.isEmpty()) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 150));
QSizeF txt_size = eta_doc.size();
p.drawRoundedRect((width() - txt_size.width()) / 2 - UI_BORDER_SIZE, 0, txt_size.width() + UI_BORDER_SIZE * 2, height() + 25, 25, 25);
p.translate((width() - txt_size.width()) / 2, (height() - txt_size.height()) / 2);
eta_doc.drawContents(&p);
}
}
void MapETA::updateETA(float s, float s_typical, float d) {
// ETA
auto eta_t = QDateTime::currentDateTime().addSecs(s).time();
auto eta = format_24h ? std::array{eta_t.toString("HH:mm"), tr("eta")}
: std::array{eta_t.toString("h:mm a").split(' ')[0], eta_t.toString("a")};
// Remaining time
auto remaining = s < 3600 ? std::array{QString::number(int(s / 60)), tr("min")}
: std::array{QString("%1:%2").arg((int)s / 3600).arg(((int)s % 3600) / 60, 2, 10, QLatin1Char('0')), tr("hr")};
QString color = "#25DA6E";
if (s / s_typical > 1.5)
color = "#DA3025";
else if (s / s_typical > 1.2)
color = "#DAA725";
// Distance
float num = uiState()->scene.is_metric ? (d / 1000.0) : (d * METER_TO_MILE);
auto distance = std::array{QString::number(num, 'f', num < 100 ? 1 : 0),
uiState()->scene.is_metric ? tr("km") : tr("mi")};
eta_doc.setHtml(QString(R"(<body><table><tr><td><b>%1</b></td><td>%2</td>
<td style="padding-left:40px;color:%3;"><b>%4</b></td><td style="padding-right:40px;color:%3;">%5</td>
<td><b>%6</b></td><td>%7</td></tr></body>)")
.arg(eta[0], eta[1], color, remaining[0], remaining[1], distance[0], distance[1]));
setVisible(d >= MANEUVER_TRANSITION_THRESHOLD);
update();
}

@ -0,0 +1,23 @@
#pragma once
#include <QPaintEvent>
#include <QTextDocument>
#include <QWidget>
#include "common/params.h"
class MapETA : public QWidget {
Q_OBJECT
public:
MapETA(QWidget * parent=nullptr);
void updateETA(float seconds, float seconds_typical, float distance);
private:
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override { format_24h = param.getBool("NavSettingTime24h"); }
bool format_24h = false;
QTextDocument eta_doc;
Params param;
};

@ -0,0 +1,146 @@
#include "selfdrive/ui/qt/maps/map_instructions.h"
#include <QDir>
#include <QVBoxLayout>
#include "selfdrive/ui/ui.h"
const QString ICON_SUFFIX = ".png";
MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) {
is_rhd = Params().getBool("IsRhdDetected");
QHBoxLayout *main_layout = new QHBoxLayout(this);
main_layout->setContentsMargins(11, 50, 11, 11);
main_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop);
QWidget *right_container = new QWidget(this);
right_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QVBoxLayout *layout = new QVBoxLayout(right_container);
layout->addWidget(distance = new QLabel);
distance->setStyleSheet(R"(font-size: 90px;)");
layout->addWidget(primary = new QLabel);
primary->setStyleSheet(R"(font-size: 60px;)");
primary->setWordWrap(true);
layout->addWidget(secondary = new QLabel);
secondary->setStyleSheet(R"(font-size: 50px;)");
secondary->setWordWrap(true);
layout->addLayout(lane_layout = new QHBoxLayout);
main_layout->addWidget(right_container);
setStyleSheet("color:white");
QPalette pal = palette();
pal.setColor(QPalette::Background, QColor(0, 0, 0, 150));
setAutoFillBackground(true);
setPalette(pal);
buildPixmapCache();
}
void MapInstructions::buildPixmapCache() {
QDir dir("../assets/navigation");
for (QString fn : dir.entryList({"*" + ICON_SUFFIX}, QDir::Files)) {
QPixmap pm(dir.filePath(fn));
QString key = fn.left(fn.size() - ICON_SUFFIX.length());
pm = pm.scaledToWidth(200, Qt::SmoothTransformation);
// Maneuver icons
pixmap_cache[key] = pm;
// lane direction icons
if (key.contains("turn_")) {
pixmap_cache["lane_" + key] = pm.scaled({125, 125}, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
// for rhd, reflect direction and then flip
if (key.contains("_left")) {
pixmap_cache["rhd_" + key.replace("_left", "_right")] = pm.transformed(QTransform().scale(-1, 1));
} else if (key.contains("_right")) {
pixmap_cache["rhd_" + key.replace("_right", "_left")] = pm.transformed(QTransform().scale(-1, 1));
}
}
}
QString MapInstructions::getDistance(float d) {
d = std::max(d, 0.0f);
if (uiState()->scene.is_metric) {
return (d > 500) ? QString::number(d / 1000, 'f', 1) + tr(" km")
: QString::number(50 * int(d / 50)) + tr(" m");
} else {
float feet = d * METER_TO_FOOT;
return (feet > 500) ? QString::number(d * METER_TO_MILE, 'f', 1) + tr(" mi")
: QString::number(50 * int(feet / 50)) + tr(" ft");
}
}
void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruction) {
setUpdatesEnabled(false);
// Show instruction text
QString primary_str = QString::fromStdString(instruction.getManeuverPrimaryText());
QString secondary_str = QString::fromStdString(instruction.getManeuverSecondaryText());
primary->setText(primary_str);
secondary->setVisible(secondary_str.length() > 0);
secondary->setText(secondary_str);
distance->setText(getDistance(instruction.getManeuverDistance()));
// Show arrow with direction
QString type = QString::fromStdString(instruction.getManeuverType());
QString modifier = QString::fromStdString(instruction.getManeuverModifier());
if (!type.isEmpty()) {
QString fn = "direction_" + type;
if (!modifier.isEmpty()) {
fn += "_" + modifier;
}
fn = fn.replace(' ', '_');
bool rhd = is_rhd && (fn.contains("_left") || fn.contains("_right"));
icon_01->setPixmap(pixmap_cache[!rhd ? fn : "rhd_" + fn]);
icon_01->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
icon_01->setVisible(true);
}
// Show lanes
auto lanes = instruction.getLanes();
for (int i = 0; i < lanes.size(); ++i) {
bool active = lanes[i].getActive();
// TODO: only use active direction if active
bool left = false, straight = false, right = false;
for (auto const &direction : lanes[i].getDirections()) {
left |= direction == cereal::NavInstruction::Direction::LEFT;
right |= direction == cereal::NavInstruction::Direction::RIGHT;
straight |= direction == cereal::NavInstruction::Direction::STRAIGHT;
}
// TODO: Make more images based on active direction and combined directions
QString fn = "lane_direction_";
if (left) {
fn += "turn_left";
} else if (right) {
fn += "turn_right";
} else if (straight) {
fn += "turn_straight";
}
if (!active) {
fn += "_inactive";
}
QLabel *label = (i < lane_labels.size()) ? lane_labels[i] : lane_labels.emplace_back(new QLabel);
if (!label->parentWidget()) {
lane_layout->addWidget(label);
}
label->setPixmap(pixmap_cache[fn]);
label->setVisible(true);
}
for (int i = lanes.size(); i < lane_labels.size(); ++i) {
lane_labels[i]->setVisible(false);
}
setUpdatesEnabled(true);
setVisible(true);
}

@ -0,0 +1,27 @@
#pragma once
#include <QHash>
#include <QHBoxLayout>
#include <QLabel>
#include "cereal/gen/cpp/log.capnp.h"
class MapInstructions : public QWidget {
Q_OBJECT
private:
QLabel *distance;
QLabel *primary;
QLabel *secondary;
QLabel *icon_01;
QHBoxLayout *lane_layout;
bool is_rhd = false;
std::vector<QLabel *> lane_labels;
QHash<QString, QPixmap> pixmap_cache;
public:
MapInstructions(QWidget * parent=nullptr);
void buildPixmapCache();
QString getDistance(float d);
void updateInstructions(cereal::NavInstruction::Reader instruction);
};

@ -7,10 +7,6 @@
#include "selfdrive/ui/qt/request_repeater.h" #include "selfdrive/ui/qt/request_repeater.h"
#include "selfdrive/ui/qt/widgets/scrollview.h" #include "selfdrive/ui/qt/widgets/scrollview.h"
static QString shorten(const QString &str, int max_len) {
return str.size() > max_len ? str.left(max_len).trimmed() + "" : str;
}
MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) { MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
close_icon = loadPixmap("../assets/icons/close.svg", {100, 100}); close_icon = loadPixmap("../assets/icons/close.svg", {100, 100});
setContentsMargins(0, 0, 0, 0); setContentsMargins(0, 0, 0, 0);
@ -253,9 +249,8 @@ void DestinationWidget::set(const QJsonObject &destination, bool current) {
icon->setPixmap(icon_pixmap); icon->setPixmap(icon_pixmap);
// TODO: onroad and offroad have different dimensions title->setText(title_text);
title->setText(shorten(title_text, 26)); subtitle->setText(subtitle_text);
subtitle->setText(shorten(subtitle_text, 26));
subtitle->setVisible(true); subtitle->setVisible(true);
// TODO: use pixmap // TODO: use pixmap

@ -404,8 +404,8 @@
<translation>Warten auf GPS</translation> <translation>Warten auf GPS</translation>
</message> </message>
<message> <message>
<source>Waiting for internet</source> <source>Waiting for route</source>
<translation type="unfinished">Auf Internet warten</translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context>

@ -403,8 +403,8 @@
<translation>GPS信号を探しています</translation> <translation>GPS信号を探しています</translation>
</message> </message>
<message> <message>
<source>Waiting for internet</source> <source>Waiting for route</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context>

@ -403,8 +403,8 @@
<translation>GPS </translation> <translation>GPS </translation>
</message> </message>
<message> <message>
<source>Waiting for internet</source> <source>Waiting for route</source>
<translation type="unfinished"> </translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context>

@ -404,8 +404,8 @@
<translation>Esperando por GPS</translation> <translation>Esperando por GPS</translation>
</message> </message>
<message> <message>
<source>Waiting for internet</source> <source>Waiting for route</source>
<translation type="unfinished">Esperando pela internet</translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context>

@ -403,8 +403,8 @@
<translation> GPS</translation> <translation> GPS</translation>
</message> </message>
<message> <message>
<source>Waiting for internet</source> <source>Waiting for route</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context>

@ -403,8 +403,8 @@
<translation> GPS</translation> <translation> GPS</translation>
</message> </message>
<message> <message>
<source>Waiting for internet</source> <source>Waiting for route</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context>

@ -1,19 +1,19 @@
[ [
{ {
"name": "boot", "name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-7d953f5e1bc606984e4d49c6f957421a4172f72b4ebd359baa689ef43b7e911c.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/boot-8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279.img.xz",
"hash": "7d953f5e1bc606984e4d49c6f957421a4172f72b4ebd359baa689ef43b7e911c", "hash": "8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279",
"hash_raw": "7d953f5e1bc606984e4d49c6f957421a4172f72b4ebd359baa689ef43b7e911c", "hash_raw": "8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279",
"size": 15153152, "size": 15636480,
"sparse": false, "sparse": false,
"full_check": true, "full_check": true,
"has_ab": true "has_ab": true
}, },
{ {
"name": "abl", "name": "abl",
"url": "https://commadist.azureedge.net/agnosupdate/abl-50329ac734ff7a6c20c3f552dce9b13f84b3eb2e73faa64b9810049d9b406602.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/abl-0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d.img.xz",
"hash": "50329ac734ff7a6c20c3f552dce9b13f84b3eb2e73faa64b9810049d9b406602", "hash": "0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d",
"hash_raw": "50329ac734ff7a6c20c3f552dce9b13f84b3eb2e73faa64b9810049d9b406602", "hash_raw": "0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d",
"size": 274432, "size": 274432,
"sparse": false, "sparse": false,
"full_check": true, "full_check": true,
@ -21,9 +21,9 @@
}, },
{ {
"name": "xbl", "name": "xbl",
"url": "https://commadist.azureedge.net/agnosupdate/xbl-dc297986b38f50c47584bd8549b188b37b1d6a0c77b3255859dd675c177b5c15.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/xbl-942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa.img.xz",
"hash": "dc297986b38f50c47584bd8549b188b37b1d6a0c77b3255859dd675c177b5c15", "hash": "942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa",
"hash_raw": "dc297986b38f50c47584bd8549b188b37b1d6a0c77b3255859dd675c177b5c15", "hash_raw": "942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa",
"size": 3282672, "size": 3282672,
"sparse": false, "sparse": false,
"full_check": true, "full_check": true,
@ -31,9 +31,9 @@
}, },
{ {
"name": "xbl_config", "name": "xbl_config",
"url": "https://commadist.azureedge.net/agnosupdate/xbl_config-b73fbbb42934aabc6d4f16ce84ac6c8c0205bc70e0a85412a771f3cc1d62cc40.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf.img.xz",
"hash": "b73fbbb42934aabc6d4f16ce84ac6c8c0205bc70e0a85412a771f3cc1d62cc40", "hash": "6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf",
"hash_raw": "b73fbbb42934aabc6d4f16ce84ac6c8c0205bc70e0a85412a771f3cc1d62cc40", "hash_raw": "6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf",
"size": 98124, "size": 98124,
"sparse": false, "sparse": false,
"full_check": true, "full_check": true,
@ -41,9 +41,9 @@
}, },
{ {
"name": "devcfg", "name": "devcfg",
"url": "https://commadist.azureedge.net/agnosupdate/devcfg-2d3063d106813006ac9ceeaf8818a31d4b33996873e81178ac5129f5e1b82bca.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/devcfg-9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262.img.xz",
"hash": "2d3063d106813006ac9ceeaf8818a31d4b33996873e81178ac5129f5e1b82bca", "hash": "9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262",
"hash_raw": "2d3063d106813006ac9ceeaf8818a31d4b33996873e81178ac5129f5e1b82bca", "hash_raw": "9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262",
"size": 40336, "size": 40336,
"sparse": false, "sparse": false,
"full_check": true, "full_check": true,
@ -51,9 +51,9 @@
}, },
{ {
"name": "aop", "name": "aop",
"url": "https://commadist.azureedge.net/agnosupdate/aop-d69450d5438b3e5e2ba5b77db1ae49e1cf9cab17836f563aa57192b5b3a4ac3e.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/aop-c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222.img.xz",
"hash": "d69450d5438b3e5e2ba5b77db1ae49e1cf9cab17836f563aa57192b5b3a4ac3e", "hash": "c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222",
"hash_raw": "d69450d5438b3e5e2ba5b77db1ae49e1cf9cab17836f563aa57192b5b3a4ac3e", "hash_raw": "c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222",
"size": 184364, "size": 184364,
"sparse": false, "sparse": false,
"full_check": true, "full_check": true,
@ -61,9 +61,9 @@
}, },
{ {
"name": "system", "name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-4a8311dd591006e0c2a6f60060d6ef579ceec9b3d688e8438a9aef4e230ae028.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/system-e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432.img.xz",
"hash": "23c9f111f81fc3ee83f85016cb320e03a46aad6721a85e1b4a3f04b6a764e934", "hash": "611011f3e3f147bc24f371105a9dd3760ec11ba424c56d4a442a66b098c784c0",
"hash_raw": "4a8311dd591006e0c2a6f60060d6ef579ceec9b3d688e8438a9aef4e230ae028", "hash_raw": "e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432",
"size": 10737418240, "size": 10737418240,
"sparse": true, "sparse": true,
"full_check": false, "full_check": false,

@ -56,7 +56,7 @@ if __name__ == "__main__":
pr_err /= len(car) pr_err /= len(car)
speed_err /= len(car) speed_err /= len(car)
print("avg psuedorange err %f avg speed err %f" % (pr_err, speed_err)) print("avg psuedorange err %f avg speed err %f" % (pr_err, speed_err))
for c in sorted(car, key=lambda x: abs(x[1] - x[3] - pr_err)): # type: ignore for c in sorted(car, key=lambda x: abs(x[1] - x[3] - pr_err)):
svid, ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed, cno = c svid, ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed, cno = c
print("svid: %3d pseudorange: %10.2f m speed: %8.2f m/s meas: %12.2f speed: %10.2f meas_err: %10.3f speed_err: %8.3f cno: %d" % print("svid: %3d pseudorange: %10.2f m speed: %8.2f m/s meas: %12.2f speed: %10.2f meas_err: %10.3f speed_err: %8.3f cno: %d" %
(svid, ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed, (svid, ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed,

Loading…
Cancel
Save