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
system/proclogd/proclogd
selfdrive/ui/_ui
selfdrive/ui/translations/alerts_generated.h
selfdrive/test/longitudinal_maneuvers/out
selfdrive/car/tests/cars_dump
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.
# 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|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@ -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 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 (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|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>||

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

15
poetry.lock generated

@ -4280,14 +4280,6 @@ pure-eval = "*"
[package.extras]
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]]
name = "sympy"
version = "1.11.1"
@ -4921,7 +4913,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "1.1"
python-versions = "~3.8"
content-hash = "e6fcadbd1083d80b2e70d287b927d897e1a3d4f4907f4e5443f4d4b23ac02d89"
content-hash = "f296825a07c5536c82529833a60996ffd5ae8c3e3537d1da1f9b26150b3d899d"
[metadata.files]
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.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 = [
{file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"},
{file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"},

@ -100,7 +100,6 @@ scipy = "^1.8.1"
sphinx = "^5.0.2"
sphinx-rtd-theme = "^1.0.0"
sphinx-sitemap = "^2.2.0"
subprocess32 = "^3.5.4"
tabulate = "^0.8.10"
tenacity = "^8.0.1"
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 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.
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]
CAN = CanBus(None, hda2, fingerprint)
@ -186,10 +186,10 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.9
ret.steerRatio = 16.
tire_stiffness_factor = 0.65
elif candidate == CAR.IONIQ_5:
ret.mass = 2012 + STD_CARGO_KG
ret.wheelbase = 3.0
ret.steerRatio = 16.
elif candidate in (CAR.IONIQ_5, CAR.IONIQ_6):
ret.mass = 1948 + STD_CARGO_KG
ret.wheelbase = 2.97
ret.steerRatio = 14.26
tire_stiffness_factor = 0.65
elif candidate == CAR.KIA_SPORTAGE_HYBRID_5TH_GEN:
ret.mass = 1767. + STD_CARGO_KG # SX Prestige trim support only

@ -92,6 +92,7 @@ class CAR:
VELOSTER = "HYUNDAI VELOSTER 2019"
SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021"
IONIQ_5 = "HYUNDAI IONIQ 5 2022"
IONIQ_6 = "HYUNDAI IONIQ 6 2023"
TUCSON_4TH_GEN = "HYUNDAI TUCSON 4TH GEN"
TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH 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 (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: [
HyundaiCarInfo("Hyundai Tucson 2022", 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: [
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
@ -1766,6 +1771,14 @@ FW_VERSIONS = {
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: {
(Ecu.fwdCamera, 0x7c4, None): [
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: {
(Ecu.fwdCamera, 0x7c4, None): [
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): [
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},
}
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
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, }
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
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_HYBRID_4TH_GEN: 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.KIA_SPORTAGE_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.EQUINOX,
HYUNDAI.GENESIS_G90,
HYUNDAI.IONIQ_6,
HYUNDAI.KIA_OPTIMA_H,
HONDA.ODYSSEY_CHN,
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("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("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("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),

@ -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 2020-2022: 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 HYBRID 2021: HYUNDAI SONATA 2020
HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019

@ -373,7 +373,7 @@ class Controls:
else:
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)
if not self.sm['lateralPlan'].mpcSolutionValid:
self.events.add(EventName.plannerError)
@ -411,7 +411,7 @@ class Controls:
pass
# TODO: fix simulator
if not SIMULATION:
if not SIMULATION or REPLAY:
if not NOSENSOR:
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
@ -436,7 +436,7 @@ class Controls:
if not self.initialized:
all_valid = CS.canValid and self.sm.all_checks()
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)
if VisionStreamType.VISION_STREAM_ROAD not in available_streams:
self.sm.ignore_alive.append('roadCameraState')

@ -49,6 +49,7 @@ int main(int argc, char **argv) {
DMonitoringModelState model;
dmonitoring_init(&model);
LOGW("connecting to driver stream");
VisionIpcClient vipc_client = VisionIpcClient("camerad", VISION_STREAM_DRIVER, true);
while (!do_exit && !vipc_client.connect(false)) {
util::sleep_for(100);

@ -33,7 +33,7 @@ SNPEModel::SNPEModel(const std::string path, float *_output, size_t _output_size
// load model
std::unique_ptr<zdl::DlContainer::IDlContainer> container = zdl::DlContainer::IDlContainer::open((uint8_t*)model_data.data(), model_data.size());
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
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 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
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::UserBufferEncodingTf8 ub_encoding_tf8(0, 1./255); // network takes 0-1

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

@ -1 +1 @@
a683d689bd74ba5ba7c10bfad237aacc04b978c3
219a815856d8984cb4933d83db9a15bf7cd09f16

@ -6,7 +6,6 @@
#include "common/transformations/coordinates.hpp"
#include "selfdrive/ui/qt/maps/map_helpers.h"
#include "selfdrive/ui/qt/request_repeater.h"
#include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/ui.h"
@ -61,6 +60,11 @@ MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings),
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->addStretch(1);
overlay_layout->addWidget(settings_btn, Qt::AlignLeft);
@ -169,21 +173,15 @@ void MapWindow::updateState(const UIState &s) {
emit requestSettings(false);
}
if (m_map.isNull()) {
return;
}
loaded_once = loaded_once || m_map->isFullyLoaded();
loaded_once = loaded_once || (m_map && m_map->isFullyLoaded());
if (!loaded_once) {
map_instructions->showError(tr("Map Loading"));
setError(tr("Map Loading"));
return;
}
initLayers();
setError(locationd_valid ? "" : tr("Waiting for GPS"));
if (locationd_valid) {
map_instructions->noError();
// Update current location marker
auto point = coordinate_to_collection(*last_position);
QMapbox::Feature feature1(QMapbox::Feature::PointType, point, {}, {});
@ -191,8 +189,6 @@ void MapWindow::updateState(const UIState &s) {
carPosSource["type"] = "geojson";
carPosSource["data"] = QVariant::fromValue<QMapbox::Feature>(feature1);
m_map->updateSource("carPosSource", carPosSource);
} else {
map_instructions->showError(tr("Waiting for GPS"));
}
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) {
m_map->resize(size() / MAP_SCALE);
map_overlay->setFixedSize(width(), height());
@ -279,7 +283,7 @@ void MapWindow::clearRoute() {
updateDestinationMarker();
}
map_instructions->hideIfNoError();
map_instructions->setVisible(false);
map_eta->setVisible(false);
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");
QHBoxLayout *main_layout = new QHBoxLayout(this);
main_layout->setContentsMargins(11, 50, 11, 11);
{
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);
main_layout->addWidget(icon_01 = new QLabel, 0, Qt::AlignTop);
primary = new QLabel;
primary->setStyleSheet(R"(font-size: 60px;)");
primary->setWordWrap(true);
layout->addWidget(primary);
QWidget *right_container = new QWidget(this);
right_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QVBoxLayout *layout = new QVBoxLayout(right_container);
secondary = new QLabel;
secondary->setStyleSheet(R"(font-size: 50px;)");
secondary->setWordWrap(true);
layout->addWidget(secondary);
layout->addWidget(distance = new QLabel);
distance->setStyleSheet(R"(font-size: 90px;)");
lane_widget = new QWidget;
lane_widget->setFixedHeight(125);
layout->addWidget(primary = new QLabel);
primary->setStyleSheet(R"(font-size: 60px;)");
primary->setWordWrap(true);
lane_layout = new QHBoxLayout(lane_widget);
layout->addWidget(lane_widget);
layout->addWidget(secondary = new QLabel);
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");
QPalette pal = palette();
pal.setColor(QPalette::Background, QColor(0, 0, 0, 150));
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) {
setUpdatesEnabled(false);
@ -464,7 +435,6 @@ void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruct
primary->setText(primary_str);
secondary->setVisible(secondary_str.length() > 0);
secondary->setText(secondary_str);
distance->setAlignment(Qt::AlignLeft);
distance->setText(getDistance(instruction.getManeuverDistance()));
// 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) {
lane_labels[i]->setVisible(false);
}
lane_widget->setVisible(lanes.size() > 0);
setUpdatesEnabled(true);
setVisible(true);
}
void MapInstructions::hideIfNoError() {
if (!error) {
hide();
}
}
MapETA::MapETA(QWidget *parent) : QWidget(parent) {
setVisible(false);
setAttribute(Qt::WA_TranslucentBackground);

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

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

@ -2,7 +2,6 @@
#include <QApplication>
#include <QDebug>
#include <vector>
#include "common/util.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;
}
MapSettings::MapSettings(bool closeable, QWidget *parent)
: QFrame(parent), current_destination(nullptr) {
QSize icon_size(100, 100);
close_icon = loadPixmap("../assets/icons/close.svg", icon_size);
MapSettings::MapSettings(bool closeable, QWidget *parent) : QFrame(parent) {
close_icon = loadPixmap("../assets/icons/close.svg", {100, 100});
setContentsMargins(0, 0, 0, 0);
auto *frame = new QVBoxLayout(this);
@ -68,7 +64,7 @@ MapSettings::MapSettings(bool closeable, QWidget *parent)
current_widget = new DestinationWidget(this);
QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() {
if (!current_destination) return;
if (current_destination.empty()) return;
params.remove("NavDestination");
updateCurrentRoute();
});
@ -85,7 +81,7 @@ MapSettings::MapSettings(bool closeable, QWidget *parent)
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);
}
@ -106,72 +102,37 @@ void MapSettings::updateCurrentRoute() {
qWarning() << "JSON Parse failed on NavDestination" << dest;
return;
}
auto destination = std::make_unique<NavDestination>(doc.object());
if (current_destination && *destination == *current_destination) return;
current_destination = std::move(destination);
current_widget->set(current_destination.get(), true);
current_destination = doc.object();
current_widget->set(current_destination, true);
} else {
current_destination.reset(nullptr);
current_destination = {};
current_widget->unset("", true);
}
if (isVisible()) refresh();
}
void MapSettings::parseResponse(const QString &response, bool success) {
if (!success || response == cur_destinations) return;
cur_destinations = response;
void MapSettings::updateLocations(const QJsonArray &locations) {
current_locations = locations;
refresh();
}
void MapSettings::refresh() {
bool has_home = false, has_work = 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));
}
}
setUpdatesEnabled(false);
// TODO: should we build a new layout and swap it in?
clearLayout(destinations_layout);
// Sort: HOME, WORK, alphabetical FAVORITES, and then most recent (as returned by API)
std::stable_sort(destinations.begin(), destinations.end(), [](const auto &a, const auto &b) {
if (a->isFavorite() && b->isFavorite()) {
if (a->label() == NAV_FAVORITE_LABEL_HOME) return true;
else if (b->label() == NAV_FAVORITE_LABEL_HOME) return false;
else if (a->label() == NAV_FAVORITE_LABEL_WORK) return true;
else if (b->label() == NAV_FAVORITE_LABEL_WORK) return false;
else return a->name() < b->name();
bool has_home = false, has_work = false;
for (auto location : current_locations) {
auto dest = location.toObject();
if (dest["save_type"].toString() == NAV_TYPE_FAVORITE) {
has_home = has_home || dest["label"].toString() == NAV_FAVORITE_LABEL_HOME;
has_work = has_work || dest["label"].toString() == NAV_FAVORITE_LABEL_WORK;
}
else if (a->isFavorite()) return true;
else if (b->isFavorite()) return false;
return false;
});
if (dest == current_destination) continue;
for (auto &destination : destinations) {
auto widget = new DestinationWidget(this);
widget->set(destination.get(), false);
QObject::connect(widget, &QPushButton::clicked, [this, dest = destination->toJson()]() {
widget->set(dest, false);
QObject::connect(widget, &QPushButton::clicked, [this, dest]() {
navigateTo(dest);
emit closeSettings();
});
@ -189,11 +150,12 @@ void MapSettings::refresh() {
auto widget = new DestinationWidget(this);
widget->unset(NAV_FAVORITE_LABEL_WORK);
// 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->addStretch();
setUpdatesEnabled(true);
}
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("set", true);
auto icon_pixmap = current ? icons().directions : icons().recent;
auto title_text = destination->name();
auto subtitle_text = destination->details();
auto title_text = destination["place_name"].toString();
auto subtitle_text = destination["place_details"].toString();
if (destination->isFavorite()) {
if (destination->label() == NAV_FAVORITE_LABEL_HOME) {
if (destination["save_type"] == NAV_TYPE_FAVORITE) {
if (destination["label"] == NAV_FAVORITE_LABEL_HOME) {
icon_pixmap = icons().home;
subtitle_text = title_text + ", " + subtitle_text;
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;
subtitle_text = title_text + ", " + subtitle_text;
title_text = tr("Work");
subtitle_text = destination->name() + ", " + destination->details();
} else {
icon_pixmap = icons().favorite;
}
@ -332,9 +294,8 @@ NavigationRequest::NavigationRequest(QObject *parent) : QObject(parent) {
// Fetch favorite and recent locations
QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations";
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
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
#include <memory>
#include <QFrame>
#include <QJsonArray>
#include <QJsonDocument>
@ -20,7 +18,6 @@ const QString NAV_TYPE_RECENT = "recent";
const QString NAV_FAVORITE_LABEL_HOME = "home";
const QString NAV_FAVORITE_LABEL_WORK = "work";
class NavDestination;
class DestinationWidget;
class NavigationRequest : public QObject {
@ -30,13 +27,15 @@ public:
static NavigationRequest *instance();
signals:
void locationsUpdated(const QString &response, bool success);
void locationsUpdated(const QJsonArray &locations);
void nextDestinationUpdated(const QString &response, bool success);
private:
NavigationRequest(QObject *parent);
void parseLocationsResponse(const QString &response, bool success);
Params params;
QString prev_response;
};
class MapSettings : public QFrame {
@ -45,7 +44,7 @@ public:
explicit MapSettings(bool closeable = false, QWidget *parent = nullptr);
void navigateTo(const QJsonObject &place);
void parseResponse(const QString &response, bool success);
void updateLocations(const QJsonArray &locations);
void updateCurrentRoute();
private:
@ -54,64 +53,21 @@ private:
void refresh();
Params params;
QString cur_destinations;
QJsonArray current_locations;
QJsonObject current_destination;
QVBoxLayout *destinations_layout;
std::unique_ptr<NavDestination> current_destination;
DestinationWidget *current_widget;
QPixmap close_icon;
signals:
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 {
Q_OBJECT
public:
explicit DestinationWidget(QWidget *parent = nullptr);
void set(NavDestination *, bool current = false);
void set(const QJsonObject &location, bool current = false);
void unset(const QString &label, bool current = false);
signals:

@ -5,7 +5,7 @@ RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, con
timer = new QTimer(this);
timer->setTimerType(Qt::VeryCoarseTimer);
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);
}
});

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

@ -18,8 +18,6 @@ private:
void openSettings(int index = 0, const QString &param = "");
void closeSettings();
Device device;
QStackedLayout *main_layout;
HomeWindow *homeWindow;
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) {
setAwake(true);
resetInteractiveTimout();
resetInteractiveTimeout();
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) {
updateBrightness(s);
updateWakefulness(s);
// TODO: remove from UIState and use signals
uiState()->awake = awake;
}
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;
}
@ -335,9 +332,9 @@ void Device::updateWakefulness(const UIState &s) {
ignition_on = s.scene.ignition;
if (ignition_just_turned_off) {
resetInteractiveTimout();
resetInteractiveTimeout();
} else if (interactive_timeout > 0 && --interactive_timeout == 0) {
emit interactiveTimout();
emit interactiveTimeout();
}
setAwake(s.scene.ignition || interactive_timeout > 0);
@ -347,3 +344,8 @@ UIState *uiState() {
static UIState ui_state;
return &ui_state;
}
Device *device() {
static Device _device;
return &_device;
}

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

@ -8,6 +8,7 @@ import time
import pycurl
import subprocess
from datetime import datetime
from multiprocessing import Process
from typing import NoReturn, Optional
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)
DEBUG = int(os.getenv("DEBUG", "0"))==1
ASSIST_DATA_FILE = '/tmp/xtra3grc.bin'
ASSISTANCE_URL = 'http://xtrapath3.izatcloud.net/xtra3grc.bin'
LOG_TYPES = [
LOG_GNSS_GPS_MEASUREMENT_REPORT,
@ -84,7 +87,7 @@ measurementStatusGlonassFields = {
def try_setup_logs(diag, log_types):
for _ in range(5):
for _ in range(3):
try:
setup_logs(diag, log_types)
break
@ -94,11 +97,12 @@ def try_setup_logs(diag, log_types):
raise Exception(f"setup logs failed, {log_types=}")
def at_cmd(cmd: str) -> Optional[str]:
for _ in range(5):
for _ in range(3):
try:
return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8')
except subprocess.CalledProcessError:
cloudlog.exception("rawgps.mmcli_command_failed")
time.sleep(1.0)
raise Exception(f"failed to execute mmcli command {cmd=}")
@ -109,53 +113,45 @@ def gps_enabled() -> bool:
except subprocess.CalledProcessError as exc:
raise Exception("failed to execute QGPS mmcli command") from exc
def download_and_inject_assistance():
assist_data_file = '/tmp/xtra3grc.bin'
assistance_url = 'http://xtrapath3.izatcloud.net/xtra3grc.bin'
def download_assistance():
try:
# download assistance
try:
c = pycurl.Curl()
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.setopt(pycurl.URL, assistance_url)
c.setopt(pycurl.NOBODY, 1)
c.setopt(pycurl.CONNECTTIMEOUT, 2)
c.setopt(pycurl.URL, ASSISTANCE_URL)
c.setopt(pycurl.CONNECTTIMEOUT, 5)
c.setopt(pycurl.WRITEDATA, fp)
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.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
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):
# enable OEMDRE in the NV
@ -180,7 +176,8 @@ def setup_quectel(diag: ModemDiag):
# Do internet assistance
at_cmd("AT+QGPSXTRA=1")
at_cmd("AT+QGPSSUPLURL=\"NULL\"")
download_and_inject_assistance()
if os.path.exists(ASSIST_DATA_FILE):
inject_assistance()
#at_cmd("AT+QGPSXTRADATA?")
time_str = datetime.utcnow().strftime("%Y/%m/%d,%H:%M:%S")
at_cmd(f"AT+QGPSXTRATIME=0,\"{time_str}\",1,1,1000")
@ -214,6 +211,15 @@ def teardown_quectel(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:
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)
@ -229,28 +235,25 @@ def main() -> NoReturn:
unpack_position, _ = dict_unpacker(position_report)
# wait for ModemManager to come up
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)
wait_for_modem()
# connect to modem
diag = ModemDiag()
def cleanup(sig, frame):
cloudlog.warning(f"caught sig {sig}, disabling quectel gps")
assist_fetch_proc = None
def cleanup(proc):
cloudlog.warning("caught sig disabling quectel gps")
gpio_set(GPIO.UBLOX_PWR_EN, False)
teardown_quectel(diag)
cloudlog.warning("quectel cleanup done")
sys.exit(0)
signal.signal(signal.SIGINT, cleanup)
signal.signal(signal.SIGTERM, cleanup)
signal.signal(signal.SIGINT, lambda sig, frame: cleanup(assist_fetch_proc))
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)
current_gps_time = utc_to_gpst(GPSTime.from_datetime(datetime.utcnow()))
last_fetch_time = time.monotonic()
cloudlog.warning("quectel setup done")
gpio_init(GPIO.UBLOX_PWR_EN, True)
gpio_set(GPIO.UBLOX_PWR_EN, True)
@ -258,6 +261,20 @@ def main() -> NoReturn:
pm = messaging.PubMaster(['qcomGnss', 'gpsLocation'])
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()
if opcode != DIAG_LOG_F:
cloudlog.error(f"Unhandled opcode: {opcode}")
@ -345,6 +362,8 @@ def main() -> NoReturn:
gps.speedAccuracy = math.sqrt(sum([x**2 for x in vNEDsigma]))
# quectel gps verticalAccuracy is clipped to 500, set invalid if so
gps.flags = 1 if gps.verticalAccuracy != 500 else 0
if gps.flags:
want_assistance = False
pm.send('gpsLocation', msg)

@ -9,68 +9,61 @@ import numpy as np
import cereal.messaging as messaging
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 common.transformations.coordinates import ecef_from_geodetic
GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0')))
UPDATE_MS = 100
UPDATES_PER_S = 1000//UPDATE_MS
class TestRawgpsd(unittest.TestCase):
@classmethod
def setUpClass(cls):
os.system("sudo systemctl restart systemd-resolved")
os.system("sudo systemctl restart ModemManager lte")
wait_for_modem()
if not TICI:
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'])
cls.sm_gps_location = messaging.SubMaster(['gpsLocation'])
cls.sm_gnss_measurements = messaging.SubMaster(['gnssMeasurements'])
def setUp(self):
at_cmd("AT+QGPSDEL=0")
def tearDown(self):
managed_processes['rawgpsd'].stop()
os.system("sudo systemctl restart systemd-resolved")
def _wait_for_output(self, t=10):
self.sm_qcom_gnss.update(0)
for __ in range(t*UPDATES_PER_S):
self.sm_qcom_gnss.update(UPDATE_MS)
if self.sm_qcom_gnss.updated['qcomGnss']:
return True
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
time.sleep(t)
self.sm.update()
def test_no_crash_double_command(self):
at_cmd("AT+QGPSDEL=0")
at_cmd("AT+QGPSDEL=0")
def test_wait_for_modem(self):
os.system("sudo systemctl stop ModemManager lte")
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")
assert self._wait_for_output(30)
self._wait_for_output(30)
assert self.sm.updated['qcomGnss']
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()
start_time = time.monotonic()
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"
self._wait_for_output(7)
assert self.sm.updated['qcomGnss']
managed_processes['rawgpsd'].stop()
def test_turns_off_gnss(self):
@ -83,38 +76,63 @@ class TestRawgpsd(unittest.TestCase):
loc_status = json.loads(ls)
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 loading: '+QGPSXTRADATA: 10080,"2023/06/24,19:00:00"'
out = at_cmd("AT+QGPSXTRADATA?")
out = out.split("+QGPSXTRADATA:")[1].split("'")[0].strip()
valid_duration, injected_time_str = out.split(",", 1)
assert valid_duration == "10080" # should be max time
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)
if should_be_loaded:
assert valid_duration == "10080" # should be max time
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")
def test_fix(self):
# clear assistance data
at_cmd("AT+QGPSDEL=0")
managed_processes['rawgpsd'].start()
managed_processes['laikad'].start()
assert self._wait_for_location(120)
assert self.sm_gps_location['gpsLocation'].flags == 1
module_fix = ecef_from_geodetic([self.sm_gps_location['gpsLocation'].latitude,
self.sm_gps_location['gpsLocation'].longitude,
self.sm_gps_location['gpsLocation'].altitude])
assert self._wait_for_laikad_location(90)
total_diff = np.array(self.sm_gnss_measurements['gnssMeasurements'].positionECEF.value) - module_fix
print(total_diff)
assert self._wait_for_output(60)
assert self.sm.updated['qcomGnss']
assert self.sm.updated['gpsLocation']
assert self.sm['gpsLocation'].flags == 1
module_fix = ecef_from_geodetic([self.sm['gpsLocation'].latitude,
self.sm['gpsLocation'].longitude,
self.sm['gpsLocation'].altitude])
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)
managed_processes['laikad'].stop()
managed_processes['rawgpsd'].stop()

Loading…
Cancel
Save