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

pull/29034/head
Shane Smiskol 2 years ago
commit a28ccf851e
  1. 2
      Jenkinsfile
  2. 8
      RELEASES.md
  3. 2
      body
  4. 4
      common/gpio.py
  5. 2
      common/params.cc
  6. 2
      docs/CARS.md
  7. 5
      docs/SAFETY.md
  8. 2
      panda
  9. 1739
      poetry.lock
  10. 259
      pyproject.toml
  11. 27
      selfdrive/athena/athenad.py
  12. 18
      selfdrive/athena/tests/test_athenad_ping.py
  13. 11
      selfdrive/boardd/boardd.cc
  14. 11
      selfdrive/boardd/spi.cc
  15. 26
      selfdrive/car/body/carcontroller.py
  16. 8
      selfdrive/car/disable_ecu.py
  17. 7
      selfdrive/car/toyota/values.py
  18. 2
      selfdrive/car/volkswagen/values.py
  19. 2
      selfdrive/controls/controlsd.py
  20. 4
      selfdrive/manager/test/test_manager.py
  21. 3
      selfdrive/modeld/dmonitoringmodeld.cc
  22. 8
      selfdrive/modeld/navmodeld.cc
  23. 2
      selfdrive/test/process_replay/model_replay.py
  24. 12
      selfdrive/thermald/thermald.py
  25. 31
      selfdrive/ui/qt/maps/map.cc
  26. 2
      selfdrive/ui/qt/maps/map.h
  27. 8
      selfdrive/ui/qt/maps/map_panel.cc
  28. 3
      selfdrive/ui/qt/maps/map_panel.h
  29. 8
      selfdrive/ui/qt/offroad/settings.cc
  30. 42
      selfdrive/ui/qt/onroad.cc
  31. 20
      selfdrive/ui/qt/onroad.h
  32. 36
      system/loggerd/loggerd.cc
  33. 51
      system/sensord/rawgps/rawgpsd.py
  34. 53
      system/sensord/rawgps/test_rawgps.py

2
Jenkinsfile vendored

@ -168,7 +168,7 @@ pipeline {
["test exposure", "python system/camerad/test/test_exposure.py"], ["test exposure", "python system/camerad/test/test_exposure.py"],
["test amp", "pytest 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 hw", "pytest system/hardware/tici/tests/test_hardware.py"],
["test rawgpsd", "python system/sensord/rawgps/test_rawgps.py"], ["test rawgpsd", "pytest system/sensord/rawgps/test_rawgps.py"],
]) ])
} }
} }

@ -1,14 +1,14 @@
Version 0.9.4 (2023-XX-XX) Version 0.9.4 (2023-XX-XX)
======================== ========================
* Navigate on openpilot * Navigate on openpilot
* When navigation has a destination openpilot will input the map information into the model, generally improving behavior * When navigation has a destination, openpilot will input the map information into the model, generally improving behavior
* When navigating on openpilot, openpilot will keep left or right appropriately at forks/exits and take turns * When navigating on openpilot, openpilot will keep left or right appropriately at forks/exits and take turns
* When navigating on openpilot, lane change behavior is unchanged and still activated by the driver * When navigating on openpilot, lane change behavior is unchanged and still activated by the driver
* When navigate on openpilot is active, the path on the map is green
* UI updates * UI updates
* Navigation settings moved to home screen and map * Navigation settings moved to home screen and map
* 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 means info, orange means warning, and red means critical alert
* Bookmarked segments are preserved on the device's storage * 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!

@ -1 +1 @@
Subproject commit 5f5b8a9dff671cbf9369eb10e18b41ca619566ba Subproject commit 05021c559e78fd7c539abceeedcc47f981b05455

@ -1,4 +1,4 @@
from functools import cache from functools import lru_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,7 +25,7 @@ def gpio_read(pin: int) -> Optional[bool]:
return val return val
@cache @lru_cache(maxsize=None)
def get_irq_action(irq: int) -> List[str]: def get_irq_action(irq: int) -> List[str]:
try: try:
with open(f"/sys/kernel/irq/{irq}/actions") as f: with open(f"/sys/kernel/irq/{irq}/actions") as f:

@ -107,6 +107,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"DisablePowerDown", PERSISTENT}, {"DisablePowerDown", PERSISTENT},
{"DisableUpdates", PERSISTENT}, {"DisableUpdates", PERSISTENT},
{"DisengageOnAccelerator", PERSISTENT}, {"DisengageOnAccelerator", PERSISTENT},
{"DmModelInitialized", CLEAR_ON_ONROAD_TRANSITION},
{"DongleId", PERSISTENT}, {"DongleId", PERSISTENT},
{"DoReboot", CLEAR_ON_MANAGER_START}, {"DoReboot", CLEAR_ON_MANAGER_START},
{"DoShutdown", CLEAR_ON_MANAGER_START}, {"DoShutdown", CLEAR_ON_MANAGER_START},
@ -204,6 +205,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"UpdaterTargetBranch", CLEAR_ON_MANAGER_START}, {"UpdaterTargetBranch", CLEAR_ON_MANAGER_START},
{"Version", PERSISTENT}, {"Version", PERSISTENT},
{"VisionRadarToggle", PERSISTENT}, {"VisionRadarToggle", PERSISTENT},
{"WheeledBody", PERSISTENT},
}; };
} // namespace } // namespace

@ -153,7 +153,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Lexus|RX 2020-22|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=Lexus&model=RX 2020-22">Buy Here</a></sub></details>|| |Lexus|RX 2020-22|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=Lexus&model=RX 2020-22">Buy Here</a></sub></details>||
|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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=Lexus&model=RX Hybrid 2016">Buy Here</a></sub></details>|| |Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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=Lexus&model=RX Hybrid 2016">Buy Here</a></sub></details>||
|Lexus|RX Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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=Lexus&model=RX Hybrid 2017-19">Buy Here</a></sub></details>|| |Lexus|RX Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.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=Lexus&model=RX Hybrid 2017-19">Buy Here</a></sub></details>||
|Lexus|RX Hybrid 2020-21|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=Lexus&model=RX Hybrid 2020-21">Buy Here</a></sub></details>|| |Lexus|RX Hybrid 2020-22|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=Lexus&model=RX Hybrid 2020-22">Buy Here</a></sub></details>||
|Lexus|UX Hybrid 2019-23|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=Lexus&model=UX Hybrid 2019-23">Buy Here</a></sub></details>|| |Lexus|UX Hybrid 2019-23|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=Lexus&model=UX Hybrid 2019-23">Buy Here</a></sub></details>||
|Lincoln|Aviator 2020-21|Co-Pilot360 Plus|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<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=Lincoln&model=Aviator 2020-21">Buy Here</a></sub></details>|| |Lincoln|Aviator 2020-21|Co-Pilot360 Plus|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<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=Lincoln&model=Aviator 2020-21">Buy Here</a></sub></details>||
|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|31 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 angled mount (8 degrees)<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=MAN&model=eTGE 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|31 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 angled mount (8 degrees)<br>- 1 comma three<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-three.html?make=MAN&model=eTGE 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/4100gLeabmo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|

