Merge remote-tracking branch 'upstream/master' into ui-fix-high-draw-times

pull/28902/head
Shane Smiskol 2 years ago
commit e14c1859ed
  1. 1
      .gitignore
  2. 2
      cereal
  3. 3
      docs/CARS.md
  4. 2
      opendbc
  5. 15
      poetry.lock
  6. 1
      pyproject.toml
  7. 10
      selfdrive/car/hyundai/interface.py
  8. 22
      selfdrive/car/hyundai/values.py
  9. 2
      selfdrive/car/tests/routes.py
  10. 1
      selfdrive/car/torque_data/substitute.yaml
  11. 6
      selfdrive/controls/controlsd.py
  12. 1
      selfdrive/modeld/dmonitoringmodeld.cc
  13. 4
      selfdrive/modeld/runners/snpemodel.cc
  14. 1
      selfdrive/test/process_replay/process_replay.py
  15. 2
      selfdrive/test/process_replay/ref_commit
  16. 102
      selfdrive/ui/qt/maps/map.cc
  17. 7
      selfdrive/ui/qt/maps/map.h
  18. 3
      selfdrive/ui/qt/maps/map_panel.cc
  19. 125
      selfdrive/ui/qt/maps/map_settings.cc
  20. 58
      selfdrive/ui/qt/maps/map_settings.h
  21. 2
      selfdrive/ui/qt/request_repeater.cc
  22. 8
      selfdrive/ui/qt/window.cc
  23. 2
      selfdrive/ui/qt/window.h
  24. 16
      selfdrive/ui/ui.cc
  25. 8
      selfdrive/ui/ui.h
  26. 135
      system/sensord/rawgps/rawgpsd.py
  27. 132
      system/sensord/rawgps/test_rawgps.py

1
.gitignore vendored

@ -45,6 +45,7 @@ selfdrive/logcatd/logcatd
selfdrive/mapd/default_speeds_by_region.json selfdrive/mapd/default_speeds_by_region.json
system/proclogd/proclogd system/proclogd/proclogd
selfdrive/ui/_ui selfdrive/ui/_ui
selfdrive/ui/translations/alerts_generated.h
selfdrive/test/longitudinal_maneuvers/out selfdrive/test/longitudinal_maneuvers/out
selfdrive/car/tests/cars_dump selfdrive/car/tests/cars_dump
system/camerad/camerad system/camerad/camerad

@ -1 +1 @@
Subproject commit c94c7c61cc576e950a1604e1a3c9a91b1f86964c Subproject commit a2f1f0cb8dd45ea4265255855da7de8fd89156ed

@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
# 254 Supported Cars # 255 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video| |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@ -107,6 +107,7 @@ A supported vehicle is one that just works when you install a comma three. All s
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 FCA 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=Jeep&model=Grand Cherokee 2016-18">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=eLR9o2JkuRk" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 FCA 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=Jeep&model=Grand Cherokee 2016-18">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=eLR9o2JkuRk" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 FCA 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=Jeep&model=Grand Cherokee 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=jBe4lWnRSu4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 FCA 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=Jeep&model=Grand Cherokee 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=jBe4lWnRSu4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Kia|Carnival 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 Hyundai A 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=Kia&model=Carnival 2023">Buy Here</a></sub></details>|| |Kia|Carnival 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 Hyundai A 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=Kia&model=Carnival 2023">Buy Here</a></sub></details>||
|Kia|Carnival (China only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 Hyundai K 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=Kia&model=Carnival (China only) 2023">Buy Here</a></sub></details>||
|Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 Hyundai E 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=Kia&model=Ceed 2019">Buy Here</a></sub></details>|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>View</summary><sub>- 1 Hyundai E 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=Kia&model=Ceed 2019">Buy Here</a></sub></details>||
|Kia|EV6 (Southeast Asia only) 2022-23[<sup>6</sup>](#footnotes)|All|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 Hyundai P 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=Kia&model=EV6 (Southeast Asia only) 2022-23">Buy Here</a></sub></details>|| |Kia|EV6 (Southeast Asia only) 2022-23[<sup>6</sup>](#footnotes)|All|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 Hyundai P 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=Kia&model=EV6 (Southeast Asia only) 2022-23">Buy Here</a></sub></details>||
|Kia|EV6 (with HDA II) 2022-23[<sup>6</sup>](#footnotes)|Highway Driving Assist II|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 Hyundai P 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=Kia&model=EV6 (with HDA II) 2022-23">Buy Here</a></sub></details>|| |Kia|EV6 (with HDA II) 2022-23[<sup>6</sup>](#footnotes)|Highway Driving Assist II|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 Hyundai P 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=Kia&model=EV6 (with HDA II) 2022-23">Buy Here</a></sub></details>||

@ -1 +1 @@
Subproject commit fe8d535a7fd99eeb15526ca944a6019b9a1e5ea0 Subproject commit 236359cf63c3caaf8e02b972c452aabac416662a

15
poetry.lock generated

@ -4280,14 +4280,6 @@ pure-eval = "*"
[package.extras] [package.extras]
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
[[package]]
name = "subprocess32"
version = "3.5.4"
description = "A backport of the subprocess module from Python 3 for use on 2.x."
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
[[package]] [[package]]
name = "sympy" name = "sympy"
version = "1.11.1" version = "1.11.1"
@ -4921,7 +4913,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "~3.8" python-versions = "~3.8"
content-hash = "e6fcadbd1083d80b2e70d287b927d897e1a3d4f4907f4e5443f4d4b23ac02d89" content-hash = "f296825a07c5536c82529833a60996ffd5ae8c3e3537d1da1f9b26150b3d899d"
[metadata.files] [metadata.files]
adal = [ adal = [
@ -8736,11 +8728,6 @@ stack-data = [
{file = "stack_data-0.5.1-py3-none-any.whl", hash = "sha256:5120731a18ba4c82cefcf84a945f6f3e62319ef413bfc210e32aca3a69310ba2"}, {file = "stack_data-0.5.1-py3-none-any.whl", hash = "sha256:5120731a18ba4c82cefcf84a945f6f3e62319ef413bfc210e32aca3a69310ba2"},
{file = "stack_data-0.5.1.tar.gz", hash = "sha256:95eb784942e861a3d80efd549ff9af6cf847d88343a12eb681d7157cfcb6e32b"}, {file = "stack_data-0.5.1.tar.gz", hash = "sha256:95eb784942e861a3d80efd549ff9af6cf847d88343a12eb681d7157cfcb6e32b"},
] ]
subprocess32 = [
{file = "subprocess32-3.5.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b"},
{file = "subprocess32-3.5.4-cp27-cp27mu-manylinux2014_x86_64.whl", hash = "sha256:e45d985aef903c5b7444d34350b05da91a9e0ea015415ab45a21212786c649d0"},
{file = "subprocess32-3.5.4.tar.gz", hash = "sha256:eb2937c80497978d181efa1b839ec2d9622cf9600a039a79d0e108d1f9aec79d"},
]
sympy = [ sympy = [
{file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"}, {file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"},
{file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"}, {file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"},

@ -100,7 +100,6 @@ scipy = "^1.8.1"
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"
subprocess32 = "^3.5.4"
tabulate = "^0.8.10" tabulate = "^0.8.10"
tenacity = "^8.0.1" tenacity = "^8.0.1"
types-atomicwrites = "^1.4.5" types-atomicwrites = "^1.4.5"

@ -26,7 +26,7 @@ class CarInterface(CarInterfaceBase):
# These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars have been put into dashcam only due to both a lack of users and test coverage.
# These cars likely still work fine. Once a user confirms each car works and a test route is # These cars likely still work fine. Once a user confirms each car works and a test route is
# added to selfdrive/car/tests/routes.py, we can remove it from this list. # added to selfdrive/car/tests/routes.py, we can remove it from this list.
ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, } ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, CAR.IONIQ_6}
hda2 = Ecu.adas in [fw.ecu for fw in car_fw] hda2 = Ecu.adas in [fw.ecu for fw in car_fw]
CAN = CanBus(None, hda2, fingerprint) CAN = CanBus(None, hda2, fingerprint)
@ -186,10 +186,10 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.9 ret.wheelbase = 2.9
ret.steerRatio = 16. ret.steerRatio = 16.
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
elif candidate == CAR.IONIQ_5: elif candidate in (CAR.IONIQ_5, CAR.IONIQ_6):
ret.mass = 2012 + STD_CARGO_KG ret.mass = 1948 + STD_CARGO_KG
ret.wheelbase = 3.0 ret.wheelbase = 2.97
ret.steerRatio = 16. ret.steerRatio = 14.26
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
elif candidate == CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: elif candidate == CAR.KIA_SPORTAGE_HYBRID_5TH_GEN:
ret.mass = 1767. + STD_CARGO_KG # SX Prestige trim support only ret.mass = 1767. + STD_CARGO_KG # SX Prestige trim support only

@ -92,6 +92,7 @@ class CAR:
VELOSTER = "HYUNDAI VELOSTER 2019" VELOSTER = "HYUNDAI VELOSTER 2019"
SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021" SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021"
IONIQ_5 = "HYUNDAI IONIQ 5 2022" IONIQ_5 = "HYUNDAI IONIQ 5 2022"
IONIQ_6 = "HYUNDAI IONIQ 6 2023"
TUCSON_4TH_GEN = "HYUNDAI TUCSON 4TH GEN" TUCSON_4TH_GEN = "HYUNDAI TUCSON 4TH GEN"
TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH GEN" TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH GEN"
SANTA_CRUZ_1ST_GEN = "HYUNDAI SANTA CRUZ 1ST GEN" SANTA_CRUZ_1ST_GEN = "HYUNDAI SANTA CRUZ 1ST GEN"
@ -190,6 +191,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_k])), HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_k])),
HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q])), HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q])),
], ],
CAR.IONIQ_6: [
HyundaiCarInfo("Hyundai Ioniq 6 (without HDA II) 2023", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_k])), # TODO: unknown
HyundaiCarInfo("Hyundai Ioniq 6 (with HDA II) 2023", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])),
],
CAR.TUCSON_4TH_GEN: [ CAR.TUCSON_4TH_GEN: [
HyundaiCarInfo("Hyundai Tucson 2022", car_parts=CarParts.common([CarHarness.hyundai_n])), HyundaiCarInfo("Hyundai Tucson 2022", car_parts=CarParts.common([CarHarness.hyundai_n])),
HyundaiCarInfo("Hyundai Tucson 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_n])), HyundaiCarInfo("Hyundai Tucson 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_n])),
@ -244,7 +249,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
], ],
CAR.KIA_CARNIVAL_4TH_GEN: [ CAR.KIA_CARNIVAL_4TH_GEN: [
HyundaiCarInfo("Kia Carnival 2023", car_parts=CarParts.common([CarHarness.hyundai_a])), HyundaiCarInfo("Kia Carnival 2023", car_parts=CarParts.common([CarHarness.hyundai_a])),
# HyundaiCarInfo("Kia Carnival (China only) 2023", car_parts=CarParts.common([CarHarness.hyundai_k])) HyundaiCarInfo("Kia Carnival (China only) 2023", car_parts=CarParts.common([CarHarness.hyundai_k]))
], ],
# Genesis # Genesis
@ -1766,6 +1771,14 @@ FW_VERSIONS = {
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401',
], ],
}, },
CAR.IONIQ_6: {
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00CE__ RDR ----- 1.00 1.01 99110-KL000 ',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CE MFC AT USA LHD 1.00 1.04 99211-KL000 221213',
],
},
CAR.TUCSON_4TH_GEN: { CAR.TUCSON_4TH_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G',
@ -1870,9 +1883,11 @@ FW_VERSIONS = {
CAR.KIA_CARNIVAL_4TH_GEN: { CAR.KIA_CARNIVAL_4TH_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.06 99210-R0000 220221', b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.06 99210-R0000 220221',
b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525',
], ],
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ', b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ',
b'\xf1\x00KA4c SCC FHCUP 1.00 1.01 99110-I4000 ',
], ],
}, },
} }
@ -1889,7 +1904,7 @@ CAN_GEARS = {
"use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022, CAR.KIA_K5_HEV_2020}, "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022, CAR.KIA_K5_HEV_2020},
} }
CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_EV_2ND_GEN, CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN} CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_EV_2ND_GEN, CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN}
# The radar does SCC on these cars when HDA I, rather than the camera # The radar does SCC on these cars when HDA I, rather than the camera
CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN} CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN}
@ -1898,7 +1913,7 @@ CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, C
CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } CAMERA_SCC_CAR = {CAR.KONA_EV_2022, }
HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN} # these cars use a different gas signal HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN} # these cars use a different gas signal
EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5, CAR.GENESIS_GV60_EV_1ST_GEN} EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.GENESIS_GV60_EV_1ST_GEN}
# these cars require a special panda safety mode due to missing counters and checksums in the messages # these cars require a special panda safety mode due to missing counters and checksums in the messages
LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER,
@ -1953,6 +1968,7 @@ DBC = {
CAR.TUCSON_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.TUCSON_4TH_GEN: dbc_dict('hyundai_canfd', None),
CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None),
CAR.IONIQ_5: dbc_dict('hyundai_canfd', None), CAR.IONIQ_5: dbc_dict('hyundai_canfd', None),
CAR.IONIQ_6: dbc_dict('hyundai_canfd', None),
CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None),
CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None),
CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None),

