Merge remote-tracking branch 'upstream/master' into navd-fix-empty-last-step

pull/29034/head
sshane 2 years ago
commit 968cf9cabf
  1. 3
      .github/workflows/prebuilt.yaml
  2. 10
      .github/workflows/selfdrive_tests.yaml
  3. 1
      .pre-commit-config.yaml
  4. 2
      .python-version
  5. 2
      Dockerfile.openpilot_base
  6. 31
      Jenkinsfile
  7. 3
      RELEASES.md
  8. 1
      SConstruct
  9. 2
      cereal
  10. 2
      common/gpio.cc
  11. 22
      common/gpio.py
  12. 1
      common/params.cc
  13. 1
      common/swaglog.cc
  14. 6
      common/swaglog.h
  15. 2
      docs/CARS.md
  16. 2
      launch_env.sh
  17. 2
      mypy.ini
  18. 2
      opendbc
  19. 2
      panda
  20. 6281
      poetry.lock
  21. 40
      pyproject.toml
  22. 2
      rednose_repo
  23. 1
      release/files_common
  24. 4
      selfdrive/athena/athenad.py
  25. 48
      selfdrive/boardd/pandad.py
  26. BIN
      selfdrive/boardd/tests/bootstub.panda_h7.bin
  27. 9
      selfdrive/boardd/tests/test_pandad.py
  28. 6
      selfdrive/car/car_helpers.py
  29. 2
      selfdrive/car/docs_definitions.py
  30. 31
      selfdrive/car/gm/interface.py
  31. 3
      selfdrive/car/interfaces.py
  32. 36
      selfdrive/car/tests/test_models.py
  33. 1
      selfdrive/car/torque_data/override.yaml
  34. 1
      selfdrive/car/torque_data/params.yaml
  35. 7
      selfdrive/car/volkswagen/values.py
  36. 8
      selfdrive/controls/controlsd.py
  37. 7
      selfdrive/controls/lib/lateral_mpc_lib/SConscript
  38. 7
      selfdrive/controls/lib/longitudinal_mpc_lib/SConscript
  39. 4
      selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
  40. 159
      selfdrive/controls/lib/radar_helpers.py
  41. 3
      selfdrive/controls/plannerd.py
  42. 271
      selfdrive/controls/radard.py
  43. 3
      selfdrive/debug/dump.py
  44. 3
      selfdrive/debug/internal/measure_torque_time_to_max.py
  45. 2
      selfdrive/debug/run_process_on_route.py
  46. 10
      selfdrive/locationd/calibrationd.py
  47. 6
      selfdrive/locationd/laikad.py
  48. 3
      selfdrive/locationd/paramsd.py
  49. 3
      selfdrive/locationd/test/_test_locationd_lib.py
  50. 10
      selfdrive/locationd/torqued.py
  51. 8
      selfdrive/navd/navd.py
  52. 2
      selfdrive/test/longitudinal_maneuvers/plant.py
  53. 71
      selfdrive/test/process_replay/README.md
  54. 2
      selfdrive/test/process_replay/__init__.py
  55. 2
      selfdrive/test/process_replay/compare_logs.py
  56. 4
      selfdrive/test/process_replay/helpers.py
  57. 5
      selfdrive/test/process_replay/process_replay.py
  58. 2
      selfdrive/test/process_replay/ref_commit
  59. 5
      selfdrive/test/process_replay/test_fuzzy.py
  60. 3
      selfdrive/ui/SConscript
  61. 239
      selfdrive/ui/qt/maps/map.cc
  62. 45
      selfdrive/ui/qt/maps/map.h
  63. 57
      selfdrive/ui/qt/maps/map_eta.cc
  64. 23
      selfdrive/ui/qt/maps/map_eta.h
  65. 146
      selfdrive/ui/qt/maps/map_instructions.cc
  66. 27
      selfdrive/ui/qt/maps/map_instructions.h
  67. 10
      selfdrive/ui/qt/maps/map_settings.cc
  68. 1
      selfdrive/ui/qt/maps/map_settings.h
  69. 3
      selfdrive/ui/qt/offroad/networking.cc
  70. 4
      selfdrive/ui/translations/main_de.ts
  71. 4
      selfdrive/ui/translations/main_ja.ts
  72. 4
      selfdrive/ui/translations/main_ko.ts
  73. 4
      selfdrive/ui/translations/main_pt-BR.ts
  74. 4
      selfdrive/ui/translations/main_zh-CHS.ts
  75. 4
      selfdrive/ui/translations/main_zh-CHT.ts
  76. 2
      system/camerad/cameras/camera_qcom2.cc
  77. 44
      system/hardware/tici/agnos.json
  78. 42
      system/hardware/tici/hardware.py
  79. 31
      system/hardware/tici/tests/test_hardware.py
  80. 41
      system/loggerd/deleter.py
  81. 19
      system/loggerd/loggerd.cc
  82. 3
      system/loggerd/loggerd.h
  83. 15
      system/loggerd/tests/loggerd_tests_common.py
  84. 99
      system/loggerd/tests/test_deleter.py
  85. 87
      system/loggerd/tests/test_loggerd.py
  86. 4
      system/loggerd/tests/test_uploader.py
  87. 2
      system/sensord/rawgps/compare.py
  88. 3
      system/sensord/rawgps/rawgpsd.py
  89. 2
      system/sensord/sensors/bmx055_accel.cc
  90. 2
      system/sensord/sensors/bmx055_gyro.cc
  91. 2
      system/sensord/sensors/bmx055_magn.cc
  92. 4
      system/sensord/sensors/lsm6ds3_accel.cc
  93. 4
      system/sensord/sensors/lsm6ds3_gyro.cc
  94. 2
      system/sensord/sensors/mmc5603nj_magn.cc
  95. 4
      system/sensord/tests/test_sensord.py
  96. 2
      system/ubloxd/ublox_msg.cc
  97. 9
      tools/cabana/SConscript
  98. 6
      tools/cabana/messageswidget.cc
  99. 4
      tools/cabana/messageswidget.h
  100. 2
      tools/cabana/signalview.cc
  101. Some files were not shown because too many files have changed in this diff Show More

@ -21,6 +21,7 @@ jobs:
IMAGE_NAME: openpilot-prebuilt IMAGE_NAME: openpilot-prebuilt
steps: steps:
- name: Wait for green check mark - name: Wait for green check mark
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: lewagon/wait-on-check-action@e2558238c09778af25867eb5de5a3ce4bbae3dcd uses: lewagon/wait-on-check-action@e2558238c09778af25867eb5de5a3ce4bbae3dcd
with: with:
ref: master ref: master
@ -38,3 +39,5 @@ jobs:
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest
docker tag $DOCKER_REGISTRY/$IMAGE_NAME:latest $DOCKER_REGISTRY/$IMAGE_NAME:$GITHUB_SHA
docker push $DOCKER_REGISTRY/$IMAGE_NAME:$GITHUB_SHA

@ -136,6 +136,11 @@ jobs:
run: | run: |
source tools/openpilot_env.sh source tools/openpilot_env.sh
poetry run scons -j$(nproc) poetry run scons -j$(nproc)
- name: Run tests
run: |
source tools/openpilot_env.sh
export PYTHONPATH=$PWD
poetry run tools/plotjuggler/test_plotjuggler.py
- name: Pre Cache - Cleanup scons cache - name: Pre Cache - Cleanup scons cache
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
run: | run: |
@ -167,7 +172,6 @@ jobs:
name: docker push name: docker push
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
needs: static_analysis # hack to ensure slow tests run first since this and static_analysis are fast
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
@ -178,12 +182,16 @@ jobs:
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest
docker tag $DOCKER_REGISTRY/$BASE_IMAGE:latest $DOCKER_REGISTRY/$BASE_IMAGE:$GITHUB_SHA
docker push $DOCKER_REGISTRY/$BASE_IMAGE:$GITHUB_SHA
- name: Build CL Docker image - name: Build CL Docker image
run: eval "$BUILD_CL" run: eval "$BUILD_CL"
- name: Push to container registry - name: Push to container registry
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest docker push $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest
docker tag $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest $DOCKER_REGISTRY/$CL_BASE_IMAGE:$GITHUB_SHA
docker push $DOCKER_REGISTRY/$CL_BASE_IMAGE:$GITHUB_SHA
static_analysis: static_analysis:
name: static analysis name: static analysis

@ -32,6 +32,7 @@ repos:
entry: mypy entry: mypy
language: system language: system
types: [python] types: [python]
args: ['--explicit-package-bases']
exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)'
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 6.0.0 rev: 6.0.0

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

31
Jenkinsfile vendored

@ -18,6 +18,12 @@ export GIT_SSH_COMMAND="ssh -i /data/gitkey"
source ~/.bash_profile source ~/.bash_profile
if [ -f /TICI ]; then if [ -f /TICI ]; then
source /etc/profile source /etc/profile
if ! systemctl is-active --quiet systemd-resolved; then
echo "restarting resolved"
sudo systemctl start systemd-resolved
sleep 3
fi
fi fi
if [ -f /data/openpilot/launch_env.sh ]; then if [ -f /data/openpilot/launch_env.sh ]; then
source /data/openpilot/launch_env.sh source /data/openpilot/launch_env.sh
@ -128,6 +134,28 @@ pipeline {
} }
*/ */
stage('scons build test') {
agent {
dockerfile {
filename 'Dockerfile.openpilot_base'
args '--user=root'
}
}
steps {
sh "git config --global --add safe.directory '*'"
sh "git submodule update --init --depth=1 --recursive"
sh "scons --clean && scons --no-cache -j42"
sh "scons --clean && scons --no-cache --random -j42"
}
post {
always {
sh "rm -rf ${WORKSPACE}/* || true"
sh "rm -rf .* || true"
}
}
}
stage('tizi-tests') { stage('tizi-tests') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps { steps {
@ -138,7 +166,8 @@ pipeline {
["test sensord", "cd system/sensord/tests && python -m unittest test_sensord.py"], ["test sensord", "cd system/sensord/tests && python -m unittest test_sensord.py"],
["test camerad", "python system/camerad/test/test_camerad.py"], ["test camerad", "python system/camerad/test/test_camerad.py"],
["test exposure", "python system/camerad/test/test_exposure.py"], ["test exposure", "python system/camerad/test/test_exposure.py"],
["test amp", "python system/hardware/tici/tests/test_amplifier.py"], ["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"],
["test hw", "pytest system/hardware/tici/tests/test_hardware.py"],
["test rawgpsd", "python system/sensord/rawgps/test_rawgps.py"], ["test rawgpsd", "python system/sensord/rawgps/test_rawgps.py"],
]) ])
} }

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