@ -25,9 +25,12 @@ ensuring two main safety requirements.
by stepping on the brake pedal or by pressing the cancel button. by stepping on the brake pedal or by pressing the cancel button.
2. The vehicle must not alter its trajectory too quickly for the driver to safely 2. The vehicle must not alter its trajectory too quickly for the driver to safely
react. This means that while the system is engaged, the actuators are constrained react. This means that while the system is engaged, the actuators are constrained
to operate within reasonable limits. to operate within reasonable limits[^1].
For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [panda/board/safety/](https://github.com/commaai/panda/tree/master/board/safety). For additional safety implementation details, refer to [panda safety model](https://github.com/commaai/panda#safety-model). For vehicle specific implementation of the safety concept, refer to [panda/board/safety/](https://github.com/commaai/panda/tree/master/board/safety).
**Extra note**: comma.ai strongly discourages the use of openpilot forks with safety code either missing or **Extra note**: comma.ai strongly discourages the use of openpilot forks with safety code either missing or
not fully meeting the above requirements. not fully meeting the above requirements.
[^1]: For these actuator limits we observe ISO11270 and ISO15622. Lateral limits described there translate to 0.9 seconds of maximum actuation to achieve a 1m lateral deviation.

@ -1 +1 @@
Subproject commit dd78b2bf6c9d63ef59e81d0c400e85c8b477a8be Subproject commit ed8ff7e48ad41b5b7c0ce9df5bb2bb5024b6429d

1739
poetry.lock generated

File diff suppressed because it is too large Load Diff

@ -11,167 +11,168 @@ documentation = "https://docs.comma.ai"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~3.11" python = "~3.11"
atomicwrites = "^1.4.0" atomicwrites = "*"
casadi = "==3.6.3" casadi = "==3.6.3"
cffi = "^1.15.1" cffi = "*"
crcmod = "^1.7" crcmod = "*"
cryptography = "^37.0.4" cryptography = "*"
Cython = "^3.0.0" Cython = "*"
flake8 = "^4.0.1" flake8 = "*"
Flask = "^2.1.2" Flask = "*"
future-fstrings = "^1.2.0" # for acados future-fstrings = "*" # for acados
gunicorn = "^20.1.0" gunicorn = "*"
hatanaka = "==2.4" hatanaka = "==2.4"
hexdump = "^3.3" hexdump = "*"
Jinja2 = "^3.1.2" Jinja2 = "*"
json-rpc = "^1.13.0" json-rpc = "*"
libusb1 = "^3.0.0" libusb1 = "*"
numpy = "==1.23.0" # locked pending deprecation fixes in xx numpy = "==1.23.0" # locked pending deprecation fixes in xx
onnx = "^1.14.0" onnx = ">=1.14.0"
onnxruntime-gpu = { version = "^1.15.1", platform = "linux", markers = "platform_machine == 'x86_64'" } onnxruntime-gpu = { version = ">=1.15.1", platform = "linux", markers = "platform_machine == 'x86_64'" }
pillow = "^9.2.0" pillow = "*"
poetry = "==1.2.2" poetry = "==1.2.2"
psutil = "^5.9.1" protobuf = "==3.20.3"
pycapnp = "^1.3.0" psutil = "*"
pycryptodome = "^3.15.0" pycapnp = "*"
PyJWT = "^2.5.0" pycryptodome = "*"
pyopencl = "^2022.2.4" PyJWT = "*"
pyserial = "^3.5" pyopencl = "*"
python-dateutil = "^2.8.2" pyserial = "*"
PyYAML = "^6.0" python-dateutil = "*"
pyzmq = "^23.2.0" PyYAML = "*"
requests = "^2.28.1" pyzmq = "*"
scons = "^4.5.2" requests = "*"
sentry-sdk = "^1.6.0" scons = "*"
setproctitle = "^1.2.3" sentry-sdk = "*"
smbus2 = "^0.4.2" setproctitle = "*"
sounddevice = "^0.4.5" smbus2 = "*"
spidev = { version = "^3.6", platform = "linux" } sounddevice = "*"
spidev2 = { version = "^0.9.0", platform = "linux" } spidev = { version = "*", platform = "linux" }
sympy = "^1.11.1" spidev2 = { version = "*", platform = "linux" }
timezonefinder = "^6.2.0" sympy = "*"
tqdm = "^4.64.0" timezonefinder = "*"
urllib3 = "^1.26.10" tqdm = "*"
utm = "^0.7.0" urllib3 = "*"
websocket_client = "^1.3.3" utm = "*"
polyline = "^1.4.0" websocket_client = "*"
polyline = "*"
sconscontrib = {git = "https://github.com/SCons/scons-contrib.git"} sconscontrib = {git = "https://github.com/SCons/scons-contrib.git"}
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
av = "^10.0.0" av = "*"
azure-storage-blob = "~2.1" azure-storage-blob = "~2.1"
breathe = "^4.34.0" breathe = "*"
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'" } 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 = "*"
coverage = "^6.4.1" coverage = "*"
dictdiffer = "^0.9.0" dictdiffer = "*"
fastcluster = "^1.2.6" fastcluster = "*"
ft4222 = "^1.4.1" ft4222 = "*"
hexdump = "^3.3" hexdump = "*"
hypothesis = "==6.46.7" hypothesis = "==6.46.7"
inputs = "^0.5" inputs = "*"
lru-dict = "^1.1.7" lru-dict = "*"
lxml = "^4.9.1" lxml = "*"
markdown-it-py = "^2.1.0" markdown-it-py = "*"
matplotlib = "^3.5.2" matplotlib = "*"
mpld3 = "^0.5.8" mpld3 = "*"
mypy = "^1.4.0" mypy = "*"
myst-parser = "^0.18.0" myst-parser = "*"
natsort = "^8.1.0" natsort = "*"
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-cp311/opencv_python_headless-4.5.5.64-cp311-cp311-manylinux_2_31_x86_64.whl", platform = "linux" }
pandas = "^1.4.3" pandas = "*"
parameterized = "^0.8.1" parameterized = "^0.8"
paramiko = "^2.11.0" paramiko = "*"
pprofile = "^2.1.0" pprofile = "*"
pre-commit = "^2.19.0" pre-commit = "*"
pycurl = "^7.45.1" pycurl = "*"
pygame = "^2.4.0" pygame = "*"
pylint = "^2.17.4" pylint = "*"
pyprof2calltree = "^1.4.5" pyprof2calltree = "*"
pytest = "^7.1.2" pytest = "*"
pytest-xdist = "^2.5.0" pytest-xdist = "*"
reverse_geocoder = "^1.5.1" reverse_geocoder = "*"
scipy = "==1.9.3" # pinned until xx refs changes can be checked scipy = "==1.9.3" # pinned until xx refs changes can be checked
sphinx = "^5.0.2" sphinx = "*"
sphinx-rtd-theme = "^1.0.0" sphinx-rtd-theme = "*"
sphinx-sitemap = "^2.2.0" sphinx-sitemap = "*"
tabulate = "^0.8.10" tabulate = "*"
tenacity = "^8.0.1" tenacity = "*"
types-atomicwrites = "^1.4.5" types-atomicwrites = "*"
types-certifi = "^2021.10.8" types-certifi = "*"
types-pycurl = "^7.45.1" types-pycurl = "*"
types-python-dateutil = "^2.8.19.13" types-python-dateutil = "*"
types-PyYAML = "^6.0" types-PyYAML = "*"
types-requests = "^2.28.11" types-requests = "*"
types-tabulate = "^0.8.10" types-tabulate = "*"
[tool.poetry.group.xx] [tool.poetry.group.xx]
optional = true optional = true
[tool.poetry.group.xx.dependencies] [tool.poetry.group.xx.dependencies]
aenum = "^3.1.11" aenum = "*"
aiohttp = "^3.8.1" aiohttp = "*"
albumentations = "^1.2.1" albumentations = "*"
azure-cli-core = "^2.38.0" azure-cli-core = "*"
azure-common = "^1.1.28" azure-common = "*"
azure-core = "^1.24.2" azure-core = "*"
azure-nspkg = "~3.0" azure-nspkg = "~3.0"
azure-storage-common = "~2.1" azure-storage-common = "~2.1"
azure-storage-nspkg = "~3.1" azure-storage-nspkg = "~3.1"
blosc = "==1.9.2" blosc = "==1.9.2"
cloudpickle = "^2.1.0" cloudpickle = "*"
configargparse = "^1.5.3" configargparse = "*"
cupy-cuda11x = "^11.6.0" cupy-cuda11x = "*"
datadog = "^0.44.0" datadog = "*"
dotmap = "^1.3.30" dotmap = "*"
einops = "^0.5.0" einops = "*"
elasticsearch = "^8.3.1" elasticsearch = "*"
Flask-Cors = "^3.0.10" Flask-Cors = "*"
Flask-SocketIO = "^5.2.0" Flask-SocketIO = "*"
GeoAlchemy2 = "^0.12.1" GeoAlchemy2 = "*"
imageio = "^2.19.5" imageio = "*"
influxdb-client = "^1.30.0" influxdb-client = "*"
ipykernel = "^6.15.1" ipykernel = "*"
ipython = "^8.4.0" ipython = "*"
joblib = "^1.1.0" joblib = "*"
json-logging-py = "^0.2" json-logging-py = "*"
jupyter = "^1.0.0" jupyter = "*"
jupyterlab = "^3.4.4" jupyterlab = "*"
jupyterlab-vim = "^0.15.1" jupyterlab-vim = "*"
Markdown = "^3.4.1" Markdown = "*"
msgpack-python = "^0.5.6" msgpack-python = "*"
networkx = "~2.8" networkx = "~2.8"
nvidia-ml-py3 = "^7.352.0" nvidia-ml-py3 = "*"
onnx2torch = "^1.5.4" onnx2torch = "*"
onnxoptimizer = "^0.3.1" onnxoptimizer = "*"
osmium = "^3.3.0" osmium = "*"
pillow-avif-plugin = "^1.2.2" pillow-avif-plugin = "*"
pipenv = "==2022.10.12" pipenv = "==2022.10.12"
plotly = "^5.9.0" plotly = "*"
pycuda = "^2022.1" pycuda = "*"
Pygments = "^2.12.0" Pygments = "*"
PyMySQL = "~0.9" PyMySQL = "~0.9"
pyproj = "^3.3.1" pyproj = "*"
python-logstash = "^0.4.8" python-logstash = "*"
redis = "^4.3.4" redis = "*"
s2sphere = "^0.2.5" s2sphere = "*"
scikit-image = "^0.19.3" scikit-image = "*"
scikit-learn = "^1.1.1" scikit-learn = "*"
segmentation-models-pytorch = "==0.3.3" segmentation-models-pytorch = "==0.3.3"
simplejson = "^3.17.6" simplejson = "*"
SQLAlchemy = "^1.4.39" SQLAlchemy = "*"
torch = { url = "https://download.pytorch.org/whl/cu118/torch-2.0.1%2Bcu118-cp311-cp311-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 = "*"
torchvision = { url = "https://download.pytorch.org/whl/cu118/torchvision-0.15.2%2Bcu118-cp311-cp311-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 = "*"
Werkzeug = "^2.1.2" Werkzeug = "*"
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 = "*"
osmnx = "==1.2.2" osmnx = "==1.2.2"
tritonclient = {version = "2.28.0", extras = ["http"]} tritonclient = {version = "2.28.0", extras = ["http"]}
transformers = "^4.29.2" transformers = "*"
timm = "==0.9.2" timm = "==0.9.2"
PyNvCodec = { git = "https://github.com/NVIDIA/VideoProcessingFramework.git", rev = "3347e55" } PyNvCodec = { git = "https://github.com/NVIDIA/VideoProcessingFramework.git", rev = "3347e55" }

@ -42,6 +42,10 @@ from selfdrive.statsd import STATS_DIR
from system.swaglog import SWAGLOG_DIR, cloudlog from system.swaglog import SWAGLOG_DIR, cloudlog
from system.version import get_commit, get_origin, get_short_branch, get_version from system.version import get_commit, get_origin, get_short_branch, get_version
# missing in pysocket
TCP_USER_TIMEOUT = 18
ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai') ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai')
HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4"))
LOCAL_PORT_WHITELIST = {8022} LOCAL_PORT_WHITELIST = {8022}
@ -141,6 +145,7 @@ def handle_long_poll(ws: WebSocket, exit_event: Optional[threading.Event]) -> No
end_event = threading.Event() end_event = threading.Event()
threads = [ threads = [
threading.Thread(target=ws_manage, args=(ws, end_event), name='ws_manage'),
threading.Thread(target=ws_recv, args=(ws, end_event), name='ws_recv'), threading.Thread(target=ws_recv, args=(ws, end_event), name='ws_recv'),
threading.Thread(target=ws_send, args=(ws, end_event), name='ws_send'), threading.Thread(target=ws_send, args=(ws, end_event), name='ws_send'),
threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'), threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'),
@ -154,8 +159,7 @@ def handle_long_poll(ws: WebSocket, exit_event: Optional[threading.Event]) -> No
for thread in threads: for thread in threads:
thread.start() thread.start()
try: try:
while not end_event.is_set(): while not end_event.wait(0.1):
time.sleep(0.1)
if exit_event is not None and exit_event.is_set(): if exit_event is not None and exit_event.is_set():
end_event.set() end_event.set()
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
@ -756,6 +760,25 @@ def ws_send(ws: WebSocket, end_event: threading.Event) -> None:
end_event.set() end_event.set()
def ws_manage(ws: WebSocket, end_event: threading.Event) -> None:
params = Params()
onroad_prev = None
sock = ws.sock
while True:
onroad = params.get_bool("IsOnroad")
if onroad != onroad_prev:
onroad_prev = onroad
sock.setsockopt(socket.IPPROTO_TCP, TCP_USER_TIMEOUT, 16000 if onroad else 0)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 7 if onroad else 30)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2 if onroad else 3)
if end_event.wait(5):
break
def backoff(retries: int) -> int: def backoff(retries: int) -> int:
return random.randrange(0, min(128, int(2 ** retries))) return random.randrange(0, min(128, int(2 ** retries)))

@ -37,6 +37,9 @@ class TestAthenadPing(unittest.TestCase):
def _received_ping(self) -> bool: def _received_ping(self) -> bool:
return self._get_ping_time() is not None return self._get_ping_time() is not None
def _set_onroad(self, onroad: bool) -> None:
self.params.put_bool("IsOnroad", onroad)
@classmethod @classmethod
def setUpClass(cls) -> None: def setUpClass(cls) -> None:
cls.params = Params() cls.params = Params()
@ -63,8 +66,7 @@ class TestAthenadPing(unittest.TestCase):
self.exit_event.set() self.exit_event.set()
self.athenad.join() self.athenad.join()
@unittest.skipIf(not TICI, "only run on desk") def assertTimeout(self, reconnect_time: float) -> None:
def test_timeout(self) -> None:
self.athenad.start() self.athenad.start()
time.sleep(1) time.sleep(1)
@ -84,7 +86,7 @@ class TestAthenadPing(unittest.TestCase):
wifi_radio(False) wifi_radio(False)
print("waiting for reconnect attempt") print("waiting for reconnect attempt")
start_time = time.monotonic() start_time = time.monotonic()
with Timeout(180, "no reconnect attempt"): with Timeout(reconnect_time, "no reconnect attempt"):
while not athenad.create_connection.called: while not athenad.create_connection.called:
time.sleep(0.1) time.sleep(0.1)
print(f"reconnect attempt after {time.monotonic() - start_time:.2f}s") print(f"reconnect attempt after {time.monotonic() - start_time:.2f}s")
@ -97,6 +99,16 @@ class TestAthenadPing(unittest.TestCase):
time.sleep(0.1) time.sleep(0.1)
print("ping received") print("ping received")
@unittest.skipIf(not TICI, "only run on desk")
def test_offroad(self) -> None:
self._set_onroad(False)
self.assertTimeout(100) # expect approx 90s
@unittest.skipIf(not TICI, "only run on desk")
def test_onroad(self) -> None:
self._set_onroad(True)
self.assertTimeout(30) # expect 20-30s
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -301,6 +301,10 @@ std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *>
std::vector<std::array<can_health_t, PANDA_CAN_CNT>> pandaCanStates; std::vector<std::array<can_health_t, PANDA_CAN_CNT>> pandaCanStates;
pandaCanStates.reserve(pandas_cnt); pandaCanStates.reserve(pandas_cnt);
const bool red_panda_comma_three = (pandas.size() == 2) &&
(pandas[0]->hw_type == cereal::PandaState::PandaType::DOS) &&
(pandas[1]->hw_type == cereal::PandaState::PandaType::RED_PANDA);
for (const auto& panda : pandas){ for (const auto& panda : pandas){
auto health_opt = panda->get_state(); auto health_opt = panda->get_state();
if (!health_opt) { if (!health_opt) {
@ -323,6 +327,13 @@ std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *>
health.ignition_line_pkt = 1; health.ignition_line_pkt = 1;
} }
// on comma three setups with a red panda, the dos can
// get false positive ignitions due to the harness box
// without a harness connector, so ignore it
if (red_panda_comma_three && (panda->hw_type == cereal::PandaState::PandaType::DOS)) {
health.ignition_line_pkt = 0;
}
ignition_local |= ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)); ignition_local |= ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0));
pandaStates.push_back(health); pandaStates.push_back(health);