@ -21,6 +21,7 @@ non_tested_cars = [
GM.MALIBU, GM.MALIBU,
GM.EQUINOX, GM.EQUINOX,
HYUNDAI.GENESIS_G90, HYUNDAI.GENESIS_G90,
HYUNDAI.IONIQ_6,
HYUNDAI.KIA_OPTIMA_H, HYUNDAI.KIA_OPTIMA_H,
HONDA.ODYSSEY_CHN, HONDA.ODYSSEY_CHN,
VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter
@ -99,6 +100,7 @@ routes = [
CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022), CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022),
CarTestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.SANTA_FE_PHEV_2022, segment=1), CarTestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.SANTA_FE_PHEV_2022, segment=1),
CarTestRoute("de59124955b921d8|2023-06-24--00-12-50", HYUNDAI.KIA_CARNIVAL_4TH_GEN), CarTestRoute("de59124955b921d8|2023-06-24--00-12-50", HYUNDAI.KIA_CARNIVAL_4TH_GEN),
CarTestRoute("409c9409979a8abc|2023-07-11--09-06-44", HYUNDAI.KIA_CARNIVAL_4TH_GEN), # Chinese model
CarTestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED), CarTestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED),
CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA_G4), CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA_G4),
CarTestRoute("018654717bc93d7d|2022-09-19--23-11-10", HYUNDAI.KIA_OPTIMA_G4_FL, segment=0), CarTestRoute("018654717bc93d7d|2022-09-19--23-11-10", HYUNDAI.KIA_OPTIMA_G4_FL, segment=0),

@ -34,6 +34,7 @@ HYUNDAI KONA ELECTRIC 2022: HYUNDAI KONA ELECTRIC 2019
HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019
HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019
HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019
HYUNDAI IONIQ 6 2023: HYUNDAI IONIQ 5 2022
HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019
HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020
HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019

@ -373,7 +373,7 @@ class Controls:
else: else:
self.logged_comm_issue = None self.logged_comm_issue = None
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and not SIMULATION: if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY):
self.events.add(EventName.vehicleModelInvalid) self.events.add(EventName.vehicleModelInvalid)
if not self.sm['lateralPlan'].mpcSolutionValid: if not self.sm['lateralPlan'].mpcSolutionValid:
self.events.add(EventName.plannerError) self.events.add(EventName.plannerError)
@ -411,7 +411,7 @@ class Controls:
pass pass
# TODO: fix simulator # TODO: fix simulator
if not SIMULATION: if not SIMULATION or REPLAY:
if not NOSENSOR: if not NOSENSOR:
if not self.sm['liveLocationKalman'].gpsOK and self.sm['liveLocationKalman'].inputsOK and (self.distance_traveled > 1000): if not self.sm['liveLocationKalman'].gpsOK and self.sm['liveLocationKalman'].inputsOK and (self.distance_traveled > 1000):
# Not show in first 1 km to allow for driving out of garage. This event shows after 5 minutes # Not show in first 1 km to allow for driving out of garage. This event shows after 5 minutes
@ -436,7 +436,7 @@ class Controls:
if not self.initialized: if not self.initialized:
all_valid = CS.canValid and self.sm.all_checks() all_valid = CS.canValid and self.sm.all_checks()
timed_out = self.sm.frame * DT_CTRL > (6. if REPLAY else 3.5) timed_out = self.sm.frame * DT_CTRL > (6. if REPLAY else 3.5)
if all_valid or timed_out or SIMULATION: if all_valid or timed_out or (SIMULATION and not REPLAY):
available_streams = VisionIpcClient.available_streams("camerad", block=False) available_streams = VisionIpcClient.available_streams("camerad", block=False)
if VisionStreamType.VISION_STREAM_ROAD not in available_streams: if VisionStreamType.VISION_STREAM_ROAD not in available_streams:
self.sm.ignore_alive.append('roadCameraState') self.sm.ignore_alive.append('roadCameraState')