@ -53,7 +53,7 @@ int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int p
std::string gpiochip_path = "/dev/gpiochip" + std::to_string(gpiochiop_id); std::string gpiochip_path = "/dev/gpiochip" + std::to_string(gpiochiop_id);
int fd = open(gpiochip_path.c_str(), O_RDONLY); int fd = open(gpiochip_path.c_str(), O_RDONLY);
if (fd < 0) { if (fd < 0) {
LOGE("Error opening gpiochip0 fd") LOGE("Error opening gpiochip0 fd");
return -1; return -1;
} }

@ -1,4 +1,4 @@
import glob from functools import cache
from typing import Optional, List from typing import Optional, List
def gpio_init(pin: int, output: bool) -> None: def gpio_init(pin: int, output: bool) -> None:
@ -25,12 +25,20 @@ def gpio_read(pin: int) -> Optional[bool]:
return val return val
def get_irq_for_action(action: str) -> List[int]: @cache
ret = [] def get_irq_action(irq: int) -> List[str]:
for fn in glob.glob('/sys/kernel/irq/*/actions'): try:
with open(fn) as f: with open(f"/sys/kernel/irq/{irq}/actions") as f:
actions = f.read().strip().split(',') actions = f.read().strip().split(',')
if action in actions: return actions
irq = int(fn.split('/')[-2]) except FileNotFoundError:
return []
def get_irqs_for_action(action: str) -> List[str]:
ret = []
with open("/proc/interrupts") as f:
for l in f.readlines():
irq = l.split(':')[0].strip()
if irq.isdigit() and action in get_irq_action(irq):
ret.append(irq) ret.append(irq)
return ret return ret

@ -177,6 +177,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"Offroad_UpdateFailed", CLEAR_ON_MANAGER_START}, {"Offroad_UpdateFailed", CLEAR_ON_MANAGER_START},
{"OpenpilotEnabledToggle", PERSISTENT}, {"OpenpilotEnabledToggle", PERSISTENT},
{"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION},
{"PandaLogState", PERSISTENT},
{"PandaSignatures", CLEAR_ON_MANAGER_START}, {"PandaSignatures", CLEAR_ON_MANAGER_START},
{"Passive", PERSISTENT}, {"Passive", PERSISTENT},
{"PrimeType", PERSISTENT}, {"PrimeType", PERSISTENT},

@ -135,4 +135,3 @@ void cloudlog_te(int levelnum, const char* filename, int lineno, const char* fun
cloudlog_t_common(levelnum, filename, lineno, func, frame_id, fmt, args); cloudlog_t_common(levelnum, filename, lineno, func, frame_id, fmt, args);
va_end(args); va_end(args);
} }

@ -21,11 +21,11 @@ void cloudlog_te(int levelnum, const char* filename, int lineno, const char* fun
#define cloudlog(lvl, fmt, ...) cloudlog_e(lvl, __FILE__, __LINE__, \ #define cloudlog(lvl, fmt, ...) cloudlog_e(lvl, __FILE__, __LINE__, \
__func__, \ __func__, \
fmt, ## __VA_ARGS__); fmt, ## __VA_ARGS__)
#define cloudlog_t(lvl, ...) cloudlog_te(lvl, __FILE__, __LINE__, \ #define cloudlog_t(lvl, ...) cloudlog_te(lvl, __FILE__, __LINE__, \
__func__, \ __func__, \
__VA_ARGS__); __VA_ARGS__)
#define cloudlog_rl(burst, millis, lvl, fmt, ...) \ #define cloudlog_rl(burst, millis, lvl, fmt, ...) \

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

6281
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
@ -26,15 +26,13 @@ hexdump = "^3.3"
Jinja2 = "^3.1.2" 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" numpy = "==1.23.0" # locked pending deprecation fixes in xx
numpy = "^1.23.0"
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"
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,16 +41,15 @@ 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"
smbus2 = "^0.4.2" 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 +59,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 +76,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 +114,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 +159,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]

@ -1 +1 @@
Subproject commit 973c04dc427ddddc3e1dee8bf20af0c8c99577dd Subproject commit de6e160b0e6ef4e286461d6d08297eed8f6fc493

@ -189,7 +189,6 @@ selfdrive/controls/lib/lateral_planner.py
selfdrive/controls/lib/longcontrol.py selfdrive/controls/lib/longcontrol.py
selfdrive/controls/lib/longitudinal_planner.py selfdrive/controls/lib/longitudinal_planner.py
selfdrive/controls/lib/pid.py selfdrive/controls/lib/pid.py
selfdrive/controls/lib/radar_helpers.py
selfdrive/controls/lib/vehicle_model.py selfdrive/controls/lib/vehicle_model.py
selfdrive/controls/lib/lateral_mpc_lib/.gitignore selfdrive/controls/lib/lateral_mpc_lib/.gitignore

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

@ -3,6 +3,7 @@
import os import os
import usb1 import usb1
import time import time
import json
import subprocess import subprocess
from typing import List, NoReturn from typing import List, NoReturn
from functools import cmp_to_key from functools import cmp_to_key
@ -23,6 +24,48 @@ def get_expected_signature(panda: Panda) -> bytes:
cloudlog.exception("Error computing expected signature") cloudlog.exception("Error computing expected signature")
return b"" return b""
def read_panda_logs(panda: Panda) -> None:
"""
Forward panda logs to the cloud
"""
params = Params()
serial = panda.get_usb_serial()
log_state = {}
try:
l = json.loads(params.get("PandaLogState"))
for k, v in l.items():
if isinstance(k, str) and isinstance(v, int):
log_state[k] = v
except (TypeError, json.JSONDecodeError):
cloudlog.exception("failed to parse PandaLogState")
try:
if serial in log_state:
logs = panda.get_logs(last_id=log_state[serial])
else:
logs = panda.get_logs(get_all=True)
# truncate logs to 100 entries if needed
MAX_LOGS = 100
if len(logs) > MAX_LOGS:
cloudlog.warning(f"Panda {serial} has {len(logs)} logs, truncating to {MAX_LOGS}")
logs = logs[-MAX_LOGS:]
# update log state
if len(logs) > 0:
log_state[serial] = logs[-1]["id"]
for log in logs:
if log['timestamp'] is not None:
log['timestamp'] = log['timestamp'].isoformat()
cloudlog.event("panda_log", **log, serial=serial)
params.put("PandaLogState", json.dumps(log_state))
except Exception:
cloudlog.exception(f"Error getting logs for panda {serial}")
def flash_panda(panda_serial: str) -> Panda: def flash_panda(panda_serial: str) -> Panda:
try: try:
@ -47,7 +90,6 @@ def flash_panda(panda_serial: str) -> Panda:
if panda.bootstub: if panda.bootstub:
bootstub_version = panda.get_version() bootstub_version = panda.get_version()
cloudlog.info(f"Flashed firmware not booting, flashing development bootloader. {bootstub_version=}, {internal_panda=}") cloudlog.info(f"Flashed firmware not booting, flashing development bootloader. {bootstub_version=}, {internal_panda=}")
if internal_panda: if internal_panda:
HARDWARE.recover_internal_panda() HARDWARE.recover_internal_panda()
panda.recover(reset=(not internal_panda)) panda.recover(reset=(not internal_panda))
@ -127,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))
@ -139,6 +181,8 @@ def main() -> NoReturn:
params.put_bool("PandaHeartbeatLost", True) params.put_bool("PandaHeartbeatLost", True)
cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial()) cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial())
read_panda_logs(panda)
if first_run: if first_run:
if panda.is_internal(): if panda.is_internal():
# update time from RTC # update time from RTC

@ -6,6 +6,7 @@ import unittest
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal import log from cereal import log
from common.gpio import gpio_set, gpio_init from common.gpio import gpio_set, gpio_init
from common.params import Params
from panda import Panda, PandaDFU, PandaProtocolMismatch from panda import Panda, PandaDFU, PandaProtocolMismatch
from selfdrive.test.helpers import phone_only from selfdrive.test.helpers import phone_only
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
@ -17,6 +18,10 @@ HERE = os.path.dirname(os.path.realpath(__file__))
class TestPandad(unittest.TestCase): class TestPandad(unittest.TestCase):
def setUp(self):
self.params = Params()
self.start_log_state = self.params.get("PandaLogState")
def tearDown(self): def tearDown(self):
managed_processes['pandad'].stop() managed_processes['pandad'].stop()
@ -30,6 +35,10 @@ class TestPandad(unittest.TestCase):
if sm['peripheralState'].pandaType == log.PandaState.PandaType.unknown: if sm['peripheralState'].pandaType == log.PandaState.PandaType.unknown:
raise Exception("boardd failed to start") raise Exception("boardd failed to start")
# simple check that we did something with the panda logs
cur_log_state = self.params.get("PandaLogState")
assert cur_log_state != self.start_log_state
def _go_to_dfu(self): def _go_to_dfu(self):
HARDWARE.recover_internal_panda() HARDWARE.recover_internal_panda()
assert Panda.wait_for_dfu(None, 10) assert Panda.wait_for_dfu(None, 10)

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

@ -7,7 +7,7 @@ from cereal import car, log
from common.numpy_fast import clip from common.numpy_fast import clip
from common.realtime import sec_since_boot, config_realtime_process, Priority, Ratekeeper, DT_CTRL from common.realtime import sec_since_boot, config_realtime_process, Priority, Ratekeeper, DT_CTRL
from common.profiler import Profiler from common.profiler import Profiler
from common.params import Params, put_nonblocking from common.params import Params, put_nonblocking, put_bool_nonblocking
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal.visionipc import VisionIpcClient, VisionStreamType from cereal.visionipc import VisionIpcClient, VisionStreamType
from common.conversions import Conversions as CV from common.conversions import Conversions as CV
@ -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
@ -448,7 +448,7 @@ class Controls:
self.initialized = True self.initialized = True
self.set_initial_state() self.set_initial_state()
Params().put_bool("ControlsReady", True) put_bool_nonblocking("ControlsReady", True)
# Check for CAN timeout # Check for CAN timeout
if not can_strs: if not can_strs:

@ -57,9 +57,10 @@ source_list = ['lat_mpc.py',
lenv = env.Clone() lenv = env.Clone()
lenv.Clean(generated_files, Dir(gen)) lenv.Clean(generated_files, Dir(gen))
lenv.Command(generated_files, generated_lat = lenv.Command(generated_files,
source_list, source_list,
f"cd {Dir('.').abspath} && python3 lat_mpc.py") f"cd {Dir('.').abspath} && python3 lat_mpc.py")
lenv.Depends(generated_lat, "#common")
lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES")
lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES")

@ -64,9 +64,10 @@ source_list = ['long_mpc.py',
lenv = env.Clone() lenv = env.Clone()
lenv.Clean(generated_files, Dir(gen)) lenv.Clean(generated_files, Dir(gen))
lenv.Command(generated_files, generated_long = lenv.Command(generated_files,
source_list, source_list,
f"cd {Dir('.').abspath} && python3 long_mpc.py") f"cd {Dir('.').abspath} && python3 long_mpc.py")
lenv.Depends(generated_long, "#cereal")
lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES")
lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES")

@ -7,7 +7,7 @@ from common.numpy_fast import clip
from system.swaglog import cloudlog from system.swaglog import cloudlog
# WARNING: imports outside of constants will not trigger a rebuild # WARNING: imports outside of constants will not trigger a rebuild
from selfdrive.modeld.constants import index_function from selfdrive.modeld.constants import index_function
from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU from selfdrive.controls.radard import _LEAD_ACCEL_TAU
if __name__ == '__main__': # generating code if __name__ == '__main__': # generating code
from third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver from third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
@ -357,7 +357,7 @@ class LongitudinalMpc:
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
v_lower, v_lower,
v_upper) v_upper)
cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, get_T_FOLLOW(personality)) cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow)
x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle])
self.source = SOURCES[np.argmin(x_obstacles[0])] self.source = SOURCES[np.argmin(x_obstacles[0])]

@ -1,159 +0,0 @@
from common.numpy_fast import mean
from common.kalman.simple_kalman import KF1D
# Default lead acceleration decay set to 50% at 1s
_LEAD_ACCEL_TAU = 1.5
# radar tracks
SPEED, ACCEL = 0, 1 # Kalman filter states enum
# stationary qualification parameters
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_CAMERA = 1.52 # RADAR is ~ 1.5m ahead from center of mesh frame
class Track():
def __init__(self, v_lead, kalman_params):
self.cnt = 0
self.aLeadTau = _LEAD_ACCEL_TAU
self.K_A = kalman_params.A
self.K_C = kalman_params.C
self.K_K = kalman_params.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):
# relative values, copy
self.dRel = d_rel # LONG_DIST
self.yRel = y_rel # -LAT_DIST
self.vRel = v_rel # REL_SPEED
self.vLead = v_lead
self.measured = measured # measured or estimate
# computed velocity and accelerations
if self.cnt > 0:
self.kf.update(self.vLead)
self.vLeadK = float(self.kf.x[SPEED][0])
self.aLeadK = float(self.kf.x[ACCEL][0])
# Learn if constant acceleration
if abs(self.aLeadK) < 0.5:
self.aLeadTau = _LEAD_ACCEL_TAU
else:
self.aLeadTau *= 0.9
self.cnt += 1
def get_key_for_cluster(self):
# Weigh y higher since radar is inaccurate in this dimension
return [self.dRel, self.yRel*2, self.vRel]
def reset_a_lead(self, aLeadK, aLeadTau):
self.kf = KF1D([[self.vLead], [aLeadK]], self.K_A, self.K_C, self.K_K)
self.aLeadK = aLeadK
self.aLeadTau = aLeadTau
class Cluster():
def __init__(self):
self.tracks = set()
def add(self, t):
# add the first track
self.tracks.add(t)
# TODO: make generic
@property
def dRel(self):
return mean([t.dRel for t in self.tracks])
@property
def yRel(self):
return mean([t.yRel for t in self.tracks])
@property
def vRel(self):
return mean([t.vRel for t in self.tracks])
@property
def aRel(self):
return mean([t.aRel for t in self.tracks])
@property
def vLead(self):
return mean([t.vLead for t in self.tracks])
@property
def dPath(self):
return mean([t.dPath for t in self.tracks])
@property
def vLat(self):
return mean([t.vLat for t in self.tracks])
@property
def vLeadK(self):
return mean([t.vLeadK for t in self.tracks])
@property
def aLeadK(self):
if all(t.cnt <= 1 for t in self.tracks):
return 0.
else:
return mean([t.aLeadK for t in self.tracks if t.cnt > 1])
@property
def aLeadTau(self):
if all(t.cnt <= 1 for t in self.tracks):
return _LEAD_ACCEL_TAU
else:
return mean([t.aLeadTau for t in self.tracks if t.cnt > 1])
@property
def measured(self):
return any(t.measured for t in self.tracks)
def get_RadarState(self, model_prob=0.0):
return {
"dRel": float(self.dRel),
"yRel": float(self.yRel),
"vRel": float(self.vRel),
"vLead": float(self.vLead),
"vLeadK": float(self.vLeadK),
"aLeadK": float(self.aLeadK),
"status": True,
"fcw": self.is_potential_fcw(model_prob),
"modelProb": model_prob,
"radar": True,
"aLeadTau": float(self.aLeadTau)
}
def get_RadarState_from_vision(self, lead_msg, v_ego, model_v_ego):
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 __str__(self):
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
# 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)
def is_potential_fcw(self, model_prob):
return model_prob > .9

@ -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,20 +1,34 @@
#!/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
from selfdrive.controls.lib.radar_helpers import Cluster, Track, RADAR_TO_CAMERA
from system.swaglog import cloudlog from system.swaglog import cloudlog
from third_party.cluster.fastcluster_py import cluster_points_centroid
from common.kalman.simple_kalman import KF1D
class KalmanParams():
def __init__(self, dt): # Default lead acceleration decay set to 50% at 1s
_LEAD_ACCEL_TAU = 1.5
# radar tracks
SPEED, ACCEL = 0, 1 # Kalman filter states enum
# stationary qualification parameters
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_CAMERA = 1.52 # RADAR is ~ 1.5m ahead from center of mesh frame
class KalmanParams:
def __init__(self, dt: float):
# Lead Kalman Filter params, calculating K from A, C, Q, R requires the control library. # 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 # 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" assert dt > .01 and dt < .2, "Radar time step must be between .01s and 0.2s"
@ -35,13 +49,81 @@ class KalmanParams():
self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]] self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]]
def laplacian_pdf(x, mu, b): class Track:
def __init__(self, v_lead: float, kalman_params: KalmanParams):
self.cnt = 0
self.aLeadTau = _LEAD_ACCEL_TAU
self.K_A = kalman_params.A
self.K_C = kalman_params.C
self.K_K = kalman_params.K
self.kf = KF1D([[v_lead], [0.0]], self.K_A, self.K_C, self.K_K)
def update(self, d_rel: float, y_rel: float, v_rel: float, v_lead: float, measured: float):
# relative values, copy
self.dRel = d_rel # LONG_DIST
self.yRel = y_rel # -LAT_DIST
self.vRel = v_rel # REL_SPEED
self.vLead = v_lead
self.measured = measured # measured or estimate
# computed velocity and accelerations
if self.cnt > 0:
self.kf.update(self.vLead)
self.vLeadK = float(self.kf.x[SPEED][0])
self.aLeadK = float(self.kf.x[ACCEL][0])
# Learn if constant acceleration
if abs(self.aLeadK) < 0.5:
self.aLeadTau = _LEAD_ACCEL_TAU
else:
self.aLeadTau *= 0.9
self.cnt += 1
def get_key_for_cluster(self):
# Weigh y higher since radar is inaccurate in this dimension
return [self.dRel, self.yRel*2, self.vRel]
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.aLeadK = aLeadK
self.aLeadTau = aLeadTau
def get_RadarState(self, model_prob: float = 0.0):
return {
"dRel": float(self.dRel),
"yRel": float(self.yRel),
"vRel": float(self.vRel),
"vLead": float(self.vLead),
"vLeadK": float(self.vLeadK),
"aLeadK": float(self.aLeadK),
"status": True,
"fcw": self.is_potential_fcw(model_prob),
"modelProb": model_prob,
"radar": True,
"aLeadTau": float(self.aLeadTau)
}
def potential_low_speed_lead(self, v_ego: float):
# 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
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: float):
return model_prob > .9
def __str__(self):
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 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_cluster(v_ego, lead, clusters): def match_vision_to_track(v_ego: float, lead: capnp._DynamicStructReader, tracks: Dict[int, Track]):
# match vision point to best statistical cluster match
offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA
def prob(c): def prob(c):
@ -52,59 +134,84 @@ def match_vision_to_cluster(v_ego, lead, clusters):
# This is isn't exactly right, but good heuristic # This is isn't exactly right, but good heuristic
return prob_d * prob_y * prob_v return prob_d * prob_y * prob_v
cluster = max(clusters, key=prob) track = max(tracks.values(), key=prob)
# if no 'sane' match is found return -1 # if no 'sane' match is found return -1
# stationary radar points can be false positives # stationary radar points can be false positives
dist_sane = abs(cluster.dRel - offset_vision_dist) < max([(offset_vision_dist)*.25, 5.0]) dist_sane = abs(track.dRel - offset_vision_dist) < max([(offset_vision_dist)*.25, 5.0])
vel_sane = (abs(cluster.vRel + v_ego - lead.v[0]) < 10) or (v_ego + cluster.vRel > 3) vel_sane = (abs(track.vRel + v_ego - lead.v[0]) < 10) or (v_ego + track.vRel > 3)
if dist_sane and vel_sane: if dist_sane and vel_sane:
return cluster return track
else: else:
return None return None
def get_lead(v_ego, ready, clusters, 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(clusters) > 0 and ready and lead_msg.prob > .5: if len(tracks) > 0 and ready and lead_msg.prob > .5:
cluster = match_vision_to_cluster(v_ego, lead_msg, clusters) track = match_vision_to_track(v_ego, lead_msg, tracks)
else: else:
cluster = None track = None
lead_dict = {'status': False} lead_dict = {'status': False}
if cluster is not None: if track is not None:
lead_dict = cluster.get_RadarState(lead_msg.prob) lead_dict = track.get_RadarState(lead_msg.prob)
elif (cluster is None) and ready and (lead_msg.prob > .5): elif (track is None) and ready and (lead_msg.prob > .5):
lead_dict = Cluster().get_RadarState_from_vision(lead_msg, v_ego, model_v_ego) lead_dict = get_RadarState_from_vision(lead_msg, v_ego, model_v_ego)
if low_speed_override: if low_speed_override:
low_speed_clusters = [c for c in clusters if c.potential_low_speed_lead(v_ego)] low_speed_tracks = [c for c in tracks.values() if c.potential_low_speed_lead(v_ego)]
if len(low_speed_clusters) > 0: if len(low_speed_tracks) > 0:
closest_cluster = min(low_speed_clusters, key=lambda c: c.dRel) closest_track = min(low_speed_tracks, key=lambda c: c.dRel)
# Only choose new cluster if it is actually closer than the previous one # Only choose new track if it is actually closer than the previous one
if (not lead_dict['status']) or (closest_cluster.dRel < lead_dict['dRel']): if (not lead_dict['status']) or (closest_track.dRel < lead_dict['dRel']):
lead_dict = closest_cluster.get_RadarState() lead_dict = closest_track.get_RadarState()
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)
@ -112,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 ***
@ -132,41 +239,12 @@ class RadarD():
self.tracks[ids] = Track(v_lead, self.kalman_params) self.tracks[ids] = Track(v_lead, self.kalman_params)
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])
idens = list(sorted(self.tracks.keys()))
track_pts = [self.tracks[iden].get_key_for_cluster() for iden in idens]
# If we have multiple points, cluster them
if len(track_pts) > 1:
cluster_idxs = cluster_points_centroid(track_pts, 2.5)
clusters = [None] * (max(cluster_idxs) + 1)
for idx in range(len(track_pts)):
cluster_i = cluster_idxs[idx]
if clusters[cluster_i] is None:
clusters[cluster_i] = Cluster()
clusters[cluster_i].add(self.tracks[idens[idx]])
elif len(track_pts) == 1:
# FIXME: cluster_point_centroid hangs forever if len(track_pts) == 1
cluster_idxs = [0]
clusters = [Cluster()]
clusters[0].add(self.tracks[idens[0]])
else:
clusters = []
# if a new point, reset accel to the rest of the cluster
for idx in range(len(track_pts)):
if self.tracks[idens[idx]].cnt <= 1:
aLeadK = clusters[cluster_idxs[idx]].aLeadK
aLeadTau = clusters[cluster_idxs[idx]].aLeadTau
self.tracks[idens[idx]].reset_a_lead(aLeadK, aLeadTau)
# *** 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]
@ -174,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, clusters, 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, clusters, 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
@ -214,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)