@ -236,9 +236,12 @@ int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint1
std::this_thread::yield(); std::this_thread::yield();
if (ret == SpiError::NACK) { if (ret == SpiError::NACK) {
// prevent busy wait while the panda is NACK'ing // prevent busy waiting while the panda is NACK'ing
// due to full TX buffers
nack_count += 1; nack_count += 1;
usleep(std::clamp(nack_count*10, 200, 2000)); if (nack_count > 3) {
usleep(std::clamp(nack_count*10, 200, 2000));
}
} }
} }
} while (ret < 0 && connected && !timed_out); } while (ret < 0 && connected && !timed_out);
@ -260,7 +263,6 @@ int PandaSpiHandle::wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout,
spi_ioc_transfer transfer = { spi_ioc_transfer transfer = {
.tx_buf = (uint64_t)tx_buf, .tx_buf = (uint64_t)tx_buf,
.rx_buf = (uint64_t)rx_buf, .rx_buf = (uint64_t)rx_buf,
.delay_usecs = 10,
.len = length .len = length
}; };
tx_buf[0] = tx; tx_buf[0] = tx;
@ -284,9 +286,6 @@ int PandaSpiHandle::wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout,
LOGD("SPI: timed out waiting for ACK"); LOGD("SPI: timed out waiting for ACK");
return SpiError::ACK_TIMEOUT; return SpiError::ACK_TIMEOUT;
} }
// backoff
transfer.delay_usecs = std::clamp(transfer.delay_usecs*2, 10, 250);
} }
return 0; return 0;