@ -49,6 +49,7 @@ int main(int argc, char **argv) {
DMonitoringModelState model; DMonitoringModelState model;
dmonitoring_init(&model); dmonitoring_init(&model);
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)) {
util::sleep_for(100); util::sleep_for(100);

@ -33,7 +33,7 @@ SNPEModel::SNPEModel(const std::string path, float *_output, size_t _output_size
// load model // load model
std::unique_ptr<zdl::DlContainer::IDlContainer> container = zdl::DlContainer::IDlContainer::open((uint8_t*)model_data.data(), model_data.size()); std::unique_ptr<zdl::DlContainer::IDlContainer> container = zdl::DlContainer::IDlContainer::open((uint8_t*)model_data.data(), model_data.size());
if (!container) { PrintErrorStringAndExit(); } if (!container) { PrintErrorStringAndExit(); }
printf("loaded model with size: %lu\n", model_data.size()); LOGW("loaded model with size: %lu", model_data.size());
// create model runner // create model runner
zdl::SNPE::SNPEBuilder snpe_builder(container.get()); zdl::SNPE::SNPEBuilder snpe_builder(container.get());
@ -86,7 +86,7 @@ void SNPEModel::addInput(const std::string name, float *buffer, int size) {
const auto &input_tensor_names = *input_tensor_names_opt; const auto &input_tensor_names = *input_tensor_names_opt;
const char *input_tensor_name = input_tensor_names.at(idx); const char *input_tensor_name = input_tensor_names.at(idx);
const bool input_tf8 = use_tf8 && strcmp(input_tensor_name, "input_img") == 0; // TODO: This is a terrible hack, get rid of this name check both here and in onnx_runner.py const bool input_tf8 = use_tf8 && strcmp(input_tensor_name, "input_img") == 0; // TODO: This is a terrible hack, get rid of this name check both here and in onnx_runner.py
printf("adding index %d: %s\n", idx, input_tensor_name); LOGW("adding index %d: %s", idx, input_tensor_name);
zdl::DlSystem::UserBufferEncodingFloat ub_encoding_float; zdl::DlSystem::UserBufferEncodingFloat ub_encoding_float;
zdl::DlSystem::UserBufferEncodingTf8 ub_encoding_tf8(0, 1./255); // network takes 0-1 zdl::DlSystem::UserBufferEncodingTf8 ub_encoding_tf8(0, 1./255); // network takes 0-1

@ -273,7 +273,6 @@ CONFIGS = [
init_callback=controlsd_fingerprint_callback, init_callback=controlsd_fingerprint_callback,
should_recv_callback=controlsd_rcv_callback, should_recv_callback=controlsd_rcv_callback,
tolerance=NUMPY_TOLERANCE, tolerance=NUMPY_TOLERANCE,
simulation=False,
main_pub="can", main_pub="can",
), ),
ProcessConfig( ProcessConfig(

@ -1 +1 @@
a683d689bd74ba5ba7c10bfad237aacc04b978c3 219a815856d8984cb4933d83db9a15bf7cd09f16

@ -6,7 +6,6 @@
#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"
#include "selfdrive/ui/qt/request_repeater.h"
#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/ui.h" #include "selfdrive/ui/ui.h"
@ -61,6 +60,11 @@ MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings),
emit requestSettings(true); emit requestSettings(true);
}); });
error = new QLabel(this);
error->setStyleSheet(R"(color:white;padding:50px 11px;font-size: 90px; background-color:rgb(0, 0, 0, 150);)");
error->setAlignment(Qt::AlignCenter);
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->addWidget(settings_btn, Qt::AlignLeft);
@ -169,21 +173,15 @@ void MapWindow::updateState(const UIState &s) {
emit requestSettings(false); emit requestSettings(false);
} }
if (m_map.isNull()) { loaded_once = loaded_once || (m_map && m_map->isFullyLoaded());
return;
}
loaded_once = loaded_once || m_map->isFullyLoaded();
if (!loaded_once) { if (!loaded_once) {
map_instructions->showError(tr("Map Loading")); setError(tr("Map Loading"));
return; return;
} }
initLayers(); initLayers();
setError(locationd_valid ? "" : tr("Waiting for GPS"));
if (locationd_valid) { if (locationd_valid) {
map_instructions->noError();
// Update current location marker // Update current location marker
auto point = coordinate_to_collection(*last_position); auto point = coordinate_to_collection(*last_position);
QMapbox::Feature feature1(QMapbox::Feature::PointType, point, {}, {}); QMapbox::Feature feature1(QMapbox::Feature::PointType, point, {}, {});
@ -191,8 +189,6 @@ void MapWindow::updateState(const UIState &s) {
carPosSource["type"] = "geojson"; carPosSource["type"] = "geojson";
carPosSource["data"] = QVariant::fromValue<QMapbox::Feature>(feature1); carPosSource["data"] = QVariant::fromValue<QMapbox::Feature>(feature1);
m_map->updateSource("carPosSource", carPosSource); m_map->updateSource("carPosSource", carPosSource);
} else {
map_instructions->showError(tr("Waiting for GPS"));
} }
if (pan_counter == 0) { if (pan_counter == 0) {
@ -242,6 +238,14 @@ void MapWindow::updateState(const UIState &s) {
} }
} }
void MapWindow::setError(const QString &err_str) {
if (err_str != error->text()) {
error->setText(err_str);
error->setVisible(!err_str.isEmpty());
if (!err_str.isEmpty()) map_instructions->setVisible(false);
}
}
void MapWindow::resizeGL(int w, int h) { void MapWindow::resizeGL(int w, int h) {
m_map->resize(size() / MAP_SCALE); m_map->resize(size() / MAP_SCALE);
map_overlay->setFixedSize(width(), height()); map_overlay->setFixedSize(width(), height());
@ -279,7 +283,7 @@ void MapWindow::clearRoute() {
updateDestinationMarker(); updateDestinationMarker();
} }
map_instructions->hideIfNoError(); map_instructions->setVisible(false);
map_eta->setVisible(false); map_eta->setVisible(false);
allow_open = true; allow_open = true;
} }
@ -378,46 +382,31 @@ void MapWindow::updateDestinationMarker() {
} }
} }
MapInstructions::MapInstructions(QWidget * parent) : QWidget(parent) { MapInstructions::MapInstructions(QWidget *parent) : QWidget(parent) {
is_rhd = Params().getBool("IsRhdDetected"); is_rhd = Params().getBool("IsRhdDetected");
QHBoxLayout *main_layout = new QHBoxLayout(this); QHBoxLayout *main_layout = new QHBoxLayout(this);
main_layout->setContentsMargins(11, 50, 11, 11); main_layout->setContentsMargins(11, 50, 11, 11);
{ main_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop);
QVBoxLayout *layout = new QVBoxLayout;
icon_01 = new QLabel;
layout->addWidget(icon_01);
layout->addStretch();
main_layout->addLayout(layout);
}
{
QVBoxLayout *layout = new QVBoxLayout;
distance = new QLabel;
distance->setStyleSheet(R"(font-size: 90px;)");
layout->addWidget(distance);
primary = new QLabel; QWidget *right_container = new QWidget(this);
primary->setStyleSheet(R"(font-size: 60px;)"); right_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
primary->setWordWrap(true); QVBoxLayout *layout = new QVBoxLayout(right_container);
layout->addWidget(primary);
secondary = new QLabel; layout->addWidget(distance = new QLabel);
secondary->setStyleSheet(R"(font-size: 50px;)"); distance->setStyleSheet(R"(font-size: 90px;)");
secondary->setWordWrap(true);
layout->addWidget(secondary);
lane_widget = new QWidget; layout->addWidget(primary = new QLabel);
lane_widget->setFixedHeight(125); primary->setStyleSheet(R"(font-size: 60px;)");
primary->setWordWrap(true);
lane_layout = new QHBoxLayout(lane_widget); layout->addWidget(secondary = new QLabel);
layout->addWidget(lane_widget); secondary->setStyleSheet(R"(font-size: 50px;)");
secondary->setWordWrap(true);
main_layout->addLayout(layout); layout->addLayout(lane_layout = new QHBoxLayout);
} main_layout->addWidget(right_container);
setStyleSheet("color:white"); setStyleSheet("color:white");
QPalette pal = palette(); QPalette pal = palette();
pal.setColor(QPalette::Background, QColor(0, 0, 0, 150)); pal.setColor(QPalette::Background, QColor(0, 0, 0, 150));
setAutoFillBackground(true); setAutoFillBackground(true);
@ -436,24 +425,6 @@ QString MapInstructions::getDistance(float d) {
} }
} }
void MapInstructions::showError(QString error_text) {
primary->setText("");
distance->setText(error_text);
distance->setAlignment(Qt::AlignCenter);
secondary->setVisible(false);
icon_01->setVisible(false);
this->error = true;
lane_widget->setVisible(false);
setVisible(true);
}
void MapInstructions::noError() {
error = false;
}
void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruction) { void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruction) {
setUpdatesEnabled(false); setUpdatesEnabled(false);
@ -464,7 +435,6 @@ void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruct
primary->setText(primary_str); primary->setText(primary_str);
secondary->setVisible(secondary_str.length() > 0); secondary->setVisible(secondary_str.length() > 0);
secondary->setText(secondary_str); secondary->setText(secondary_str);
distance->setAlignment(Qt::AlignLeft);
distance->setText(getDistance(instruction.getManeuverDistance())); distance->setText(getDistance(instruction.getManeuverDistance()));
// Show arrow with direction // Show arrow with direction
@ -534,19 +504,11 @@ void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruct
for (int i = lanes.size(); i < lane_labels.size(); ++i) { for (int i = lanes.size(); i < lane_labels.size(); ++i) {
lane_labels[i]->setVisible(false); lane_labels[i]->setVisible(false);
} }
lane_widget->setVisible(lanes.size() > 0);
setUpdatesEnabled(true); setUpdatesEnabled(true);
setVisible(true); setVisible(true);
} }
void MapInstructions::hideIfNoError() {
if (!error) {
hide();
}
}
MapETA::MapETA(QWidget *parent) : QWidget(parent) { MapETA::MapETA(QWidget *parent) : QWidget(parent) {
setVisible(false); setVisible(false);
setAttribute(Qt::WA_TranslucentBackground); setAttribute(Qt::WA_TranslucentBackground);

@ -31,17 +31,12 @@ private:
QLabel *primary; QLabel *primary;
QLabel *secondary; QLabel *secondary;
QLabel *icon_01; QLabel *icon_01;
QWidget *lane_widget;
QHBoxLayout *lane_layout; QHBoxLayout *lane_layout;
bool error = false;
bool is_rhd = false; bool is_rhd = false;
std::vector<QLabel *> lane_labels; std::vector<QLabel *> lane_labels;
public: public:
MapInstructions(QWidget * parent=nullptr); MapInstructions(QWidget * parent=nullptr);
void showError(QString error);
void noError();
void hideIfNoError();
QString getDistance(float d); QString getDistance(float d);
void updateInstructions(cereal::NavInstruction::Reader instruction); void updateInstructions(cereal::NavInstruction::Reader instruction);
}; };
@ -87,6 +82,7 @@ private:
bool event(QEvent *event) final; bool event(QEvent *event) final;
bool gestureEvent(QGestureEvent *event); bool gestureEvent(QGestureEvent *event);
void pinchTriggered(QPinchGesture *gesture); void pinchTriggered(QPinchGesture *gesture);
void setError(const QString &err_str);
bool m_sourceAdded = false; bool m_sourceAdded = false;
@ -105,6 +101,7 @@ private:
bool locationd_valid = false; bool locationd_valid = false;
QWidget *map_overlay; QWidget *map_overlay;
QLabel *error;
MapInstructions* map_instructions; MapInstructions* map_instructions;
MapETA* map_eta; MapETA* map_eta;
QPushButton *settings_btn; QPushButton *settings_btn;