@ -103,6 +103,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()
@ -269,8 +270,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")
@ -297,6 +297,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

@ -8,7 +8,7 @@ from common.realtime import Ratekeeper, DT_MDL
from selfdrive.controls.lib.longcontrol import LongCtrlState from selfdrive.controls.lib.longcontrol import LongCtrlState
from selfdrive.modeld.constants import T_IDXS from selfdrive.modeld.constants import T_IDXS
from selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner
from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU from selfdrive.controls.radard import _LEAD_ACCEL_TAU
class Plant: class Plant:

@ -15,9 +15,10 @@ Currently the following processes are tested:
* calibrationd * calibrationd
* dmonitoringd * dmonitoringd
* locationd * locationd
* laikad
* paramsd * paramsd
* ubloxd * ubloxd
* laikad
* torqued
### Usage ### Usage
``` ```
@ -45,3 +46,71 @@ To generate new logs:
`./test_processes.py` `./test_processes.py`
Then, check in the new logs using git-lfs. Make sure to also update the `ref_commit` file to the current commit. Then, check in the new logs using git-lfs. Make sure to also update the `ref_commit` file to the current commit.
## API
Process replay test suite exposes programmatic APIs for simultaneously running processes or groups of processes on provided logs.
```py
def replay_process_with_name(name: Union[str, Iterable[str]], lr: Union[LogReader, List[capnp._DynamicStructReader]], *args, **kwargs) -> List[capnp._DynamicStructReader]:
def replay_process(
cfg: Union[ProcessConfig, Iterable[ProcessConfig]], lr: Union[LogReader, List[capnp._DynamicStructReader]], frs: Optional[Dict[str, Any]] = None,
fingerprint: Optional[str] = None, return_all_logs: bool = False, custom_params: Optional[Dict[str, Any]] = None, disable_progress: bool = False
) -> List[capnp._DynamicStructReader]:
```
Example usage:
```py
from selfdrive.test.process_replay import replay_process_with_name
from tools.lib.logreader import LogReader
lr = LogReader(...)
# provide a name of the process to replay
output_logs = replay_process_with_name('locationd', lr)
# or list of names
output_logs = replay_process_with_name(['ubloxd', 'locationd', 'laikad'], lr)
```
Supported processes:
* controlsd
* radard
* plannerd
* calibrationd
* dmonitoringd
* locationd
* paramsd
* ubloxd
* laikad
* torqued
* modeld
* dmonitoringmodeld
Certain processes may require an initial state, which is usually supplied within `Params` and persisting from segment to segment (e.g CalibrationParams, LiveParameters). The `custom_params` is dictionary used to prepopulate `Params` with arbitrary values. The `get_custom_params_from_lr` helper is provided to fetch meaningful values from log files.
```py
from selfdrive.test.process_replay import get_custom_params_from_lr
previous_segment_lr = LogReader(...)
current_segment_lr = LogReader(...)
custom_params = get_custom_params_from_lr(previous_segment_lr, 'last')
output_logs = replay_process_with_name('calibrationd', lr, custom_params=custom_params)
```
Replaying processes that use VisionIPC (e.g. modeld, dmonitoringmodeld) require additional `frs` dictionary with camera states as keys and `FrameReader` objects as values.
```py
from tools.lib.framereader import FrameReader
frs = {
'roadCameraState': FrameReader(...),
'wideRoadCameraState': FrameReader(...),
'driverCameraState': FrameReader(...),
}
output_logs = replay_process_with_name(['modeld', 'dmonitoringmodeld'], lr, frs=frs)
```

@ -1 +1 @@
from selfdrive.test.process_replay.process_replay import CONFIGS, get_process_config, replay_process, replay_process_with_name # noqa: F401 from selfdrive.test.process_replay.process_replay import CONFIGS, get_process_config, get_custom_params_from_lr, replay_process, replay_process_with_name # noqa: F401

@ -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 +1 @@
219a815856d8984cb4933d83db9a15bf7cd09f16 af03f2ddbc5244f9a885445c0452987a4bb81302