@ -1,5 +1,6 @@
import numpy as np import numpy as np
from common.params import Params
from common.realtime import DT_CTRL from common.realtime import DT_CTRL
from opendbc.can.packer import CANPacker from opendbc.can.packer import CANPacker
from selfdrive.car.body import bodycan from selfdrive.car.body import bodycan
@ -23,10 +24,14 @@ class CarController:
self.speed_pid = PIDController(0.115, k_i=0.23, rate=1/DT_CTRL) self.speed_pid = PIDController(0.115, k_i=0.23, rate=1/DT_CTRL)
self.balance_pid = PIDController(1300, k_i=0, k_d=280, rate=1/DT_CTRL) self.balance_pid = PIDController(1300, k_i=0, k_d=280, rate=1/DT_CTRL)
self.turn_pid = PIDController(110, k_i=11.5, rate=1/DT_CTRL) self.turn_pid = PIDController(110, k_i=11.5, rate=1/DT_CTRL)
self.wheeled_speed_pid = PIDController(110, k_i=11.5, rate=1/DT_CTRL)
self.torque_r_filtered = 0. self.torque_r_filtered = 0.
self.torque_l_filtered = 0. self.torque_l_filtered = 0.
params = Params()
self.wheeled_body = params.get("WheeledBody")
@staticmethod @staticmethod
def deadband_filter(torque, deadband): def deadband_filter(torque, deadband):
if torque > 0: if torque > 0:
@ -45,19 +50,22 @@ class CarController:
# Read these from the joystick # Read these from the joystick
# TODO: this isn't acceleration, okay? # TODO: this isn't acceleration, okay?
speed_desired = CC.actuators.accel / 5. speed_desired = CC.actuators.accel / 5.
speed_diff_desired = -CC.actuators.steer speed_diff_desired = -CC.actuators.steer / 2.
speed_measured = SPEED_FROM_RPM * (CS.out.wheelSpeeds.fl + CS.out.wheelSpeeds.fr) / 2. speed_measured = SPEED_FROM_RPM * (CS.out.wheelSpeeds.fl + CS.out.wheelSpeeds.fr) / 2.
speed_error = speed_desired - speed_measured speed_error = speed_desired - speed_measured
freeze_integrator = ((speed_error < 0 and self.speed_pid.error_integral <= -MAX_POS_INTEGRATOR) or if self.wheeled_body is None:
(speed_error > 0 and self.speed_pid.error_integral >= MAX_POS_INTEGRATOR)) freeze_integrator = ((speed_error < 0 and self.speed_pid.error_integral <= -MAX_POS_INTEGRATOR) or
angle_setpoint = self.speed_pid.update(speed_error, freeze_integrator=freeze_integrator) (speed_error > 0 and self.speed_pid.error_integral >= MAX_POS_INTEGRATOR))
angle_setpoint = self.speed_pid.update(speed_error, freeze_integrator=freeze_integrator)
# Clip angle error, this is enough to get up from stands
angle_error = np.clip((-CC.orientationNED[1]) - angle_setpoint, -MAX_ANGLE_ERROR, MAX_ANGLE_ERROR) # Clip angle error, this is enough to get up from stands
angle_error_rate = np.clip(-CC.angularVelocity[1], -1., 1.) angle_error = np.clip((-CC.orientationNED[1]) - angle_setpoint, -MAX_ANGLE_ERROR, MAX_ANGLE_ERROR)
torque = self.balance_pid.update(angle_error, error_rate=angle_error_rate) angle_error_rate = np.clip(-CC.angularVelocity[1], -1., 1.)
torque = self.balance_pid.update(angle_error, error_rate=angle_error_rate)
else:
torque = self.wheeled_speed_pid.update(speed_error, freeze_integrator=False)
speed_diff_measured = SPEED_FROM_RPM * (CS.out.wheelSpeeds.fl - CS.out.wheelSpeeds.fr) speed_diff_measured = SPEED_FROM_RPM * (CS.out.wheelSpeeds.fl - CS.out.wheelSpeeds.fr)
turn_error = speed_diff_measured - speed_diff_desired turn_error = speed_diff_measured - speed_diff_desired

@ -7,22 +7,22 @@ EXT_DIAG_RESPONSE = b'\x50\x03'
COM_CONT_RESPONSE = b'' COM_CONT_RESPONSE = b''
def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, com_cont_req=b'\x28\x83\x01', timeout=0.1, retry=10, debug=False): def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, sub_addr=None, com_cont_req=b'\x28\x83\x01', timeout=0.1, retry=10, debug=False):
"""Silence an ECU by disabling sending and receiving messages using UDS 0x28. """Silence an ECU by disabling sending and receiving messages using UDS 0x28.
The ECU will stay silent as long as openpilot keeps sending Tester Present. The ECU will stay silent as long as openpilot keeps sending Tester Present.
This is used to disable the radar in some cars. Openpilot will emulate the radar. This is used to disable the radar in some cars. Openpilot will emulate the radar.
WARNING: THIS DISABLES AEB!""" WARNING: THIS DISABLES AEB!"""
cloudlog.warning(f"ecu disable {hex(addr)} ...") cloudlog.warning(f"ecu disable {hex(addr), sub_addr} ...")
for i in range(retry): for i in range(retry):
try: try:
query = IsoTpParallelQuery(sendcan, logcan, bus, [addr], [EXT_DIAG_REQUEST], [EXT_DIAG_RESPONSE], debug=debug) query = IsoTpParallelQuery(sendcan, logcan, bus, [(addr, sub_addr)], [EXT_DIAG_REQUEST], [EXT_DIAG_RESPONSE], debug=debug)
for _, _ in query.get_data(timeout).items(): for _, _ in query.get_data(timeout).items():
cloudlog.warning("communication control disable tx/rx ...") cloudlog.warning("communication control disable tx/rx ...")
query = IsoTpParallelQuery(sendcan, logcan, bus, [addr], [com_cont_req], [COM_CONT_RESPONSE], debug=debug) query = IsoTpParallelQuery(sendcan, logcan, bus, [(addr, sub_addr)], [com_cont_req], [COM_CONT_RESPONSE], debug=debug)
query.get_data(0) query.get_data(0)
cloudlog.warning("ecu disabled") cloudlog.warning("ecu disabled")