@ -14,6 +14,9 @@ MapPanel::MapPanel(const QMapboxGLSettings &mapboxSettings, QWidget *parent) : Q
auto map = new MapWindow(mapboxSettings); auto map = new MapWindow(mapboxSettings);
QObject::connect(uiState(), &UIState::offroadTransition, map, &MapWindow::offroadTransition); QObject::connect(uiState(), &UIState::offroadTransition, map, &MapWindow::offroadTransition);
QObject::connect(device(), &Device::interactiveTimeout, [=]() {
content_stack->setCurrentIndex(0);
});
QObject::connect(map, &MapWindow::requestVisible, [=](bool visible) { QObject::connect(map, &MapWindow::requestVisible, [=](bool visible) {
// when we show the map for a new route, signal HomeWindow to hide the sidebar // when we show the map for a new route, signal HomeWindow to hide the sidebar
if (visible) { emit mapPanelRequested(); } if (visible) { emit mapPanelRequested(); }

@ -2,7 +2,6 @@
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
#include <vector>
#include "common/util.h" #include "common/util.h"
#include "selfdrive/ui/qt/request_repeater.h" #include "selfdrive/ui/qt/request_repeater.h"
@ -12,11 +11,8 @@ static QString shorten(const QString &str, int max_len) {
return str.size() > max_len ? str.left(max_len).trimmed() + "" : str; return str.size() > max_len ? str.left(max_len).trimmed() + "" : str;
} }
MapSettings::MapSettings(bool closeable, QWidget *parent) MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
: QFrame(parent), current_destination(nullptr) { close_icon = loadPixmap("../assets/icons/close.svg", {100, 100});
QSize icon_size(100, 100);
close_icon = loadPixmap("../assets/icons/close.svg", icon_size);
setContentsMargins(0, 0, 0, 0); setContentsMargins(0, 0, 0, 0);
auto *frame = new QVBoxLayout(this); auto *frame = new QVBoxLayout(this);
@ -68,7 +64,7 @@ MapSettings::MapSettings(bool closeable, QWidget *parent)
current_widget = new DestinationWidget(this); current_widget = new DestinationWidget(this);
QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() { QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() {
if (!current_destination) return; if (current_destination.empty()) return;
params.remove("NavDestination"); params.remove("NavDestination");
updateCurrentRoute(); updateCurrentRoute();
}); });
@ -85,7 +81,7 @@ MapSettings::MapSettings(bool closeable, QWidget *parent)
setStyleSheet("MapSettings { background-color: #333333; }"); setStyleSheet("MapSettings { background-color: #333333; }");
QObject::connect(NavigationRequest::instance(), &NavigationRequest::locationsUpdated, this, &MapSettings::parseResponse); QObject::connect(NavigationRequest::instance(), &NavigationRequest::locationsUpdated, this, &MapSettings::updateLocations);
QObject::connect(NavigationRequest::instance(), &NavigationRequest::nextDestinationUpdated, this, &MapSettings::updateCurrentRoute); QObject::connect(NavigationRequest::instance(), &NavigationRequest::nextDestinationUpdated, this, &MapSettings::updateCurrentRoute);
} }
@ -106,72 +102,37 @@ void MapSettings::updateCurrentRoute() {
qWarning() << "JSON Parse failed on NavDestination" << dest; qWarning() << "JSON Parse failed on NavDestination" << dest;
return; return;
} }
auto destination = std::make_unique<NavDestination>(doc.object()); current_destination = doc.object();
if (current_destination && *destination == *current_destination) return; current_widget->set(current_destination, true);
current_destination = std::move(destination);
current_widget->set(current_destination.get(), true);
} else { } else {
current_destination.reset(nullptr); current_destination = {};
current_widget->unset("", true); current_widget->unset("", true);
} }
if (isVisible()) refresh(); if (isVisible()) refresh();
} }
void MapSettings::parseResponse(const QString &response, bool success) { void MapSettings::updateLocations(const QJsonArray &locations) {
if (!success || response == cur_destinations) return; current_locations = locations;
cur_destinations = response;
refresh(); refresh();
} }
void MapSettings::refresh() { void MapSettings::refresh() {
bool has_home = false, has_work = false; setUpdatesEnabled(false);
auto destinations = std::vector<std::unique_ptr<NavDestination>>();
auto destinations_str = cur_destinations.trimmed();
if (!destinations_str.isEmpty()) {
QJsonDocument doc = QJsonDocument::fromJson(destinations_str.toUtf8());
if (doc.isNull()) {
qWarning() << "JSON Parse failed on navigation locations" << cur_destinations;
return;
}
for (auto el : doc.array()) {
auto destination = std::make_unique<NavDestination>(el.toObject());
// add home and work later if they are missing
if (destination->isFavorite()) {
if (destination->label() == NAV_FAVORITE_LABEL_HOME) has_home = true;
else if (destination->label() == NAV_FAVORITE_LABEL_WORK) has_work = true;
}
// skip current destination
if (current_destination && *destination == *current_destination) continue;
destinations.push_back(std::move(destination));
}
}
// TODO: should we build a new layout and swap it in? // TODO: should we build a new layout and swap it in?
clearLayout(destinations_layout); clearLayout(destinations_layout);
// Sort: HOME, WORK, alphabetical FAVORITES, and then most recent (as returned by API) bool has_home = false, has_work = false;
std::stable_sort(destinations.begin(), destinations.end(), [](const auto &a, const auto &b) { for (auto location : current_locations) {
if (a->isFavorite() && b->isFavorite()) { auto dest = location.toObject();
if (a->label() == NAV_FAVORITE_LABEL_HOME) return true; if (dest["save_type"].toString() == NAV_TYPE_FAVORITE) {
else if (b->label() == NAV_FAVORITE_LABEL_HOME) return false; has_home = has_home || dest["label"].toString() == NAV_FAVORITE_LABEL_HOME;
else if (a->label() == NAV_FAVORITE_LABEL_WORK) return true; has_work = has_work || dest["label"].toString() == NAV_FAVORITE_LABEL_WORK;
else if (b->label() == NAV_FAVORITE_LABEL_WORK) return false;
else return a->name() < b->name();
} }
else if (a->isFavorite()) return true; if (dest == current_destination) continue;
else if (b->isFavorite()) return false;
return false;
});
for (auto &destination : destinations) {
auto widget = new DestinationWidget(this); auto widget = new DestinationWidget(this);
widget->set(destination.get(), false); widget->set(dest, false);
QObject::connect(widget, &QPushButton::clicked, [this, dest]() {
QObject::connect(widget, &QPushButton::clicked, [this, dest = destination->toJson()]() {
navigateTo(dest); navigateTo(dest);
emit closeSettings(); emit closeSettings();
}); });
@ -189,11 +150,12 @@ void MapSettings::refresh() {
auto widget = new DestinationWidget(this); auto widget = new DestinationWidget(this);
widget->unset(NAV_FAVORITE_LABEL_WORK); widget->unset(NAV_FAVORITE_LABEL_WORK);
// TODO: refactor to remove this hack // TODO: refactor to remove this hack
int index = !has_home || (current_destination && current_destination->isFavorite() && current_destination->label() == NAV_FAVORITE_LABEL_HOME) ? 0 : 1; int index = !has_home || (current_destination["save_type"] == NAV_TYPE_FAVORITE && current_destination["label"] == NAV_FAVORITE_LABEL_HOME) ? 0 : 1;
destinations_layout->insertWidget(index, widget); destinations_layout->insertWidget(index, widget);
} }
destinations_layout->addStretch(); destinations_layout->addStretch();
setUpdatesEnabled(true);
} }
void MapSettings::navigateTo(const QJsonObject &place) { void MapSettings::navigateTo(const QJsonObject &place) {
@ -263,23 +225,23 @@ DestinationWidget::DestinationWidget(QWidget *parent) : QPushButton(parent) {
)"); )");
} }
void DestinationWidget::set(NavDestination *destination, bool current) { void DestinationWidget::set(const QJsonObject &destination, bool current) {
setProperty("current", current); setProperty("current", current);
setProperty("set", true); setProperty("set", true);
auto icon_pixmap = current ? icons().directions : icons().recent; auto icon_pixmap = current ? icons().directions : icons().recent;
auto title_text = destination->name(); auto title_text = destination["place_name"].toString();
auto subtitle_text = destination->details(); auto subtitle_text = destination["place_details"].toString();
if (destination->isFavorite()) { if (destination["save_type"] == NAV_TYPE_FAVORITE) {
if (destination->label() == NAV_FAVORITE_LABEL_HOME) { if (destination["label"] == NAV_FAVORITE_LABEL_HOME) {
icon_pixmap = icons().home; icon_pixmap = icons().home;
subtitle_text = title_text + ", " + subtitle_text;
title_text = tr("Home"); title_text = tr("Home");
subtitle_text = destination->name() + ", " + destination->details(); } else if (destination["label"] == NAV_FAVORITE_LABEL_WORK) {
} else if (destination->label() == NAV_FAVORITE_LABEL_WORK) {
icon_pixmap = icons().work; icon_pixmap = icons().work;
subtitle_text = title_text + ", " + subtitle_text;
title_text = tr("Work"); title_text = tr("Work");
subtitle_text = destination->name() + ", " + destination->details();
} else { } else {
icon_pixmap = icons().favorite; icon_pixmap = icons().favorite;
} }
@ -332,9 +294,8 @@ NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) {
// Fetch favorite and recent locations // Fetch favorite and recent locations
QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations"; QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations";
RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true); RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true);
QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavigationRequest::locationsUpdated); QObject::connect(repeater, &RequestRepeater::requestDone, this, &NavigationRequest::parseLocationsResponse);
} }
{ {
// Destination set while offline // Destination set while offline
QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/next"; QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/next";
@ -358,3 +319,29 @@ NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) {
} }
} }
} }
static void swap(QJsonValueRef v1, QJsonValueRef v2) { std::swap(v1, v2); }
void NavigationRequest::parseLocationsResponse(const QString &response, bool success) {
if (!success || response == prev_response) return;
prev_response = response;
QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8());
if (doc.isNull()) {
qWarning() << "JSON Parse failed on navigation locations" << response;
return;
}
// Sort: HOME, WORK, alphabetical FAVORITES, and then most recent (as returned by API)
QJsonArray locations = doc.array();
std::stable_sort(locations.begin(), locations.end(), [](const QJsonValue &a, const QJsonValue &b) {
if (a["save_type"] == NAV_TYPE_FAVORITE || b["save_type"] == NAV_TYPE_FAVORITE) {
QString a_label = a["label"].toString(), b_label = b["label"].toString();
return std::tuple(a["save_type"].toString(), (a_label.isEmpty() ? "xxx" : a_label), a["place_name"].toString()) <
std::tuple(b["save_type"].toString(), (b_label.isEmpty() ? "xxx" : b_label), b["place_name"].toString());
} else {
return false;
}
});
emit locationsUpdated(locations);
}