@ -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);
@ -112,7 +108,21 @@ void MapWindow::initLayers() {
m_map->setPaintProperty("navLayer", "line-color-transition", transition); m_map->setPaintProperty("navLayer", "line-color-transition", transition);
m_map->setPaintProperty("navLayer", "line-width", 7.5); m_map->setPaintProperty("navLayer", "line-width", 7.5);
m_map->setLayoutProperty("navLayer", "line-cap", "round"); m_map->setLayoutProperty("navLayer", "line-cap", "round");
m_map->addAnnotationIcon("default_marker", QImage("../assets/navigation/default_marker.svg")); }
if (!m_map->layerExists("pinLayer")) {
qDebug() << "Initializing pinLayer";
m_map->addImage("default_marker", QImage("../assets/navigation/default_marker.svg"));
QVariantMap pin;
pin["id"] = "pinLayer";
pin["type"] = "symbol";
pin["source"] = "pinSource";
m_map->addLayer(pin);
m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport");
m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker");
m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true);
m_map->setLayoutProperty("pinLayer", "icon-allow-overlap", true);
m_map->setLayoutProperty("pinLayer", "symbol-sort-key", 0);
m_map->setLayoutProperty("pinLayer", "icon-anchor", "bottom");
} }
if (!m_map->layerExists("carPosLayer")) { if (!m_map->layerExists("carPosLayer")) {
qDebug() << "Initializing carPosLayer"; qDebug() << "Initializing carPosLayer";
@ -128,6 +138,7 @@ void MapWindow::initLayers() {
m_map->setLayoutProperty("carPosLayer", "icon-size", 0.5); m_map->setLayoutProperty("carPosLayer", "icon-size", 0.5);
m_map->setLayoutProperty("carPosLayer", "icon-ignore-placement", true); m_map->setLayoutProperty("carPosLayer", "icon-ignore-placement", true);
m_map->setLayoutProperty("carPosLayer", "icon-allow-overlap", true); m_map->setLayoutProperty("carPosLayer", "icon-allow-overlap", true);
// TODO: remove, symbol-sort-key does not seem to matter outside of each layer
m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0); m_map->setLayoutProperty("carPosLayer", "symbol-sort-key", 0);
} }
} }
@ -191,7 +202,14 @@ void MapWindow::updateState(const UIState &s) {
} }
initLayers(); initLayers();
setError(locationd_valid ? "" : tr("Waiting for GPS")); if (!locationd_valid) {
setError(tr("Waiting for GPS"));
} else if (routing_problem) {
setError(tr("Waiting for route"));
} else {
setError("");
}
if (locationd_valid) { if (locationd_valid) {
// Update current location marker // Update current location marker
auto point = coordinate_to_collection(*last_position); auto point = coordinate_to_collection(*last_position);
@ -216,6 +234,13 @@ void MapWindow::updateState(const UIState &s) {
} }
if (sm.updated("navInstruction")) { if (sm.updated("navInstruction")) {
// an invalid navInstruction packet with a nav destination is only possible if:
// - API exception/no internet
// - route response is empty
// - any time navd is waiting for recompute_countdown
auto dest = coordinate_from_param("NavDestination");
routing_problem = !sm.valid("navInstruction") && dest.has_value();
if (sm.valid("navInstruction")) { if (sm.valid("navInstruction")) {
auto i = sm["navInstruction"].getNavInstruction(); auto i = sm["navInstruction"].getNavInstruction();
map_eta->updateETA(i.getTimeRemaining(), i.getTimeRemainingTypical(), i.getDistanceRemaining()); map_eta->updateETA(i.getTimeRemaining(), i.getTimeRemainingTypical(), i.getDistanceRemaining());
@ -276,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;
} }
@ -373,6 +402,7 @@ void MapWindow::offroadTransition(bool offroad) {
if (offroad) { if (offroad) {
clearRoute(); clearRoute();
uiState()->scene.navigate_on_openpilot = false; uiState()->scene.navigate_on_openpilot = false;
routing_problem = false;
} else { } else {
auto dest = coordinate_from_param("NavDestination"); auto dest = coordinate_from_param("NavDestination");
emit requestVisible(dest.has_value()); emit requestVisible(dest.has_value());
@ -381,197 +411,16 @@ void MapWindow::offroadTransition(bool offroad) {
} }
void MapWindow::updateDestinationMarker() { void MapWindow::updateDestinationMarker() {
if (marker_id != -1) { m_map->setPaintProperty("pinLayer", "visibility", "none");
m_map->removeAnnotation(marker_id);
marker_id = -1;
}
auto nav_dest = coordinate_from_param("NavDestination"); auto nav_dest = coordinate_from_param("NavDestination");
if (nav_dest.has_value()) { if (nav_dest.has_value()) {
auto ano = QMapbox::SymbolAnnotation {*nav_dest, "default_marker"}; auto point = coordinate_to_collection(*nav_dest);
marker_id = m_map->addAnnotation(QVariant::fromValue<QMapbox::SymbolAnnotation>(ano)); QMapbox::Feature feature(QMapbox::Feature::PointType, point, {}, {});
} QVariantMap pinSource;
} pinSource["type"] = "geojson";
pinSource["data"] = QVariant::fromValue<QMapbox::Feature>(feature);
MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) { m_map->updateSource("pinSource", pinSource);
is_rhd = Params().getBool("IsRhdDetected"); m_map->setPaintProperty("pinLayer", "visibility", "visible");
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
@ -74,7 +37,6 @@ private:
QMapboxGLSettings m_settings; QMapboxGLSettings m_settings;
QScopedPointer<QMapboxGL> m_map; QScopedPointer<QMapboxGL> m_map;
QMapbox::AnnotationID marker_id = -1;
void initLayers(); void initLayers();
@ -87,8 +49,6 @@ private:
void pinchTriggered(QPinchGesture *gesture); void pinchTriggered(QPinchGesture *gesture);
void setError(const QString &err_str); void setError(const QString &err_str);
bool m_sourceAdded = false;
bool loaded_once = false; bool loaded_once = false;
bool allow_open = true; bool allow_open = true;
@ -102,6 +62,7 @@ private:
std::optional<float> last_bearing; std::optional<float> last_bearing;
FirstOrderFilter velocity_filter; FirstOrderFilter velocity_filter;
bool locationd_valid = false; bool locationd_valid = false;
bool routing_problem = false;
QWidget *map_overlay; QWidget *map_overlay;
QLabel *error; QLabel *error;

@ -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,12 +7,7 @@
#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});
setContentsMargins(0, 0, 0, 0); setContentsMargins(0, 0, 0, 0);
auto *frame = new QVBoxLayout(this); auto *frame = new QVBoxLayout(this);
@ -253,9 +248,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

@ -62,7 +62,6 @@ private:
DestinationWidget *home_widget; DestinationWidget *home_widget;
DestinationWidget *work_widget; DestinationWidget *work_widget;
std::vector<DestinationWidget *> widgets; std::vector<DestinationWidget *> widgets;
QPixmap close_icon;
signals: signals:
void closeSettings(); void closeSettings();

@ -286,6 +286,7 @@ void WifiUI::refresh() {
scanningLabel->setVisible(is_empty); scanningLabel->setVisible(is_empty);
if (is_empty) return; if (is_empty) return;
const bool is_tethering_enabled = wifi->isTetheringEnabled();
QList<Network> sortedNetworks = wifi->seenNetworks.values(); QList<Network> sortedNetworks = wifi->seenNetworks.values();
std::sort(sortedNetworks.begin(), sortedNetworks.end(), compare_by_strength); std::sort(sortedNetworks.begin(), sortedNetworks.end(), compare_by_strength);
@ -313,7 +314,7 @@ void WifiUI::refresh() {
} }
// Forget button // Forget button
if (wifi->isKnownConnection(network.ssid) && !wifi->isTetheringEnabled()) { if (wifi->isKnownConnection(network.ssid) && !is_tethering_enabled) {
QPushButton *forgetBtn = new QPushButton(tr("FORGET")); QPushButton *forgetBtn = new QPushButton(tr("FORGET"));
forgetBtn->setObjectName("forgetBtn"); forgetBtn->setObjectName("forgetBtn");
QObject::connect(forgetBtn, &QPushButton::clicked, [=]() { QObject::connect(forgetBtn, &QPushButton::clicked, [=]() {

@ -403,6 +403,10 @@
<source>Waiting for GPS</source> <source>Waiting for GPS</source>
<translation>Warten auf GPS</translation> <translation>Warten auf GPS</translation>
</message> </message>
<message>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MultiOptionDialog</name> <name>MultiOptionDialog</name>

@ -402,6 +402,10 @@
<source>Waiting for GPS</source> <source>Waiting for GPS</source>
<translation>GPS信号を探しています</translation> <translation>GPS信号を探しています</translation>
</message> </message>
<message>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MultiOptionDialog</name> <name>MultiOptionDialog</name>

@ -402,6 +402,10 @@
<source>Waiting for GPS</source> <source>Waiting for GPS</source>
<translation>GPS </translation> <translation>GPS </translation>
</message> </message>
<message>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MultiOptionDialog</name> <name>MultiOptionDialog</name>

@ -403,6 +403,10 @@
<source>Waiting for GPS</source> <source>Waiting for GPS</source>
<translation>Esperando por GPS</translation> <translation>Esperando por GPS</translation>
</message> </message>
<message>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MultiOptionDialog</name> <name>MultiOptionDialog</name>

@ -402,6 +402,10 @@
<source>Waiting for GPS</source> <source>Waiting for GPS</source>
<translation> GPS</translation> <translation> GPS</translation>
</message> </message>
<message>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MultiOptionDialog</name> <name>MultiOptionDialog</name>

@ -402,6 +402,10 @@
<source>Waiting for GPS</source> <source>Waiting for GPS</source>
<translation> GPS</translation> <translation> GPS</translation>
</message> </message>
<message>
<source>Waiting for route</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>MultiOptionDialog</name> <name>MultiOptionDialog</name>

@ -496,7 +496,7 @@ void CameraState::enqueue_buffer(int i, bool dp) {
strcpy(sync_create.name, "NodeOutputPortFence"); strcpy(sync_create.name, "NodeOutputPortFence");
ret = do_cam_control(multi_cam_state->cam_sync_fd, CAM_SYNC_CREATE, &sync_create, sizeof(sync_create)); ret = do_cam_control(multi_cam_state->cam_sync_fd, CAM_SYNC_CREATE, &sync_create, sizeof(sync_create));
if (ret != 0) { if (ret != 0) {
LOGE("failed to create fence: %d %d", ret, sync_create.sync_obj) LOGE("failed to create fence: %d %d", ret, sync_create.sync_obj);
} }
sync_objs[i] = sync_create.sync_obj; sync_objs[i] = sync_create.sync_obj;

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

@ -8,7 +8,7 @@ from functools import cached_property, lru_cache
from pathlib import Path from pathlib import Path
from cereal import log from cereal import log
from common.gpio import gpio_set, gpio_init, get_irq_for_action from common.gpio import gpio_set, gpio_init, get_irqs_for_action
from system.hardware.base import HardwareBase, ThermalConfig from system.hardware.base import HardwareBase, ThermalConfig
from system.hardware.tici import iwlist from system.hardware.tici import iwlist
from system.hardware.tici.pins import GPIO from system.hardware.tici.pins import GPIO
@ -61,17 +61,27 @@ MM_MODEM_ACCESS_TECHNOLOGY_LTE = 1 << 14
def sudo_write(val, path): def sudo_write(val, path):
os.system(f"sudo su -c 'echo {val} > {path}'") try:
with open(path, 'w') as f:
f.write(str(val))
except PermissionError:
os.system(f"sudo chmod a+w {path}")
try:
with open(path, 'w') as f:
f.write(str(val))
except PermissionError:
# fallback for debugfs files
os.system(f"sudo su -c 'echo {val} > {path}'")
def affine_irq(val, action): def affine_irq(val, action):
irq = get_irq_for_action(action) irqs = get_irqs_for_action(action)
if len(irq) == 0: if len(irqs) == 0:
print(f"No IRQs found for '{action}'") print(f"No IRQs found for '{action}'")
return return
for i in irq:
sudo_write(str(val), f"/proc/irq/{i}/smp_affinity_list")
for i in irqs:
sudo_write(str(val), f"/proc/irq/{i}/smp_affinity_list")
class Tici(HardwareBase): class Tici(HardwareBase):
@cached_property @cached_property
@ -438,17 +448,17 @@ class Tici(HardwareBase):
# *** IRQ config *** # *** IRQ config ***
# GPU
affine_irq(5, "kgsl-3d0")
# boardd core # boardd core
affine_irq(4, "spi_geni") # SPI affine_irq(4, "spi_geni") # SPI
affine_irq(4, "xhci-hcd:usb3") # aux panda USB (or potentially anything else on USB) affine_irq(4, "xhci-hcd:usb3") # aux panda USB (or potentially anything else on USB)
if "tici" in self.get_device_type(): if "tici" in self.get_device_type():
affine_irq(4, "xhci-hcd:usb1") # internal panda USB affine_irq(4, "xhci-hcd:usb1") # internal panda USB (also modem)
# GPU
affine_irq(5, "kgsl-3d0")
# camerad core # camerad core
camera_irqs = ("cci", "cpas_camnoc", "cpas-cdm", "csid", "ife", "csid", "csid-lite", "ife-lite") camera_irqs = ("cci", "cpas_camnoc", "cpas-cdm", "csid", "ife", "csid-lite", "ife-lite")
for n in camera_irqs: for n in camera_irqs:
affine_irq(5, n) affine_irq(5, n)
@ -472,14 +482,14 @@ class Tici(HardwareBase):
# *** IRQ config *** # *** IRQ config ***
# move these off the default core
affine_irq(1, "msm_drm")
affine_irq(1, "msm_vidc")
affine_irq(1, "i2c_geni")
# mask off big cluster from default affinity # mask off big cluster from default affinity
sudo_write("f", "/proc/irq/default_smp_affinity") sudo_write("f", "/proc/irq/default_smp_affinity")
# move these off the default core
affine_irq(1, "msm_drm") # display
affine_irq(1, "msm_vidc") # encoders
affine_irq(1, "i2c_geni") # sensors
# *** GPU config *** # *** GPU config ***
# https://github.com/commaai/agnos-kernel-sdm845/blob/master/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi#L216 # https://github.com/commaai/agnos-kernel-sdm845/blob/master/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi#L216
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/min_pwrlevel") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/min_pwrlevel")

@ -0,0 +1,31 @@
#!/usr/bin/env python3
import time
import unittest
import numpy as np
from system.hardware import TICI
from system.hardware.tici.hardware import Tici
HARDWARE = Tici()
class TestHardware(unittest.TestCase):
@classmethod
def setUpClass(cls):
if not TICI:
raise unittest.SkipTest
def test_power_save_time(self):
ts = []
for _ in range(5):
for on in (True, False):
st = time.monotonic()
HARDWARE.set_power_save(on)
ts.append(time.monotonic() - st)
assert 0.1 < np.mean(ts) < 0.2
assert max(ts) < 0.3
if __name__ == "__main__":
unittest.main()

@ -2,15 +2,48 @@
import os import os
import shutil import shutil
import threading import threading
from typing import List
from system.swaglog import cloudlog from system.swaglog import cloudlog
from system.loggerd.config import ROOT, get_available_bytes, get_available_percent from system.loggerd.config import ROOT, get_available_bytes, get_available_percent
from system.loggerd.uploader import listdir_by_creation from system.loggerd.uploader import listdir_by_creation
from system.loggerd.xattr_cache import getxattr
MIN_BYTES = 5 * 1024 * 1024 * 1024 MIN_BYTES = 5 * 1024 * 1024 * 1024
MIN_PERCENT = 10 MIN_PERCENT = 10
DELETE_LAST = ['boot', 'crash'] DELETE_LAST = ['boot', 'crash']
PRESERVE_ATTR_NAME = 'user.preserve'
PRESERVE_ATTR_VALUE = b'1'
PRESERVE_COUNT = 5
def has_preserve_xattr(d: str) -> bool:
return getxattr(os.path.join(ROOT, d), PRESERVE_ATTR_NAME) == PRESERVE_ATTR_VALUE
def get_preserved_segments(dirs_by_creation: List[str]) -> List[str]:
preserved = []
for n, d in enumerate(filter(has_preserve_xattr, reversed(dirs_by_creation))):
if n == PRESERVE_COUNT:
break
date_str, _, seg_str = d.rpartition("--")
# ignore non-segment directories
if not date_str:
continue
try:
seg_num = int(seg_str)
except ValueError:
continue
# preserve segment and its prior
preserved.append(d)
preserved.append(f"{date_str}--{seg_num - 1}")
return preserved
def deleter_thread(exit_event): def deleter_thread(exit_event):
while not exit_event.is_set(): while not exit_event.is_set():
@ -18,9 +51,13 @@ def deleter_thread(exit_event):
out_of_percent = get_available_percent(default=MIN_PERCENT + 1) < MIN_PERCENT out_of_percent = get_available_percent(default=MIN_PERCENT + 1) < MIN_PERCENT
if out_of_percent or out_of_bytes: if out_of_percent or out_of_bytes:
dirs = listdir_by_creation(ROOT)
# skip deleting most recent N preserved segments (and their prior segment)
preserved_dirs = get_preserved_segments(dirs)
# remove the earliest directory we can # remove the earliest directory we can
dirs = sorted(listdir_by_creation(ROOT), key=lambda x: x in DELETE_LAST) for delete_dir in sorted(dirs, key=lambda d: (d in DELETE_LAST, d in preserved_dirs)):
for delete_dir in dirs:
delete_path = os.path.join(ROOT, delete_dir) delete_path = os.path.join(ROOT, delete_dir)
if any(name.endswith(".lock") for name in os.listdir(delete_path)): if any(name.endswith(".lock") for name in os.listdir(delete_path)):

@ -1,3 +1,5 @@
#include <sys/xattr.h>
#include <unordered_map> #include <unordered_map>
#include "system/loggerd/encoder/encoder.h" #include "system/loggerd/encoder/encoder.h"
@ -170,6 +172,19 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct
return bytes_count; return bytes_count;
} }
void handle_user_flag(LoggerdState *s) {
LOGW("preserving %s", s->segment_path);
#ifdef __APPLE__
int ret = setxattr(s->segment_path, PRESERVE_ATTR_NAME, &PRESERVE_ATTR_VALUE, 1, 0, 0);
#else
int ret = setxattr(s->segment_path, PRESERVE_ATTR_NAME, &PRESERVE_ATTR_VALUE, 1, 0);
#endif
if (ret) {
LOGE("setxattr %s failed for %s: %s", PRESERVE_ATTR_NAME, s->segment_path, strerror(errno));
}
}
void loggerd_thread() { void loggerd_thread() {
// setup messaging // setup messaging
typedef struct QlogState { typedef struct QlogState {
@ -228,6 +243,10 @@ void loggerd_thread() {
while (!do_exit && (msg = sock->receive(true))) { while (!do_exit && (msg = sock->receive(true))) {
const bool in_qlog = qs.freq != -1 && (qs.counter++ % qs.freq == 0); const bool in_qlog = qs.freq != -1 && (qs.counter++ % qs.freq == 0);
if (qs.name == "userFlag") {
handle_user_flag(&s);
}
if (qs.encoder) { if (qs.encoder) {
s.last_camera_seen_tms = millis_since_boot(); s.last_camera_seen_tms = millis_since_boot();
bytes_count += handle_encoder_msg(&s, msg, qs.name, remote_encoders[sock], encoder_infos_dict[qs.name]); bytes_count += handle_encoder_msg(&s, msg, qs.name, remote_encoders[sock], encoder_infos_dict[qs.name]);

@ -24,6 +24,9 @@ const int MAIN_BITRATE = 10000000;
const bool LOGGERD_TEST = getenv("LOGGERD_TEST"); const bool LOGGERD_TEST = getenv("LOGGERD_TEST");
const int SEGMENT_LENGTH = LOGGERD_TEST ? atoi(getenv("LOGGERD_SEGMENT_LENGTH")) : 60; const int SEGMENT_LENGTH = LOGGERD_TEST ? atoi(getenv("LOGGERD_SEGMENT_LENGTH")) : 60;
constexpr char PRESERVE_ATTR_NAME[] = "user.preserve";
constexpr char PRESERVE_ATTR_VALUE = '1';
class EncoderInfo { class EncoderInfo {
public: public:
const char *publish_name; const char *publish_name;

@ -7,10 +7,12 @@ import unittest
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
import system.loggerd.deleter as deleter
import system.loggerd.uploader as uploader import system.loggerd.uploader as uploader
from system.loggerd.xattr_cache import setxattr
def create_random_file(file_path: Path, size_mb: float, lock: bool = False, xattr: Optional[bytes] = None) -> None: def create_random_file(file_path: Path, size_mb: float, lock: bool = False, upload_xattr: Optional[bytes] = None) -> None:
file_path.parent.mkdir(parents=True, exist_ok=True) file_path.parent.mkdir(parents=True, exist_ok=True)
if lock: if lock:
@ -25,8 +27,8 @@ def create_random_file(file_path: Path, size_mb: float, lock: bool = False, xatt
for _ in range(chunks): for _ in range(chunks):
f.write(data) f.write(data)
if xattr is not None: if upload_xattr is not None:
uploader.setxattr(str(file_path), uploader.UPLOAD_ATTR_NAME, xattr) setxattr(str(file_path), uploader.UPLOAD_ATTR_NAME, upload_xattr)
class MockResponse(): class MockResponse():
def __init__(self, text, status_code): def __init__(self, text, status_code):
@ -105,8 +107,11 @@ class UploaderTestCase(unittest.TestCase):
raise raise
def make_file_with_data(self, f_dir: str, fn: str, size_mb: float = .1, lock: bool = False, def make_file_with_data(self, f_dir: str, fn: str, size_mb: float = .1, lock: bool = False,
xattr: Optional[bytes] = None) -> Path: upload_xattr: Optional[bytes] = None, preserve_xattr: Optional[bytes] = None) -> Path:
file_path = self.root / f_dir / fn file_path = self.root / f_dir / fn
create_random_file(file_path, size_mb, lock, xattr) create_random_file(file_path, size_mb, lock, upload_xattr)
if preserve_xattr is not None:
setxattr(str(file_path.parent), deleter.PRESERVE_ATTR_NAME, preserve_xattr)
return file_path return file_path

@ -3,9 +3,11 @@ import time
import threading import threading
import unittest import unittest
from collections import namedtuple from collections import namedtuple
from pathlib import Path
from typing import Sequence
from common.timeout import Timeout, TimeoutException
import system.loggerd.deleter as deleter import system.loggerd.deleter as deleter
from common.timeout import Timeout, TimeoutException
from system.loggerd.tests.loggerd_tests_common import UploaderTestCase from system.loggerd.tests.loggerd_tests_common import UploaderTestCase
Stats = namedtuple("Stats", ['f_bavail', 'f_blocks', 'f_frsize']) Stats = namedtuple("Stats", ['f_bavail', 'f_blocks', 'f_frsize'])
@ -37,30 +39,59 @@ class TestDeleter(UploaderTestCase):
self.start_thread() self.start_thread()
with Timeout(5, "Timeout waiting for file to be deleted"): try:
while f_path.exists(): with Timeout(2, "Timeout waiting for file to be deleted"):
time.sleep(0.01) while f_path.exists():
self.join_thread() time.sleep(0.01)
finally:
self.assertFalse(f_path.exists(), "File not deleted") self.join_thread()
def test_delete_files_in_create_order(self): def assertDeleteOrder(self, f_paths: Sequence[Path], timeout: int = 5) -> None:
f_path_1 = self.make_file_with_data(self.seg_dir, self.f_type) deleted_order = []
time.sleep(1)
self.seg_num += 1
self.seg_dir = self.seg_format.format(self.seg_num)
f_path_2 = self.make_file_with_data(self.seg_dir, self.f_type)
self.start_thread() self.start_thread()
try:
with Timeout(timeout, "Timeout waiting for files to be deleted"):
while True:
for f in f_paths:
if not f.exists() and f not in deleted_order:
deleted_order.append(f)
if len(deleted_order) == len(f_paths):
break
time.sleep(0.01)
except TimeoutException:
print("Not deleted:", [f for f in f_paths if f not in deleted_order])
raise
finally:
self.join_thread()
with Timeout(5, "Timeout waiting for file to be deleted"): self.assertEqual(deleted_order, f_paths, "Files not deleted in expected order")
while f_path_1.exists() and f_path_2.exists():
time.sleep(0.01) def test_delete_order(self):
self.assertDeleteOrder([
self.join_thread() self.make_file_with_data(self.seg_format.format(0), self.f_type),
self.make_file_with_data(self.seg_format.format(1), self.f_type),
self.assertFalse(f_path_1.exists(), "Older file not deleted") self.make_file_with_data(self.seg_format2.format(0), self.f_type),
self.assertTrue(f_path_2.exists(), "Newer file deleted before older file") ])
def test_delete_many_preserved(self):
self.assertDeleteOrder([
self.make_file_with_data(self.seg_format.format(0), self.f_type),
self.make_file_with_data(self.seg_format.format(1), self.f_type, preserve_xattr=deleter.PRESERVE_ATTR_VALUE),
self.make_file_with_data(self.seg_format.format(2), self.f_type),
] + [
self.make_file_with_data(self.seg_format2.format(i), self.f_type, preserve_xattr=deleter.PRESERVE_ATTR_VALUE)
for i in range(5)
])
def test_delete_last(self):
self.assertDeleteOrder([
self.make_file_with_data(self.seg_format.format(1), self.f_type),
self.make_file_with_data(self.seg_format2.format(0), self.f_type),
self.make_file_with_data(self.seg_format.format(0), self.f_type, preserve_xattr=deleter.PRESERVE_ATTR_VALUE),
self.make_file_with_data("boot", self.seg_format[:-4]),
self.make_file_with_data("crash", self.seg_format2[:-4]),
])
def test_no_delete_when_available_space(self): def test_no_delete_when_available_space(self):
f_path = self.make_file_with_data(self.seg_dir, self.f_type) f_path = self.make_file_with_data(self.seg_dir, self.f_type)
@ -70,15 +101,10 @@ class TestDeleter(UploaderTestCase):
self.fake_stats = Stats(f_bavail=available, f_blocks=10, f_frsize=block_size) self.fake_stats = Stats(f_bavail=available, f_blocks=10, f_frsize=block_size)
self.start_thread() self.start_thread()
start_time = time.monotonic()
try: while f_path.exists() and time.monotonic() - start_time < 2:
with Timeout(2, "Timeout waiting for file to be deleted"): time.sleep(0.01)
while f_path.exists(): self.join_thread()
time.sleep(0.01)
except TimeoutException:
pass
finally:
self.join_thread()
self.assertTrue(f_path.exists(), "File deleted with available space") self.assertTrue(f_path.exists(), "File deleted with available space")
@ -86,15 +112,10 @@ class TestDeleter(UploaderTestCase):
f_path = self.make_file_with_data(self.seg_dir, self.f_type, lock=True) f_path = self.make_file_with_data(self.seg_dir, self.f_type, lock=True)
self.start_thread() self.start_thread()
start_time = time.monotonic()
try: while f_path.exists() and time.monotonic() - start_time < 2:
with Timeout(2, "Timeout waiting for file to be deleted"): time.sleep(0.01)
while f_path.exists(): self.join_thread()
time.sleep(0.01)
except TimeoutException:
pass
finally:
self.join_thread()
self.assertTrue(f_path.exists(), "File deleted when locked") self.assertTrue(f_path.exists(), "File deleted when locked")

@ -8,6 +8,7 @@ import time
import unittest import unittest
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
from typing import Dict, List
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal import log from cereal import log
@ -16,6 +17,8 @@ from common.basedir import BASEDIR
from common.params import Params from common.params import Params
from common.timeout import Timeout from common.timeout import Timeout
from system.loggerd.config import ROOT from system.loggerd.config import ROOT
from system.loggerd.xattr_cache import getxattr
from system.loggerd.deleter import PRESERVE_ATTR_NAME, PRESERVE_ATTR_VALUE
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
from system.version import get_version from system.version import get_version
from tools.lib.logreader import LogReader from tools.lib.logreader import LogReader
@ -71,6 +74,30 @@ class TestLoggerd(unittest.TestCase):
end_type = SentinelType.endOfRoute if route else SentinelType.endOfSegment end_type = SentinelType.endOfRoute if route else SentinelType.endOfSegment
self.assertTrue(msgs[-1].sentinel.type == end_type) self.assertTrue(msgs[-1].sentinel.type == end_type)
def _publish_random_messages(self, services: List[str]) -> Dict[str, list]:
pm = messaging.PubMaster(services)
managed_processes["loggerd"].start()
for s in services:
self.assertTrue(pm.wait_for_readers_to_update(s, timeout=5))
sent_msgs = defaultdict(list)
for _ in range(random.randint(2, 10) * 100):
for s in services:
try:
m = messaging.new_message(s)
except Exception:
m = messaging.new_message(s, random.randint(2, 10))
pm.send(s, m)
sent_msgs[s].append(m)
time.sleep(0.01)
for s in services:
self.assertTrue(pm.wait_for_readers_to_update(s, timeout=5))
managed_processes["loggerd"].stop()
return sent_msgs
def test_init_data_values(self): def test_init_data_values(self):
os.environ["CLEAN"] = random.choice(["0", "1"]) os.environ["CLEAN"] = random.choice(["0", "1"])
@ -193,29 +220,7 @@ class TestLoggerd(unittest.TestCase):
services = random.sample(qlog_services, random.randint(2, min(10, len(qlog_services)))) + \ services = random.sample(qlog_services, random.randint(2, min(10, len(qlog_services)))) + \
random.sample(no_qlog_services, random.randint(2, min(10, len(no_qlog_services)))) random.sample(no_qlog_services, random.randint(2, min(10, len(no_qlog_services))))
sent_msgs = self._publish_random_messages(services)
pm = messaging.PubMaster(services)
# sleep enough for the first poll to time out
# TODO: fix loggerd bug dropping the msgs from the first poll
managed_processes["loggerd"].start()
for s in services:
while not pm.all_readers_updated(s):
time.sleep(0.1)
sent_msgs = defaultdict(list)
for _ in range(random.randint(2, 10) * 100):
for s in services:
try:
m = messaging.new_message(s)
except Exception:
m = messaging.new_message(s, random.randint(2, 10))
pm.send(s, m)
sent_msgs[s].append(m)
time.sleep(0.01)
time.sleep(1)
managed_processes["loggerd"].stop()
qlog_path = os.path.join(self._get_latest_log_dir(), "qlog") qlog_path = os.path.join(self._get_latest_log_dir(), "qlog")
lr = list(LogReader(qlog_path)) lr = list(LogReader(qlog_path))
@ -241,27 +246,7 @@ class TestLoggerd(unittest.TestCase):
def test_rlog(self): def test_rlog(self):
services = random.sample(CEREAL_SERVICES, random.randint(5, 10)) services = random.sample(CEREAL_SERVICES, random.randint(5, 10))
pm = messaging.PubMaster(services) sent_msgs = self._publish_random_messages(services)
# sleep enough for the first poll to time out
# TODO: fix loggerd bug dropping the msgs from the first poll
managed_processes["loggerd"].start()
for s in services:
while not pm.all_readers_updated(s):
time.sleep(0.1)
sent_msgs = defaultdict(list)
for _ in range(random.randint(2, 10) * 100):
for s in services:
try:
m = messaging.new_message(s)
except Exception:
m = messaging.new_message(s, random.randint(2, 10))
pm.send(s, m)
sent_msgs[s].append(m)
time.sleep(2)
managed_processes["loggerd"].stop()
lr = list(LogReader(os.path.join(self._get_latest_log_dir(), "rlog"))) lr = list(LogReader(os.path.join(self._get_latest_log_dir(), "rlog")))
@ -276,6 +261,20 @@ class TestLoggerd(unittest.TestCase):
sent.clear_write_flag() sent.clear_write_flag()
self.assertEqual(sent.to_bytes(), m.as_builder().to_bytes()) self.assertEqual(sent.to_bytes(), m.as_builder().to_bytes())
def test_preserving_flagged_segments(self):
services = set(random.sample(CEREAL_SERVICES, random.randint(5, 10))) | {"userFlag"}
self._publish_random_messages(services)
segment_dir = self._get_latest_log_dir()
self.assertEqual(getxattr(segment_dir, PRESERVE_ATTR_NAME), PRESERVE_ATTR_VALUE)
def test_not_preserving_unflagged_segments(self):
services = set(random.sample(CEREAL_SERVICES, random.randint(5, 10))) - {"userFlag"}
self._publish_random_messages(services)
segment_dir = self._get_latest_log_dir()
self.assertIsNone(getxattr(segment_dir, PRESERVE_ATTR_NAME))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -55,10 +55,10 @@ class TestUploader(UploaderTestCase):
def gen_files(self, lock=False, xattr: Optional[bytes] = None, boot=True) -> List[Path]: def gen_files(self, lock=False, xattr: Optional[bytes] = None, boot=True) -> List[Path]:
f_paths = [] f_paths = []
for t in ["qlog", "rlog", "dcamera.hevc", "fcamera.hevc"]: for t in ["qlog", "rlog", "dcamera.hevc", "fcamera.hevc"]:
f_paths.append(self.make_file_with_data(self.seg_dir, t, 1, lock=lock, xattr=xattr)) f_paths.append(self.make_file_with_data(self.seg_dir, t, 1, lock=lock, upload_xattr=xattr))
if boot: if boot:
f_paths.append(self.make_file_with_data("boot", f"{self.seg_dir}", 1, lock=lock, xattr=xattr)) f_paths.append(self.make_file_with_data("boot", f"{self.seg_dir}", 1, lock=lock, upload_xattr=xattr))
return f_paths return f_paths
def gen_order(self, seg1: List[int], seg2: List[int], boot=True) -> List[str]: def gen_order(self, seg1: List[int], seg2: List[int], boot=True) -> List[str]:

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

@ -88,12 +88,13 @@ measurementStatusGlonassFields = {
def try_setup_logs(diag, log_types): def try_setup_logs(diag, log_types):
for _ in range(3): for _ in range(10):
try: try:
setup_logs(diag, log_types) setup_logs(diag, log_types)
break break
except Exception: except Exception:
cloudlog.exception("setup logs failed, trying again") cloudlog.exception("setup logs failed, trying again")
time.sleep(1.0)
else: else:
raise Exception(f"setup logs failed, {log_types=}") raise Exception(f"setup logs failed, {log_types=}")

@ -44,7 +44,7 @@ int BMX055_Accel::shutdown() {
// enter deep suspend mode (lowest power mode) // enter deep suspend mode (lowest power mode)
int ret = set_register(BMX055_ACCEL_I2C_REG_PMU, BMX055_ACCEL_DEEP_SUSPEND); int ret = set_register(BMX055_ACCEL_I2C_REG_PMU, BMX055_ACCEL_DEEP_SUSPEND);
if (ret < 0) { if (ret < 0) {
LOGE("Could not move BMX055 ACCEL in deep suspend mode!") LOGE("Could not move BMX055 ACCEL in deep suspend mode!");
} }
return ret; return ret;

@ -54,7 +54,7 @@ int BMX055_Gyro::shutdown() {
// enter deep suspend mode (lowest power mode) // enter deep suspend mode (lowest power mode)
int ret = set_register(BMX055_GYRO_I2C_REG_LPM1, BMX055_GYRO_DEEP_SUSPEND); int ret = set_register(BMX055_GYRO_I2C_REG_LPM1, BMX055_GYRO_DEEP_SUSPEND);
if (ret < 0) { if (ret < 0) {
LOGE("Could not move BMX055 GYRO in deep suspend mode!") LOGE("Could not move BMX055 GYRO in deep suspend mode!");
} }
return ret; return ret;

@ -150,7 +150,7 @@ int BMX055_Magn::shutdown() {
// move to suspend mode // move to suspend mode
int ret = set_register(BMX055_MAGN_I2C_REG_PWR_0, 0); int ret = set_register(BMX055_MAGN_I2C_REG_PWR_0, 0);
if (ret < 0) { if (ret < 0) {
LOGE("Could not move BMX055 MAGN in suspend mode!") LOGE("Could not move BMX055 MAGN in suspend mode!");
} }
return ret; return ret;

@ -194,7 +194,7 @@ int LSM6DS3_Accel::shutdown() {
value &= ~(LSM6DS3_ACCEL_INT1_DRDY_XL); value &= ~(LSM6DS3_ACCEL_INT1_DRDY_XL);
ret = set_register(LSM6DS3_ACCEL_I2C_REG_INT1_CTRL, value); ret = set_register(LSM6DS3_ACCEL_I2C_REG_INT1_CTRL, value);
if (ret < 0) { if (ret < 0) {
LOGE("Could not disable lsm6ds3 acceleration interrupt!") LOGE("Could not disable lsm6ds3 acceleration interrupt!");
goto fail; goto fail;
} }
@ -208,7 +208,7 @@ int LSM6DS3_Accel::shutdown() {
value &= 0x0F; value &= 0x0F;
ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, value); ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, value);
if (ret < 0) { if (ret < 0) {
LOGE("Could not power-down lsm6ds3 accelerometer!") LOGE("Could not power-down lsm6ds3 accelerometer!");
goto fail; goto fail;
} }

@ -177,7 +177,7 @@ int LSM6DS3_Gyro::shutdown() {
value &= ~(LSM6DS3_GYRO_INT1_DRDY_G); value &= ~(LSM6DS3_GYRO_INT1_DRDY_G);
ret = set_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, value); ret = set_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, value);
if (ret < 0) { if (ret < 0) {
LOGE("Could not disable lsm6ds3 gyroscope interrupt!") LOGE("Could not disable lsm6ds3 gyroscope interrupt!");
goto fail; goto fail;
} }
@ -191,7 +191,7 @@ int LSM6DS3_Gyro::shutdown() {
value &= 0x0F; value &= 0x0F;
ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, value); ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, value);
if (ret < 0) { if (ret < 0) {
LOGE("Could not power-down lsm6ds3 gyroscope!") LOGE("Could not power-down lsm6ds3 gyroscope!");
goto fail; goto fail;
} }

@ -63,7 +63,7 @@ int MMC5603NJ_Magn::shutdown() {
return ret; return ret;
fail: fail:
LOGE("Could not disable mmc5603nj auto set reset") LOGE("Could not disable mmc5603nj auto set reset");
return ret; return ret;
} }

@ -7,7 +7,7 @@ from collections import namedtuple, defaultdict
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal import log from cereal import log
from common.gpio import get_irq_for_action from common.gpio import get_irqs_for_action
from system.hardware import TICI from system.hardware import TICI
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
@ -113,7 +113,7 @@ class TestSensord(unittest.TestCase):
cls.events = read_sensor_events(cls.sample_secs) cls.events = read_sensor_events(cls.sample_secs)
# determine sensord's irq # determine sensord's irq
cls.sensord_irq = get_irq_for_action("sensord")[0] cls.sensord_irq = get_irqs_for_action("sensord")[0]
finally: finally:
# teardown won't run if this doesn't succeed # teardown won't run if this doesn't succeed
managed_processes["sensord"].stop() managed_processes["sensord"].stop()

@ -390,7 +390,7 @@ kj::Array<capnp::word> UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_
eph.setP4(data->p4()); eph.setP4(data->p4());
eph.setSvURA(glonass_URA_lookup.at(data->f_t())); eph.setSvURA(glonass_URA_lookup.at(data->f_t()));
if (msg->sv_id() != data->n()) { if (msg->sv_id() != data->n()) {
LOGE("SV_ID != SLOT_NUMBER: %d %d", msg->sv_id(), data->n()) LOGE("SV_ID != SLOT_NUMBER: %d %d", msg->sv_id(), data->n());
} }
eph.setSvType(data->m()); eph.setSvType(data->m());
} }

@ -29,7 +29,7 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset
prev_moc_path = cabana_env['QT_MOCHPREFIX'] prev_moc_path = cabana_env['QT_MOCHPREFIX']
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc',
'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc',
'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
@ -38,6 +38,7 @@ cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs
if GetOption('test'): if GetOption('test'):
cabana_env.Program('tests/test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) cabana_env.Program('tests/test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs])
def generate_dbc_json(target, source, env): generate_dbc = cabana_env.Command('generate_dbc_json',
env.Execute('tools/cabana/dbc/generate_dbc_json.py --out tools/cabana/dbc/car_fingerprint_to_dbc.json') [],
cabana_env.Command('generate_dbc_json', [], generate_dbc_json) "python3 tools/cabana/dbc/generate_dbc_json.py --out tools/cabana/dbc/car_fingerprint_to_dbc.json")
cabana_env.Depends(generate_dbc, ["#common", "#selfdrive/boardd", "#opendbc", "#cereal"])

@ -28,7 +28,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
// message table // message table
view = new MessageView(this); view = new MessageView(this);
model = new MessageListModel(this); model = new MessageListModel(this);
header = new MessageViewHeader(this, model); header = new MessageViewHeader(this);
auto delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes); auto delegate = new MessageBytesDelegate(view, settings.multiple_lines_bytes);
view->setItemDelegate(delegate); view->setItemDelegate(delegate);
@ -446,7 +446,7 @@ void MessageView::headerContextMenuEvent(const QPoint &pos) {
menu->popup(header()->mapToGlobal(pos)); menu->popup(header()->mapToGlobal(pos));
} }
MessageViewHeader::MessageViewHeader(QWidget *parent, MessageListModel *model) : model(model), QHeaderView(Qt::Horizontal, parent) { MessageViewHeader::MessageViewHeader(QWidget *parent) : QHeaderView(Qt::Horizontal, parent) {
QObject::connect(this, &QHeaderView::sectionResized, this, &MessageViewHeader::updateHeaderPositions); QObject::connect(this, &QHeaderView::sectionResized, this, &MessageViewHeader::updateHeaderPositions);
QObject::connect(this, &QHeaderView::sectionMoved, this, &MessageViewHeader::updateHeaderPositions); QObject::connect(this, &QHeaderView::sectionMoved, this, &MessageViewHeader::updateHeaderPositions);
} }
@ -485,7 +485,7 @@ void MessageViewHeader::updateHeaderPositions() {
void MessageViewHeader::updateGeometries() { void MessageViewHeader::updateGeometries() {
for (int i = 0; i < count(); i++) { for (int i = 0; i < count(); i++) {
if (!editors[i]) { if (!editors[i]) {
QString column_name = model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); QString column_name = model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
editors[i] = new QLineEdit(this); editors[i] = new QLineEdit(this);
editors[i]->setClearButtonEnabled(true); editors[i]->setClearButtonEnabled(true);
editors[i]->setPlaceholderText(tr("Filter %1").arg(column_name)); editors[i]->setPlaceholderText(tr("Filter %1").arg(column_name));

@ -69,7 +69,7 @@ class MessageViewHeader : public QHeaderView {
Q_OBJECT Q_OBJECT
public: public:
MessageViewHeader(QWidget *parent, MessageListModel *model); MessageViewHeader(QWidget *parent);
void updateHeaderPositions(); void updateHeaderPositions();
void updateGeometries() override; void updateGeometries() override;
@ -85,8 +85,6 @@ private:
void updateFilters(); void updateFilters();
QMap<int, QLineEdit *> editors; QMap<int, QLineEdit *> editors;
QMap<int, QSet<QString>> values;
MessageListModel *model;
}; };
class MessagesWidget : public QWidget { class MessagesWidget : public QWidget {

@ -275,7 +275,7 @@ QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QMo
int width = option.widget->size().width() / 2; int width = option.widget->size().width() / 2;
if (index.column() == 0) { if (index.column() == 0) {
int spacing = option.widget->style()->pixelMetric(QStyle::PM_TreeViewIndentation) + color_label_width + 8; int spacing = option.widget->style()->pixelMetric(QStyle::PM_TreeViewIndentation) + color_label_width + 8;
auto text = index.data(Qt::DisplayRole).toString();; auto text = index.data(Qt::DisplayRole).toString();
auto item = (SignalModel::Item *)index.internalPointer(); auto item = (SignalModel::Item *)index.internalPointer();
if (item->type == SignalModel::Item::Sig && item->sig->type != cabana::Signal::Type::Normal) { if (item->type == SignalModel::Item::Sig && item->sig->type != cabana::Signal::Type::Normal) {
text += item->sig->type == cabana::Signal::Type::Multiplexor ? QString(" M ") : QString(" m%1 ").arg(item->sig->multiplex_value); text += item->sig->type == cabana::Signal::Type::Multiplexor ? QString(" M ") : QString(" m%1 ").arg(item->sig->multiplex_value);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save