@ -197,7 +197,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
ToyotaCarInfo("Lexus RX Hybrid 2017-19"), ToyotaCarInfo("Lexus RX Hybrid 2017-19"),
], ],
CAR.LEXUS_RX_TSS2: ToyotaCarInfo("Lexus RX 2020-22"), CAR.LEXUS_RX_TSS2: ToyotaCarInfo("Lexus RX 2020-22"),
CAR.LEXUS_RXH_TSS2: ToyotaCarInfo("Lexus RX Hybrid 2020-21"), CAR.LEXUS_RXH_TSS2: ToyotaCarInfo("Lexus RX Hybrid 2020-22"),
} }
# (addr, cars, bus, 1/freq*100, vl) # (addr, cars, bus, 1/freq*100, vl)
@ -2129,15 +2129,16 @@ FW_VERSIONS = {
b'F152648811\x00\x00\x00\x00\x00\x00', b'F152648811\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.eps, 0x7a1, None): [ (Ecu.eps, 0x7a1, None): [
b'8965B48271\x00\x00\x00\x00\x00\x00',
b'8965B48261\x00\x00\x00\x00\x00\x00', b'8965B48261\x00\x00\x00\x00\x00\x00',
b'8965B48271\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x750, 0xf): [ (Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301400\x00\x00\x00\x00', b'\x018821F3301400\x00\x00\x00\x00',
], ],
(Ecu.fwdCamera, 0x750, 0x6d): [ (Ecu.fwdCamera, 0x750, 0x6d): [
b'\x028646F4810200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F4810100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F4810100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F4810200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F4810300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
], ],
}, },
CAR.PRIUS_TSS2: { CAR.PRIUS_TSS2: {

@ -361,11 +361,13 @@ FW_VERSIONS = {
b'\xf1\x8703H906026S \xf1\x896693', b'\xf1\x8703H906026S \xf1\x896693',
b'\xf1\x8703H906026S \xf1\x899970', b'\xf1\x8703H906026S \xf1\x899970',
b'\xf1\x873CN906259 \xf1\x890005', b'\xf1\x873CN906259 \xf1\x890005',
b'\xf1\x873CN906259F \xf1\x890002',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927158A \xf1\x893387', b'\xf1\x8709G927158A \xf1\x893387',
b'\xf1\x8709G927158DR\xf1\x893536', b'\xf1\x8709G927158DR\xf1\x893536',
b'\xf1\x8709G927158DR\xf1\x893742', b'\xf1\x8709G927158DR\xf1\x893742',
b'\xf1\x8709G927158EN\xf1\x893691',
b'\xf1\x8709G927158F \xf1\x893489', b'\xf1\x8709G927158F \xf1\x893489',
b'\xf1\x8709G927158FT\xf1\x893835', b'\xf1\x8709G927158FT\xf1\x893835',
b'\xf1\x8709G927158GL\xf1\x893939', b'\xf1\x8709G927158GL\xf1\x893939',

@ -640,7 +640,7 @@ class Controls:
recent_steer_pressed = (self.sm.frame - self.last_steering_pressed_frame)*DT_CTRL < 2.0 recent_steer_pressed = (self.sm.frame - self.last_steering_pressed_frame)*DT_CTRL < 2.0
# Send a "steering required alert" if saturation count has reached the limit # Send a "steering required alert" if saturation count has reached the limit
if lac_log.active and not recent_steer_pressed: if lac_log.active and not recent_steer_pressed and not self.CP.notCar:
if self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode: if self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode:
undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2 undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2
turning = abs(lac_log.desiredLateralAccel) > 1.0 turning = abs(lac_log.desiredLateralAccel) > 1.0

@ -59,12 +59,12 @@ class TestManager(unittest.TestCase):
self.assertTrue(state.running, f"{p.name} not running") self.assertTrue(state.running, f"{p.name} not running")
exit_code = p.stop(retry=False) exit_code = p.stop(retry=False)
self.assertTrue(exit_code is not None, f"{p.name} failed to exit")
# TODO: mapsd should exit cleanly # TODO: mapsd should exit cleanly
if p.name == "mapsd": if p.name == "mapsd":
continue continue
self.assertTrue(exit_code is not None, f"{p.name} failed to exit")
# TODO: interrupted blocking read exits with 1 in cereal. use a more unique return code # TODO: interrupted blocking read exits with 1 in cereal. use a more unique return code
exit_codes = [0, 1] exit_codes = [0, 1]
if p.sigkill: if p.sigkill:

@ -5,6 +5,7 @@
#include <cstdlib> #include <cstdlib>
#include "cereal/visionipc/visionipc_client.h" #include "cereal/visionipc/visionipc_client.h"
#include "common/params.h"
#include "common/swaglog.h" #include "common/swaglog.h"
#include "common/util.h" #include "common/util.h"
#include "selfdrive/modeld/models/dmonitoring.h" #include "selfdrive/modeld/models/dmonitoring.h"
@ -49,6 +50,8 @@ int main(int argc, char **argv) {
DMonitoringModelState model; DMonitoringModelState model;
dmonitoring_init(&model); dmonitoring_init(&model);
Params().putBool("DmModelInitialized", true);
LOGW("connecting to driver stream"); LOGW("connecting to driver stream");
VisionIpcClient vipc_client = VisionIpcClient("camerad", VISION_STREAM_DRIVER, true); VisionIpcClient vipc_client = VisionIpcClient("camerad", VISION_STREAM_DRIVER, true);
while (!do_exit && !vipc_client.connect(false)) { while (!do_exit && !vipc_client.connect(false)) {

@ -5,6 +5,7 @@
#include <cstdlib> #include <cstdlib>
#include "cereal/visionipc/visionipc_client.h" #include "cereal/visionipc/visionipc_client.h"
#include "common/params.h"
#include "common/swaglog.h" #include "common/swaglog.h"
#include "common/util.h" #include "common/util.h"
#include "selfdrive/modeld/models/nav.h" #include "selfdrive/modeld/models/nav.h"
@ -41,6 +42,13 @@ void run_model(NavModelState &model, VisionIpcClient &vipc_client) {
int main(int argc, char **argv) { int main(int argc, char **argv) {
setpriority(PRIO_PROCESS, 0, -15); setpriority(PRIO_PROCESS, 0, -15);
// there exists a race condition when two processes try to create a
// SNPE model runner at the same time, wait for dmonitoringmodeld to finish
LOGW("waiting for dmonitoringmodeld to initialize");
if (!Params().getBool("DmModelInitialized", true)) {
return 0;
}
// init the models // init the models
NavModelState model; NavModelState model;
navmodel_init(&model); navmodel_init(&model);

@ -6,6 +6,7 @@ from collections import defaultdict
from typing import Any from typing import Any
import cereal.messaging as messaging import cereal.messaging as messaging
from common.params import Params
from common.spinner import Spinner from common.spinner import Spinner
from system.hardware import PC from system.hardware import PC
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
@ -62,6 +63,7 @@ def nav_model_replay(lr):
try: try:
assert "MAPBOX_TOKEN" in os.environ assert "MAPBOX_TOKEN" in os.environ
os.environ['MAP_RENDER_TEST_MODE'] = '1' os.environ['MAP_RENDER_TEST_MODE'] = '1'
Params().put_bool('DmModelInitialized', True)
managed_processes['mapsd'].start() managed_processes['mapsd'].start()
managed_processes['navmodeld'].start() managed_processes['navmodeld'].start()

@ -103,6 +103,8 @@ def hw_state_thread(end_event, hw_queue):
modem_version = None modem_version = None
modem_nv = None modem_nv = None
modem_configured = False modem_configured = False
modem_restarted = False
modem_missing_count = 0
while not end_event.is_set(): while not end_event.is_set():
# these are expensive calls. update every 10s # these are expensive calls. update every 10s
@ -120,6 +122,16 @@ def hw_state_thread(end_event, hw_queue):
if (modem_version is not None) and (modem_nv is not None): if (modem_version is not None) and (modem_nv is not None):
cloudlog.event("modem version", version=modem_version, nv=modem_nv) cloudlog.event("modem version", version=modem_version, nv=modem_nv)
else:
if not modem_restarted:
# TODO: we may be able to remove this with a MM update
# ModemManager's probing on startup can fail
# rarely, restart the service to probe again.
modem_missing_count += 1
if modem_missing_count > 3:
modem_restarted = True
cloudlog.event("restarting ModemManager")
os.system("sudo systemctl restart --no-block ModemManager")
tx, rx = HARDWARE.get_modem_data_usage() tx, rx = HARDWARE.get_modem_data_usage()

@ -34,29 +34,6 @@ MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings),
map_eta->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); map_eta->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
map_eta->setFixedHeight(120); map_eta->setFixedHeight(120);
// Settings button
QSize icon_size(120, 120);
directions_icon = loadPixmap("../assets/navigation/icon_directions_outlined.svg", icon_size);
settings_icon = loadPixmap("../assets/navigation/icon_settings.svg", icon_size);
settings_btn = new QPushButton(directions_icon, "", this);
settings_btn->setIconSize(icon_size);
settings_btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
settings_btn->setStyleSheet(R"(
QPushButton {
background-color: #96000000;
border-radius: 50px;
padding: 24px;
margin-left: 30px;
}
QPushButton:pressed {
background-color: #D9000000;
}
)");
QObject::connect(settings_btn, &QPushButton::clicked, [=]() {
emit requestSettings(true);
});
error = new QLabel(this); error = new QLabel(this);
error->setStyleSheet(R"(color:white;padding:50px 11px;font-size: 90px; background-color:rgb(0, 0, 0, 150);)"); error->setStyleSheet(R"(color:white;padding:50px 11px;font-size: 90px; background-color:rgb(0, 0, 0, 150);)");
error->setAlignment(Qt::AlignCenter); error->setAlignment(Qt::AlignCenter);
@ -64,8 +41,6 @@ MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings),
overlay_layout->addWidget(error); overlay_layout->addWidget(error);
overlay_layout->addWidget(map_instructions); overlay_layout->addWidget(map_instructions);
overlay_layout->addStretch(1); overlay_layout->addStretch(1);
overlay_layout->addWidget(settings_btn, Qt::AlignLeft);
overlay_layout->addSpacing(UI_BORDER_SIZE);
overlay_layout->addWidget(map_eta); overlay_layout->addWidget(map_eta);
auto last_gps_position = coordinate_from_param("LastGPSPosition"); auto last_gps_position = coordinate_from_param("LastGPSPosition");
@ -189,10 +164,10 @@ void MapWindow::updateState(const UIState &s) {
// Only open the map on setting destination the first time // Only open the map on setting destination the first time
if (allow_open) { if (allow_open) {
emit requestSettings(false);
emit requestVisible(true); // Show map on destination set/change emit requestVisible(true); // Show map on destination set/change
allow_open = false; allow_open = false;
} }
emit requestSettings(false);
} }
loaded_once = loaded_once || (m_map && m_map->isFullyLoaded()); loaded_once = loaded_once || (m_map && m_map->isFullyLoaded());
@ -252,10 +227,6 @@ void MapWindow::updateState(const UIState &s) {
} else { } else {
clearRoute(); clearRoute();
} }
if (isVisible()) {
settings_btn->setIcon(map_eta->isVisible() ? settings_icon : directions_icon);
}
} }
if (sm.rcv_frame("navRoute") != route_rcv_frame) { if (sm.rcv_frame("navRoute") != route_rcv_frame) {

@ -68,8 +68,6 @@ private:
QLabel *error; QLabel *error;
MapInstructions* map_instructions; MapInstructions* map_instructions;
MapETA* map_eta; MapETA* map_eta;
QPushButton *settings_btn;
QPixmap directions_icon, settings_icon;
// Blue with normal nav, green when nav is input into the model // Blue with normal nav, green when nav is input into the model
QColor getNavPathColor(bool nav_enabled) { QColor getNavPathColor(bool nav_enabled) {

@ -33,3 +33,11 @@ MapPanel::MapPanel(const QMapboxGLSettings &mapboxSettings, QWidget *parent) : Q
}); });
content_stack->addWidget(settings); content_stack->addWidget(settings);
} }
void MapPanel::toggleMapSettings() {
// show settings if not visible, then toggle between map and settings
int new_index = isVisible() ? (1 - content_stack->currentIndex()) : 1;
content_stack->setCurrentIndex(new_index);
emit mapPanelRequested();
show();
}