@ -1,7 +1,5 @@
#pragma once #pragma once
#include <memory>
#include <QFrame> #include <QFrame>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
@ -20,7 +18,6 @@ const QString NAV_TYPE_RECENT = "recent";
const QString NAV_FAVORITE_LABEL_HOME = "home"; const QString NAV_FAVORITE_LABEL_HOME = "home";
const QString NAV_FAVORITE_LABEL_WORK = "work"; const QString NAV_FAVORITE_LABEL_WORK = "work";
class NavDestination;
class DestinationWidget; class DestinationWidget;
class NavigationRequest : public QObject { class NavigationRequest : public QObject {
@ -30,13 +27,15 @@ public:
static NavigationRequest *instance(); static NavigationRequest *instance();
signals: signals:
void locationsUpdated(const QString &response, bool success); void locationsUpdated(const QJsonArray &locations);
void nextDestinationUpdated(const QString &response, bool success); void nextDestinationUpdated(const QString &response, bool success);
private: private:
NavigationRequest(QObject *parent); NavigationRequest(QObject *parent);
void parseLocationsResponse(const QString &response, bool success);
Params params; Params params;
QString prev_response;
}; };
class MapSettings : public QFrame { class MapSettings : public QFrame {
@ -45,7 +44,7 @@ public:
explicit MapSettings(bool closeable = false, QWidget *parent = nullptr); explicit MapSettings(bool closeable = false, QWidget *parent = nullptr);
void navigateTo(const QJsonObject &place); void navigateTo(const QJsonObject &place);
void parseResponse(const QString &response, bool success); void updateLocations(const QJsonArray &locations);
void updateCurrentRoute(); void updateCurrentRoute();
private: private:
@ -54,64 +53,21 @@ private:
void refresh(); void refresh();
Params params; Params params;
QString cur_destinations; QJsonArray current_locations;
QJsonObject current_destination;
QVBoxLayout *destinations_layout; QVBoxLayout *destinations_layout;
std::unique_ptr<NavDestination> current_destination;
DestinationWidget *current_widget; DestinationWidget *current_widget;
QPixmap close_icon; QPixmap close_icon;
signals: signals:
void closeSettings(); void closeSettings();
}; };
class NavDestination {
public:
explicit NavDestination(const QJsonObject &place)
: type_(place["save_type"].toString()), label_(place["label"].toString()),
name_(place["place_name"].toString()), details_(place["place_details"].toString()),
latitude_(place["latitude"].toDouble()), longitude_(place["longitude"].toDouble()) {
// if details starts with `name, ` remove it
if (details_.startsWith(name_ + ", ")) {
details_ = details_.mid(name_.length() + 2);
}
}
QString type() const { return type_; }
QString label() const { return label_; }
QString name() const { return name_; }
QString details() const { return details_; }
bool isFavorite() const { return type_ == NAV_TYPE_FAVORITE; }
bool isRecent() const { return type_ == NAV_TYPE_RECENT; }
bool operator==(const NavDestination &rhs) {
return type_ == rhs.type_ && label_ == rhs.label_ && name_ == rhs.name_ &&
details_ == rhs.details_ && latitude_ == rhs.latitude_ && longitude_ == rhs.longitude_;
}
QJsonObject toJson() const {
QJsonObject obj;
obj["save_type"] = type_;
obj["label"] = label_;
obj["place_name"] = name_;
obj["place_details"] = details_;
obj["latitude"] = latitude_;
obj["longitude"] = longitude_;
return obj;
}
private:
QString type_, label_, name_, details_;
double latitude_, longitude_;
};
class DestinationWidget : public QPushButton { class DestinationWidget : public QPushButton {
Q_OBJECT Q_OBJECT
public: public:
explicit DestinationWidget(QWidget *parent = nullptr); explicit DestinationWidget(QWidget *parent = nullptr);
void set(const QJsonObject &location, bool current = false);
void set(NavDestination *, bool current = false);
void unset(const QString &label, bool current = false); void unset(const QString &label, bool current = false);
signals: signals:

@ -5,7 +5,7 @@ RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, con
timer = new QTimer(this); timer = new QTimer(this);
timer->setTimerType(Qt::VeryCoarseTimer); timer->setTimerType(Qt::VeryCoarseTimer);
QObject::connect(timer, &QTimer::timeout, [=]() { QObject::connect(timer, &QTimer::timeout, [=]() {
if ((!uiState()->scene.started || while_onroad) && uiState()->awake && !active()) { if ((!uiState()->scene.started || while_onroad) && device()->isAwake() && !active()) {
sendRequest(requestURL); sendRequest(requestURL);
} }
}); });

@ -38,7 +38,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
closeSettings(); closeSettings();
} }
}); });
QObject::connect(&device, &Device::interactiveTimout, [=]() { QObject::connect(device(), &Device::interactiveTimeout, [=]() {
if (main_layout->currentWidget() == settingsWindow) { if (main_layout->currentWidget() == settingsWindow) {
closeSettings(); closeSettings();
} }
@ -90,9 +90,9 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
case QEvent::TouchEnd: case QEvent::TouchEnd:
case QEvent::MouseButtonPress: case QEvent::MouseButtonPress:
case QEvent::MouseMove: { case QEvent::MouseMove: {
// ignore events when device is awakened by resetInteractiveTimout // ignore events when device is awakened by resetInteractiveTimeout
ignore = !uiState()->awake; ignore = !device()->isAwake();
device.resetInteractiveTimout(); device()->resetInteractiveTimeout();
break; break;
} }
default: default:

@ -18,8 +18,6 @@ private:
void openSettings(int index = 0, const QString &param = ""); void openSettings(int index = 0, const QString &param = "");
void closeSettings(); void closeSettings();
Device device;
QStackedLayout *main_layout; QStackedLayout *main_layout;
HomeWindow *homeWindow; HomeWindow *homeWindow;
SettingsWindow *settingsWindow; SettingsWindow *settingsWindow;

@ -275,7 +275,7 @@ void UIState::setPrimeType(int type) {
Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) {
setAwake(true); setAwake(true);
resetInteractiveTimout(); resetInteractiveTimeout();
QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update);
} }
@ -283,9 +283,6 @@ Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT
void Device::update(const UIState &s) { void Device::update(const UIState &s) {
updateBrightness(s); updateBrightness(s);
updateWakefulness(s); updateWakefulness(s);
// TODO: remove from UIState and use signals
uiState()->awake = awake;
} }
void Device::setAwake(bool on) { void Device::setAwake(bool on) {
@ -297,7 +294,7 @@ void Device::setAwake(bool on) {
} }
} }
void Device::resetInteractiveTimout() { void Device::resetInteractiveTimeout() {
interactive_timeout = (ignition_on ? 10 : 30) * UI_FREQ; interactive_timeout = (ignition_on ? 10 : 30) * UI_FREQ;
} }
@ -335,9 +332,9 @@ void Device::updateWakefulness(const UIState &s) {
ignition_on = s.scene.ignition; ignition_on = s.scene.ignition;
if (ignition_just_turned_off) { if (ignition_just_turned_off) {
resetInteractiveTimout(); resetInteractiveTimeout();
} else if (interactive_timeout > 0 && --interactive_timeout == 0) { } else if (interactive_timeout > 0 && --interactive_timeout == 0) {
emit interactiveTimout(); emit interactiveTimeout();
} }
setAwake(s.scene.ignition || interactive_timeout > 0); setAwake(s.scene.ignition || interactive_timeout > 0);
@ -347,3 +344,8 @@ UIState *uiState() {
static UIState ui_state; static UIState ui_state;
return &ui_state; return &ui_state;
} }
Device *device() {
static Device _device;
return &_device;
}

@ -161,7 +161,6 @@ public:
UIStatus status; UIStatus status;
UIScene scene = {}; UIScene scene = {};
bool awake;
QString language; QString language;
QTransform car_space_transform; QTransform car_space_transform;
@ -188,6 +187,7 @@ class Device : public QObject {
public: public:
Device(QObject *parent = 0); Device(QObject *parent = 0);
bool isAwake() { return awake; }
private: private:
bool awake = false; bool awake = false;
@ -203,13 +203,15 @@ private:
signals: signals:
void displayPowerChanged(bool on); void displayPowerChanged(bool on);
void interactiveTimout(); void interactiveTimeout();
public slots: public slots:
void resetInteractiveTimout(); void resetInteractiveTimeout();
void update(const UIState &s); void update(const UIState &s);
}; };
Device *device();
void ui_update_params(UIState *s); void ui_update_params(UIState *s);
int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height); int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height);
void update_model(UIState *s, void update_model(UIState *s,

@ -8,6 +8,7 @@ import time
import pycurl import pycurl
import subprocess import subprocess
from datetime import datetime from datetime import datetime
from multiprocessing import Process
from typing import NoReturn, Optional from typing import NoReturn, Optional
from struct import unpack_from, calcsize, pack from struct import unpack_from, calcsize, pack
@ -29,6 +30,8 @@ from system.sensord.rawgps.structs import (dict_unpacker, position_report, relis
LOG_GNSS_OEMDRE_SVPOLY_REPORT) LOG_GNSS_OEMDRE_SVPOLY_REPORT)
DEBUG = int(os.getenv("DEBUG", "0"))==1 DEBUG = int(os.getenv("DEBUG", "0"))==1
ASSIST_DATA_FILE = '/tmp/xtra3grc.bin'
ASSISTANCE_URL = 'http://xtrapath3.izatcloud.net/xtra3grc.bin'
LOG_TYPES = [ LOG_TYPES = [
LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GPS_MEASUREMENT_REPORT,
@ -84,7 +87,7 @@ measurementStatusGlonassFields = {
def try_setup_logs(diag, log_types): def try_setup_logs(diag, log_types):
for _ in range(5): for _ in range(3):
try: try:
setup_logs(diag, log_types) setup_logs(diag, log_types)
break break
@ -94,11 +97,12 @@ def try_setup_logs(diag, log_types):
raise Exception(f"setup logs failed, {log_types=}") raise Exception(f"setup logs failed, {log_types=}")
def at_cmd(cmd: str) -> Optional[str]: def at_cmd(cmd: str) -> Optional[str]:
for _ in range(5): for _ in range(3):
try: try:
return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8')
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
cloudlog.exception("rawgps.mmcli_command_failed") cloudlog.exception("rawgps.mmcli_command_failed")
time.sleep(1.0)
raise Exception(f"failed to execute mmcli command {cmd=}") raise Exception(f"failed to execute mmcli command {cmd=}")
@ -109,53 +113,45 @@ def gps_enabled() -> bool:
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
raise Exception("failed to execute QGPS mmcli command") from exc raise Exception("failed to execute QGPS mmcli command") from exc
def download_and_inject_assistance(): def download_assistance():
assist_data_file = '/tmp/xtra3grc.bin'
assistance_url = 'http://xtrapath3.izatcloud.net/xtra3grc.bin'
try: try:
# download assistance c = pycurl.Curl()
try: c.setopt(pycurl.URL, ASSISTANCE_URL)
c.setopt(pycurl.NOBODY, 1)
c.setopt(pycurl.CONNECTTIMEOUT, 2)
c.perform()
bytes_n = c.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD)
c.close()
if bytes_n > 1e5:
cloudlog.error("Qcom assistance data larger than expected")
return
with open(ASSIST_DATA_FILE, 'wb') as fp:
c = pycurl.Curl() c = pycurl.Curl()
c.setopt(pycurl.URL, assistance_url) c.setopt(pycurl.URL, ASSISTANCE_URL)
c.setopt(pycurl.NOBODY, 1) c.setopt(pycurl.CONNECTTIMEOUT, 5)
c.setopt(pycurl.CONNECTTIMEOUT, 2)
c.setopt(pycurl.WRITEDATA, fp)
c.perform() c.perform()
bytes_n = c.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD)
c.close() c.close()
if bytes_n > 1e5: except pycurl.error:
cloudlog.error("Qcom assistance data larger than expected") cloudlog.exception("Failed to download assistance file")
return return
with open(assist_data_file, 'wb') as fp:
c = pycurl.Curl()
c.setopt(pycurl.URL, assistance_url)
c.setopt(pycurl.CONNECTTIMEOUT, 5)
c.setopt(pycurl.WRITEDATA, fp)
c.perform()
c.close()
except pycurl.error:
cloudlog.exception("Failed to download assistance file")
return
# inject into module
try:
cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={assist_data_file}"
subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True)
cloudlog.info("successfully loaded assistance data")
except subprocess.CalledProcessError as e:
cloudlog.event(
"rawgps.assistance_loading_failed",
error=True,
cmd=e.cmd,
output=e.output,
returncode=e.returncode
)
finally:
if os.path.exists(assist_data_file):
os.remove(assist_data_file)
def inject_assistance():
try:
cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}"
subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True)
cloudlog.info("successfully loaded assistance data")
except subprocess.CalledProcessError as e:
cloudlog.event(
"rawgps.assistance_loading_failed",
error=True,
cmd=e.cmd,
output=e.output,
returncode=e.returncode
)
def setup_quectel(diag: ModemDiag): def setup_quectel(diag: ModemDiag):
# enable OEMDRE in the NV # enable OEMDRE in the NV
@ -180,7 +176,8 @@ def setup_quectel(diag: ModemDiag):
# Do internet assistance # Do internet assistance
at_cmd("AT+QGPSXTRA=1") at_cmd("AT+QGPSXTRA=1")
at_cmd("AT+QGPSSUPLURL=\"NULL\"") at_cmd("AT+QGPSSUPLURL=\"NULL\"")
download_and_inject_assistance() if os.path.exists(ASSIST_DATA_FILE):
inject_assistance()
#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")
@ -214,6 +211,15 @@ def teardown_quectel(diag):
try_setup_logs(diag, []) try_setup_logs(diag, [])
def wait_for_modem():
cloudlog.warning("waiting for modem to come up")
while True:
ret = subprocess.call("mmcli -m any --timeout 10 --command=\"AT+QGPS?\"", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
if ret == 0:
return
time.sleep(0.1)
def main() -> NoReturn: def main() -> NoReturn:
unpack_gps_meas, size_gps_meas = dict_unpacker(gps_measurement_report, True) unpack_gps_meas, size_gps_meas = dict_unpacker(gps_measurement_report, True)
unpack_gps_meas_sv, size_gps_meas_sv = dict_unpacker(gps_measurement_report_sv, True) unpack_gps_meas_sv, size_gps_meas_sv = dict_unpacker(gps_measurement_report_sv, True)
@ -229,28 +235,25 @@ def main() -> NoReturn:
unpack_position, _ = dict_unpacker(position_report) unpack_position, _ = dict_unpacker(position_report)
# wait for ModemManager to come up wait_for_modem()
cloudlog.warning("waiting for modem to come up")
while True:
ret = subprocess.call("mmcli -m any --timeout 10 --command=\"AT+QGPS?\"", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
if ret == 0:
break
time.sleep(0.1)
# connect to modem assist_fetch_proc = None
diag = ModemDiag() def cleanup(proc):
cloudlog.warning("caught sig disabling quectel gps")
def cleanup(sig, frame):
cloudlog.warning(f"caught sig {sig}, disabling quectel gps")
gpio_set(GPIO.UBLOX_PWR_EN, False) gpio_set(GPIO.UBLOX_PWR_EN, False)
teardown_quectel(diag) teardown_quectel(diag)
cloudlog.warning("quectel cleanup done") cloudlog.warning("quectel cleanup done")
sys.exit(0) sys.exit(0)
signal.signal(signal.SIGINT, cleanup) signal.signal(signal.SIGINT, lambda sig, frame: cleanup(assist_fetch_proc))
signal.signal(signal.SIGTERM, cleanup) signal.signal(signal.SIGTERM, lambda sig, frame: cleanup(assist_fetch_proc))
# connect to modem
diag = ModemDiag()
download_assistance()
want_assistance = not os.path.exists(ASSIST_DATA_FILE)
setup_quectel(diag) setup_quectel(diag)
current_gps_time = utc_to_gpst(GPSTime.from_datetime(datetime.utcnow())) current_gps_time = utc_to_gpst(GPSTime.from_datetime(datetime.utcnow()))
last_fetch_time = time.monotonic()
cloudlog.warning("quectel setup done") cloudlog.warning("quectel setup done")
gpio_init(GPIO.UBLOX_PWR_EN, True) gpio_init(GPIO.UBLOX_PWR_EN, True)
gpio_set(GPIO.UBLOX_PWR_EN, True) gpio_set(GPIO.UBLOX_PWR_EN, True)
@ -258,6 +261,20 @@ 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 want_assistance:
setup_quectel(diag)
want_assistance = False
else:
os.remove(ASSIST_DATA_FILE)
if want_assistance and time.monotonic() - last_fetch_time > 10:
if assist_fetch_proc is None or not assist_fetch_proc.is_alive(): # type: ignore
cloudlog.warning("fetching assistance data")
assist_fetch_proc = Process(target=download_assistance)
assist_fetch_proc.start()
last_fetch_time = time.monotonic()
opcode, payload = diag.recv() opcode, payload = diag.recv()
if opcode != DIAG_LOG_F: if opcode != DIAG_LOG_F:
cloudlog.error(f"Unhandled opcode: {opcode}") cloudlog.error(f"Unhandled opcode: {opcode}")
@ -345,6 +362,8 @@ def main() -> NoReturn:
gps.speedAccuracy = math.sqrt(sum([x**2 for x in vNEDsigma])) gps.speedAccuracy = math.sqrt(sum([x**2 for x in vNEDsigma]))
# quectel gps verticalAccuracy is clipped to 500, set invalid if so # quectel gps verticalAccuracy is clipped to 500, set invalid if so
gps.flags = 1 if gps.verticalAccuracy != 500 else 0 gps.flags = 1 if gps.verticalAccuracy != 500 else 0
if gps.flags:
want_assistance = False
pm.send('gpsLocation', msg) pm.send('gpsLocation', msg)

@ -9,68 +9,61 @@ import numpy as np
import cereal.messaging as messaging import cereal.messaging as messaging
from system.hardware import TICI from system.hardware import TICI
from system.sensord.rawgps.rawgpsd import at_cmd from system.sensord.rawgps.rawgpsd import at_cmd, wait_for_modem
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
from common.transformations.coordinates import ecef_from_geodetic from common.transformations.coordinates import ecef_from_geodetic
GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0'))) GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0')))
UPDATE_MS = 100
UPDATES_PER_S = 1000//UPDATE_MS
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'])
@classmethod
def tearDownClass(cls):
managed_processes['rawgpsd'].stop()
os.system("sudo systemctl restart systemd-resolved")
os.system("sudo systemctl restart ModemManager lte")
cls.sm_qcom_gnss = messaging.SubMaster(['qcomGnss']) def setUp(self):
cls.sm_gps_location = messaging.SubMaster(['gpsLocation']) at_cmd("AT+QGPSDEL=0")
cls.sm_gnss_measurements = messaging.SubMaster(['gnssMeasurements'])
def tearDown(self): def tearDown(self):
managed_processes['rawgpsd'].stop() managed_processes['rawgpsd'].stop()
os.system("sudo systemctl restart systemd-resolved")
def _wait_for_output(self, t=10): def _wait_for_output(self, t=10):
self.sm_qcom_gnss.update(0) time.sleep(t)
for __ in range(t*UPDATES_PER_S): self.sm.update()
self.sm_qcom_gnss.update(UPDATE_MS)
if self.sm_qcom_gnss.updated['qcomGnss']: def test_no_crash_double_command(self):
return True at_cmd("AT+QGPSDEL=0")
at_cmd("AT+QGPSDEL=0")
def _wait_for_location(self, t=10):
self.sm_gps_location.update(0)
for __ in range(t*UPDATES_PER_S):
self.sm_gps_location.update(UPDATE_MS)
if self.sm_gps_location.updated['gpsLocation'] and self.sm_gps_location['gpsLocation'].flags:
return True
return False
def _wait_for_laikad_location(self, t=10):
self.sm_gnss_measurements.update(0)
for __ in range(t*UPDATES_PER_S):
self.sm_gnss_measurements.update(UPDATE_MS)
if self.sm_gnss_measurements.updated['gnssMeasurements'] and self.sm_gnss_measurements['gnssMeasurements'].positionECEF.valid:
return True
return False
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 lte")
managed_processes['rawgpsd'].start() managed_processes['rawgpsd'].start()
assert not self._wait_for_output(10) self._wait_for_output(10)
assert not self.sm.updated['qcomGnss']
os.system("sudo systemctl restart ModemManager lte") os.system("sudo systemctl restart ModemManager lte")
assert self._wait_for_output(30) self._wait_for_output(30)
assert self.sm.updated['qcomGnss']
def test_startup_time(self): def test_startup_time(self):
for _ in range(5): for i in range(2):
if i == 1:
os.system("sudo systemctl stop systemd-resolved")
managed_processes['rawgpsd'].start() managed_processes['rawgpsd'].start()
self._wait_for_output(7)
start_time = time.monotonic() assert self.sm.updated['qcomGnss']
assert self._wait_for_output(), "rawgpsd didn't start outputting messages in time"
et = time.monotonic() - start_time
assert et < 7, f"rawgpsd took {et:.1f}s to start"
managed_processes['rawgpsd'].stop() managed_processes['rawgpsd'].stop()
def test_turns_off_gnss(self): def test_turns_off_gnss(self):
@ -83,38 +76,63 @@ class TestRawgpsd(unittest.TestCase):
loc_status = json.loads(ls) loc_status = json.loads(ls)
assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'} assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'}
def test_assistance_loading(self):
# clear assistance data
at_cmd("AT+QGPSDEL=0")
managed_processes['rawgpsd'].start()
assert self._wait_for_output(10)
managed_processes['rawgpsd'].stop()
def check_assistance(self, should_be_loaded):
# after QGPSDEL: '+QGPSXTRADATA: 0,"1980/01/05,19:00:00"' # after QGPSDEL: '+QGPSXTRADATA: 0,"1980/01/05,19:00:00"'
# after loading: '+QGPSXTRADATA: 10080,"2023/06/24,19:00:00"' # after loading: '+QGPSXTRADATA: 10080,"2023/06/24,19:00:00"'
out = at_cmd("AT+QGPSXTRADATA?") out = at_cmd("AT+QGPSXTRADATA?")
out = out.split("+QGPSXTRADATA:")[1].split("'")[0].strip() out = out.split("+QGPSXTRADATA:")[1].split("'")[0].strip()
valid_duration, injected_time_str = out.split(",", 1) valid_duration, injected_time_str = out.split(",", 1)
assert valid_duration == "10080" # should be max time if should_be_loaded:
injected_time = datetime.datetime.strptime(injected_time_str.replace("\"", ""), "%Y/%m/%d,%H:%M:%S") assert valid_duration == "10080" # should be max time
self.assertLess(abs((datetime.datetime.utcnow() - injected_time).total_seconds()), 60*60*12) injected_time = datetime.datetime.strptime(injected_time_str.replace("\"", ""), "%Y/%m/%d,%H:%M:%S")
self.assertLess(abs((datetime.datetime.utcnow() - injected_time).total_seconds()), 60*60*12)
else:
valid_duration, injected_time_str = out.split(",", 1)
injected_time_str = injected_time_str.replace('\"', '').replace('\'', '')
assert injected_time_str[:] == '1980/01/05,19:00:00'[:]
assert valid_duration == '0'
def test_assistance_loading(self):
managed_processes['rawgpsd'].start()
self._wait_for_output(10)
assert self.sm.updated['qcomGnss']
managed_processes['rawgpsd'].stop()
self.check_assistance(True)
def test_no_assistance_loading(self):
os.system("sudo systemctl stop systemd-resolved")
managed_processes['rawgpsd'].start()
self._wait_for_output(10)
assert self.sm.updated['qcomGnss']
managed_processes['rawgpsd'].stop()
self.check_assistance(False)
def test_late_assistance_loading(self):
os.system("sudo systemctl stop systemd-resolved")
managed_processes['rawgpsd'].start()
self._wait_for_output(17)
assert self.sm.updated['qcomGnss']
os.system("sudo systemctl restart systemd-resolved")
self._wait_for_output(15)
managed_processes['rawgpsd'].stop()
self.check_assistance(True)
@unittest.skipIf(not GOOD_SIGNAL, "No good GPS signal") @unittest.skipIf(not GOOD_SIGNAL, "No good GPS signal")
def test_fix(self): def test_fix(self):
# clear assistance data
at_cmd("AT+QGPSDEL=0")
managed_processes['rawgpsd'].start() managed_processes['rawgpsd'].start()
managed_processes['laikad'].start() managed_processes['laikad'].start()
assert self._wait_for_location(120) assert self._wait_for_output(60)
assert self.sm_gps_location['gpsLocation'].flags == 1 assert self.sm.updated['qcomGnss']
module_fix = ecef_from_geodetic([self.sm_gps_location['gpsLocation'].latitude, assert self.sm.updated['gpsLocation']
self.sm_gps_location['gpsLocation'].longitude, assert self.sm['gpsLocation'].flags == 1
self.sm_gps_location['gpsLocation'].altitude]) module_fix = ecef_from_geodetic([self.sm['gpsLocation'].latitude,
assert self._wait_for_laikad_location(90) self.sm['gpsLocation'].longitude,
total_diff = np.array(self.sm_gnss_measurements['gnssMeasurements'].positionECEF.value) - module_fix self.sm['gpsLocation'].altitude])
print(total_diff) assert self.sm['gnssMeasurements'].positionECEF.valid
total_diff = np.array(self.sm['gnssMeasurements'].positionECEF.value) - module_fix
self.assertLess(np.linalg.norm(total_diff), 100) self.assertLess(np.linalg.norm(total_diff), 100)
managed_processes['laikad'].stop() managed_processes['laikad'].stop()
managed_processes['rawgpsd'].stop() managed_processes['rawgpsd'].stop()

Loading…
Cancel
Save