@ -13,6 +13,9 @@ public:
signals: signals:
void mapPanelRequested(); void mapPanelRequested();
public slots:
void toggleMapSettings();
private: private:
QStackedLayout *content_stack; QStackedLayout *content_stack;
}; };

@ -234,7 +234,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), langs.key(uiState()->language), this); QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), langs.key(uiState()->language), this);
if (!selection.isEmpty()) { if (!selection.isEmpty()) {
// put language setting, exit Qt UI, and trigger fast restart // put language setting, exit Qt UI, and trigger fast restart
Params().put("LanguageSetting", langs[selection].toStdString()); params.put("LanguageSetting", langs[selection].toStdString());
qApp->exit(18); qApp->exit(18);
watchdog_kick(0); watchdog_kick(0);
} }
@ -278,7 +278,7 @@ void DevicePanel::updateCalibDescription() {
QString desc = QString desc =
tr("openpilot requires the device to be mounted within 4° left or right and " tr("openpilot requires the device to be mounted within 4° left or right and "
"within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required."); "within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.");
std::string calib_bytes = Params().get("CalibrationParams"); std::string calib_bytes = params.get("CalibrationParams");
if (!calib_bytes.empty()) { if (!calib_bytes.empty()) {
try { try {
AlignedBuffer aligned_buf; AlignedBuffer aligned_buf;
@ -303,7 +303,7 @@ void DevicePanel::reboot() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), tr("Reboot"), this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), tr("Reboot"), this)) {
// Check engaged again in case it changed while the dialog was open // Check engaged again in case it changed while the dialog was open
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
Params().putBool("DoReboot", true); params.putBool("DoReboot", true);
} }
} }
} else { } else {
@ -316,7 +316,7 @@ void DevicePanel::poweroff() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), tr("Power Off"), this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), tr("Power Off"), this)) {
// Check engaged again in case it changed while the dialog was open // Check engaged again in case it changed while the dialog was open
if (!uiState()->engaged()) { if (!uiState()->engaged()) {
Params().putBool("DoShutdown", true); params.putBool("DoShutdown", true);
} }
} }
} else { } else {

@ -92,6 +92,8 @@ void OnroadWindow::offroadTransition(bool offroad) {
map = m; map = m;
QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::mapPanelRequested); QObject::connect(m, &MapPanel::mapPanelRequested, this, &OnroadWindow::mapPanelRequested);
QObject::connect(nvg->map_settings_btn, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings);
nvg->map_settings_btn->setEnabled(true);
m->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE); m->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE);
split->insertWidget(0, m); split->insertWidget(0, m);
@ -221,17 +223,45 @@ void ExperimentalButton::paintEvent(QPaintEvent *event) {
} }
// MapSettingsButton
MapSettingsButton::MapSettingsButton(QWidget *parent) : QPushButton(parent) {
setFixedSize(btn_size, btn_size);
settings_img = loadPixmap("../assets/navigation/icon_directions_outlined.svg", {img_size, img_size});
// hidden by default, made visible if map is created (has prime or mapbox token)
setVisible(false);
setEnabled(false);
}
void MapSettingsButton::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
QPoint center(btn_size / 2, btn_size / 2);
p.setOpacity(1.0);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 166));
p.drawEllipse(center, btn_size / 2, btn_size / 2);
p.setOpacity(isDown() ? 0.6 : 1.0);
p.drawPixmap((btn_size - img_size) / 2, (btn_size - img_size) / 2, settings_img);
}
// Window that shows camera view and variety of info drawn on top // Window that shows camera view and variety of info drawn on top
AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) {
pm = std::make_unique<PubMaster, const std::initializer_list<const char *>>({"uiDebug"}); pm = std::make_unique<PubMaster, const std::initializer_list<const char *>>({"uiDebug"});
QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout = new QVBoxLayout(this);
main_layout->setMargin(UI_BORDER_SIZE); main_layout->setMargin(UI_BORDER_SIZE);
main_layout->setSpacing(0); main_layout->setSpacing(0);
experimental_btn = new ExperimentalButton(this); experimental_btn = new ExperimentalButton(this);
main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight);
map_settings_btn = new MapSettingsButton(this);
main_layout->addWidget(map_settings_btn, 0, Qt::AlignBottom | Qt::AlignRight);
dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5}); dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5});
} }
@ -276,7 +306,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
setProperty("speed", cur_speed); setProperty("speed", cur_speed);
setProperty("setSpeed", set_speed); setProperty("setSpeed", set_speed);
setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph")); setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph"));
setProperty("hideDM", (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE)); setProperty("hideBottomIcons", (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE));
setProperty("status", s.status); setProperty("status", s.status);
// update engageability/experimental mode button // update engageability/experimental mode button
@ -288,6 +318,12 @@ void AnnotatedCameraWidget::updateState(const UIState &s) {
setProperty("rightHandDM", dm_state.getIsRHD()); setProperty("rightHandDM", dm_state.getIsRHD());
// DM icon transition // DM icon transition
dm_fade_state = std::clamp(dm_fade_state+0.2*(0.5-dmActive), 0.0, 1.0); dm_fade_state = std::clamp(dm_fade_state+0.2*(0.5-dmActive), 0.0, 1.0);
// hide map settings button for alerts and flip for right hand DM
if (map_settings_btn->isEnabled()) {
map_settings_btn->setVisible(!hideBottomIcons);
main_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom);
}
} }
void AnnotatedCameraWidget::drawHud(QPainter &p) { void AnnotatedCameraWidget::drawHud(QPainter &p) {
@ -648,7 +684,7 @@ void AnnotatedCameraWidget::paintGL() {
} }
// DMoji // DMoji
if (!hideDM && (sm.rcv_frame("driverStateV2") > s->scene.started_frame)) { if (!hideBottomIcons && (sm.rcv_frame("driverStateV2") > s->scene.started_frame)) {
update_dmonitoring(s, sm["driverStateV2"].getDriverStateV2(), dm_fade_state, rightHandDM); update_dmonitoring(s, sm["driverStateV2"].getDriverStateV2(), dm_fade_state, rightHandDM);
drawDriverState(painter, s); drawDriverState(painter, s);
} }

@ -47,6 +47,19 @@ private:
bool engageable; bool engageable;
}; };
class MapSettingsButton : public QPushButton {
Q_OBJECT
public:
explicit MapSettingsButton(QWidget *parent = 0);
private:
void paintEvent(QPaintEvent *event) override;
QPixmap settings_img;
};
// container window for the NVG UI // container window for the NVG UI
class AnnotatedCameraWidget : public CameraWidget { class AnnotatedCameraWidget : public CameraWidget {
Q_OBJECT Q_OBJECT
@ -60,7 +73,7 @@ class AnnotatedCameraWidget : public CameraWidget {
Q_PROPERTY(bool is_metric MEMBER is_metric); Q_PROPERTY(bool is_metric MEMBER is_metric);
Q_PROPERTY(bool dmActive MEMBER dmActive); Q_PROPERTY(bool dmActive MEMBER dmActive);
Q_PROPERTY(bool hideDM MEMBER hideDM); Q_PROPERTY(bool hideBottomIcons MEMBER hideBottomIcons);
Q_PROPERTY(bool rightHandDM MEMBER rightHandDM); Q_PROPERTY(bool rightHandDM MEMBER rightHandDM);
Q_PROPERTY(int status MEMBER status); Q_PROPERTY(int status MEMBER status);
@ -68,10 +81,13 @@ public:
explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0); explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0);
void updateState(const UIState &s); void updateState(const UIState &s);
MapSettingsButton *map_settings_btn;
private: private:
void drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity); void drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity);
void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255);
QVBoxLayout *main_layout;
ExperimentalButton *experimental_btn; ExperimentalButton *experimental_btn;
QPixmap dm_img; QPixmap dm_img;
float speed; float speed;
@ -81,7 +97,7 @@ private:
bool is_cruise_set = false; bool is_cruise_set = false;
bool is_metric = false; bool is_metric = false;
bool dmActive = false; bool dmActive = false;
bool hideDM = false; bool hideBottomIcons = false;
bool rightHandDM = false; bool rightHandDM = false;
float dm_fade_state = 1.0; float dm_fade_state = 1.0;
bool has_us_speed_limit = false; bool has_us_speed_limit = false;

@ -173,6 +173,9 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct
} }
void handle_user_flag(LoggerdState *s) { void handle_user_flag(LoggerdState *s) {
static int prev_segment = -1;
if (s->rotate_segment == prev_segment) return;
LOGW("preserving %s", s->segment_path); LOGW("preserving %s", s->segment_path);
#ifdef __APPLE__ #ifdef __APPLE__
@ -183,16 +186,17 @@ void handle_user_flag(LoggerdState *s) {
if (ret) { if (ret) {
LOGE("setxattr %s failed for %s: %s", PRESERVE_ATTR_NAME, s->segment_path, strerror(errno)); LOGE("setxattr %s failed for %s: %s", PRESERVE_ATTR_NAME, s->segment_path, strerror(errno));
} }
prev_segment = s->rotate_segment.load();
} }
void loggerd_thread() { void loggerd_thread() {
// setup messaging // setup messaging
typedef struct QlogState { typedef struct ServiceState {
std::string name; std::string name;
int counter, freq; int counter, freq;
bool encoder; bool encoder, user_flag;
} QlogState; } ServiceState;
std::unordered_map<SubSocket*, QlogState> qlog_states; std::unordered_map<SubSocket*, ServiceState> service_state;
std::unordered_map<SubSocket*, struct RemoteEncoder> remote_encoders; std::unordered_map<SubSocket*, struct RemoteEncoder> remote_encoders;
std::unique_ptr<Context> ctx(Context::create()); std::unique_ptr<Context> ctx(Context::create());
@ -207,11 +211,12 @@ void loggerd_thread() {
SubSocket * sock = SubSocket::create(ctx.get(), it.name); SubSocket * sock = SubSocket::create(ctx.get(), it.name);
assert(sock != NULL); assert(sock != NULL);
poller->registerSocket(sock); poller->registerSocket(sock);
qlog_states[sock] = { service_state[sock] = {
.name = it.name, .name = it.name,
.counter = 0, .counter = 0,
.freq = it.decimation, .freq = it.decimation,
.encoder = encoder, .encoder = encoder,
.user_flag = (strcmp(it.name, "userFlag") == 0),
}; };
} }
@ -236,20 +241,19 @@ void loggerd_thread() {
for (auto sock : poller->poll(1000)) { for (auto sock : poller->poll(1000)) {
if (do_exit) break; if (do_exit) break;
ServiceState &service = service_state[sock];
if (service.user_flag) {
handle_user_flag(&s);
}
// drain socket // drain socket
int count = 0; int count = 0;
QlogState &qs = qlog_states[sock];
Message *msg = nullptr; Message *msg = nullptr;
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 = service.freq != -1 && (service.counter++ % service.freq == 0);
if (service.encoder) {
if (qs.name == "userFlag") {
handle_user_flag(&s);
}
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, service.name, remote_encoders[sock], encoder_infos_dict[service.name]);
} else { } else {
logger_log(&s.logger, (uint8_t *)msg->getData(), msg->getSize(), in_qlog); logger_log(&s.logger, (uint8_t *)msg->getData(), msg->getSize(), in_qlog);
bytes_count += msg->getSize(); bytes_count += msg->getSize();
@ -265,7 +269,7 @@ void loggerd_thread() {
count++; count++;
if (count >= 200) { if (count >= 200) {
LOGD("large volume of '%s' messages", qs.name.c_str()); LOGD("large volume of '%s' messages", service.name.c_str());
break; break;
} }
} }
@ -282,7 +286,7 @@ void loggerd_thread() {
} }
// messaging cleanup // messaging cleanup
for (auto &[sock, qs] : qlog_states) delete sock; for (auto &[sock, service] : service_state) delete sock;
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {

@ -149,20 +149,26 @@ def downloader_loop(event):
time.sleep(10) time.sleep(10)
def inject_assistance(): def inject_assistance():
try: for _ in range(5):
cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}" try:
subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}"
cloudlog.info("successfully loaded assistance data") subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True)
except subprocess.CalledProcessError as e: cloudlog.info("successfully loaded assistance data")
cloudlog.event( return
"rawgps.assistance_loading_failed", except subprocess.CalledProcessError as e:
error=True, cloudlog.event(
cmd=e.cmd, "rawgps.assistance_loading_failed",
output=e.output, error=True,
returncode=e.returncode cmd=e.cmd,
) output=e.output,
returncode=e.returncode
def setup_quectel(diag: ModemDiag): )
time.sleep(0.2)
cloudlog.error("failed to load assistance after retry")
def setup_quectel(diag: ModemDiag) -> bool:
ret = False
# enable OEMDRE in the NV # enable OEMDRE in the NV
# TODO: it has to reboot for this to take effect # TODO: it has to reboot for this to take effect
DIAG_NV_READ_F = 38 DIAG_NV_READ_F = 38
@ -186,7 +192,9 @@ def setup_quectel(diag: ModemDiag):
at_cmd("AT+QGPSXTRA=1") at_cmd("AT+QGPSXTRA=1")
at_cmd("AT+QGPSSUPLURL=\"NULL\"") at_cmd("AT+QGPSSUPLURL=\"NULL\"")
if os.path.exists(ASSIST_DATA_FILE): if os.path.exists(ASSIST_DATA_FILE):
ret = True
inject_assistance() inject_assistance()
os.remove(ASSIST_DATA_FILE)
#at_cmd("AT+QGPSXTRADATA?") #at_cmd("AT+QGPSXTRADATA?")
time_str = datetime.utcnow().strftime("%Y/%m/%d,%H:%M:%S") time_str = datetime.utcnow().strftime("%Y/%m/%d,%H:%M:%S")
at_cmd(f"AT+QGPSXTRATIME=0,\"{time_str}\",1,1,1000") at_cmd(f"AT+QGPSXTRATIME=0,\"{time_str}\",1,1,1000")
@ -213,6 +221,8 @@ def setup_quectel(diag: ModemDiag):
0,0 0,0
)) ))
return ret
def teardown_quectel(diag): def teardown_quectel(diag):
at_cmd("AT+QGPSCFG=\"outport\",\"none\"") at_cmd("AT+QGPSCFG=\"outport\",\"none\"")
if gps_enabled(): if gps_enabled():
@ -260,8 +270,8 @@ def main() -> NoReturn:
# connect to modem # connect to modem
diag = ModemDiag() diag = ModemDiag()
setup_quectel(diag) r = setup_quectel(diag)
want_assistance = True want_assistance = not r
current_gps_time = utc_to_gpst(GPSTime.from_datetime(datetime.utcnow())) current_gps_time = utc_to_gpst(GPSTime.from_datetime(datetime.utcnow()))
cloudlog.warning("quectel setup done") cloudlog.warning("quectel setup done")
gpio_init(GPIO.UBLOX_PWR_EN, True) gpio_init(GPIO.UBLOX_PWR_EN, True)
@ -270,12 +280,9 @@ def main() -> NoReturn:
pm = messaging.PubMaster(['qcomGnss', 'gpsLocation']) pm = messaging.PubMaster(['qcomGnss', 'gpsLocation'])
while 1: while 1:
if os.path.exists(ASSIST_DATA_FILE): if os.path.exists(ASSIST_DATA_FILE) and want_assistance:
if want_assistance: setup_quectel(diag)
setup_quectel(diag) want_assistance = False
want_assistance = False
else:
os.remove(ASSIST_DATA_FILE)
opcode, payload = diag.recv() opcode, payload = diag.recv()
if opcode != DIAG_LOG_F: if opcode != DIAG_LOG_F:

@ -19,12 +19,12 @@ GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0')))
class TestRawgpsd(unittest.TestCase): class TestRawgpsd(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
os.system("sudo systemctl restart systemd-resolved")
os.system("sudo systemctl restart ModemManager lte")
wait_for_modem()
if not TICI: if not TICI:
raise unittest.SkipTest raise unittest.SkipTest
cls.sm = messaging.SubMaster(['qcomGnss', 'gpsLocation', 'gnssMeasurements'])
os.system("sudo systemctl start systemd-resolved")
os.system("sudo systemctl restart ModemManager lte")
wait_for_modem()
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
@ -34,40 +34,44 @@ class TestRawgpsd(unittest.TestCase):
def setUp(self): def setUp(self):
at_cmd("AT+QGPSDEL=0") at_cmd("AT+QGPSDEL=0")
self.sm = messaging.SubMaster(['qcomGnss', 'gpsLocation', 'gnssMeasurements'])
def tearDown(self): def tearDown(self):
managed_processes['rawgpsd'].stop() managed_processes['rawgpsd'].stop()
os.system("sudo systemctl restart systemd-resolved") os.system("sudo systemctl restart systemd-resolved")
def _wait_for_output(self, t=10): def _wait_for_output(self, t):
time.sleep(t) dt = 0.1
self.sm.update() for _ in range(t*int(1/dt)):
self.sm.update(0)
if self.sm.updated['qcomGnss']:
break
time.sleep(dt)
return self.sm.updated['qcomGnss']
def test_no_crash_double_command(self): def test_no_crash_double_command(self):
at_cmd("AT+QGPSDEL=0") at_cmd("AT+QGPSDEL=0")
at_cmd("AT+QGPSDEL=0") at_cmd("AT+QGPSDEL=0")
def test_wait_for_modem(self): def test_wait_for_modem(self):
os.system("sudo systemctl stop ModemManager lte") os.system("sudo systemctl stop ModemManager")
managed_processes['rawgpsd'].start() managed_processes['rawgpsd'].start()
self._wait_for_output(10) assert not self._wait_for_output(5)
assert not self.sm.updated['qcomGnss']
os.system("sudo systemctl restart ModemManager lte") os.system("sudo systemctl restart ModemManager")
self._wait_for_output(30) assert self._wait_for_output(30)
assert self.sm.updated['qcomGnss']
def test_startup_time(self): def test_startup_time(self):
for i in range(2): for internet in (True, False):
if i == 1: if not internet:
os.system("sudo systemctl stop systemd-resolved") os.system("sudo systemctl stop systemd-resolved")
managed_processes['rawgpsd'].start() with self.subTest(internet=internet):
self._wait_for_output(10) managed_processes['rawgpsd'].start()
assert self.sm.updated['qcomGnss'] assert self._wait_for_output(7)
managed_processes['rawgpsd'].stop() managed_processes['rawgpsd'].stop()
def test_turns_off_gnss(self): def test_turns_off_gnss(self):
for s in (0.1, 0.5, 1, 5): for s in (0.1, 1, 5):
managed_processes['rawgpsd'].start() managed_processes['rawgpsd'].start()
time.sleep(s) time.sleep(s)
managed_processes['rawgpsd'].stop() managed_processes['rawgpsd'].stop()
@ -95,8 +99,7 @@ class TestRawgpsd(unittest.TestCase):
def test_assistance_loading(self): def test_assistance_loading(self):
managed_processes['rawgpsd'].start() managed_processes['rawgpsd'].start()
self._wait_for_output(10) assert self._wait_for_output(10)
assert self.sm.updated['qcomGnss']
managed_processes['rawgpsd'].stop() managed_processes['rawgpsd'].stop()
self.check_assistance(True) self.check_assistance(True)
@ -104,8 +107,7 @@ class TestRawgpsd(unittest.TestCase):
os.system("sudo systemctl stop systemd-resolved") os.system("sudo systemctl stop systemd-resolved")
managed_processes['rawgpsd'].start() managed_processes['rawgpsd'].start()
self._wait_for_output(10) assert self._wait_for_output(10)
assert self.sm.updated['qcomGnss']
managed_processes['rawgpsd'].stop() managed_processes['rawgpsd'].stop()
self.check_assistance(False) self.check_assistance(False)
@ -115,8 +117,9 @@ class TestRawgpsd(unittest.TestCase):
managed_processes['rawgpsd'].start() managed_processes['rawgpsd'].start()
self._wait_for_output(17) self._wait_for_output(17)
assert self.sm.updated['qcomGnss'] assert self.sm.updated['qcomGnss']
os.system("sudo systemctl restart systemd-resolved") os.system("sudo systemctl restart systemd-resolved")
self._wait_for_output(15) time.sleep(15)
managed_processes['rawgpsd'].stop() managed_processes['rawgpsd'].stop()
self.check_assistance(True) self.check_assistance(True)

Loading…
Cancel
Save