diff --git a/.gitignore b/.gitignore index de8116416e..91aecd9a72 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/cereal b/cereal index c94c7c61cc..a2f1f0cb8d 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit c94c7c61cc576e950a1604e1a3c9a91b1f86964c +Subproject commit a2f1f0cb8dd45ea4265255855da7de8fd89156ed diff --git a/docs/CARS.md b/docs/CARS.md index ee99b7d138..f9a8ac7cee 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -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|Hardware Needed
 |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)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |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)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Carnival 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|EV6 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|EV6 (with HDA II) 2022-23[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/opendbc b/opendbc index fe8d535a7f..236359cf63 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit fe8d535a7fd99eeb15526ca944a6019b9a1e5ea0 +Subproject commit 236359cf63c3caaf8e02b972c452aabac416662a diff --git a/poetry.lock b/poetry.lock index d3e8e1fb3b..3016522123 100644 --- a/poetry.lock +++ b/poetry.lock @@ -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"}, diff --git a/pyproject.toml b/pyproject.toml index 4f48c67b6b..93e28e1a75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index cfdca75093..66bf303def 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -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 diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 27b62414fa..607c37f2f2 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -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), diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index c4ee5f617c..3e365991bd 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -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), diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index 5ea84e5591..d79dbe8573 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -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 diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index b0721266a4..38378757e8 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -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') diff --git a/selfdrive/modeld/dmonitoringmodeld.cc b/selfdrive/modeld/dmonitoringmodeld.cc index cde13a9bee..8d61151ef1 100644 --- a/selfdrive/modeld/dmonitoringmodeld.cc +++ b/selfdrive/modeld/dmonitoringmodeld.cc @@ -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); diff --git a/selfdrive/modeld/runners/snpemodel.cc b/selfdrive/modeld/runners/snpemodel.cc index aa5ee3bb0e..441122c522 100644 --- a/selfdrive/modeld/runners/snpemodel.cc +++ b/selfdrive/modeld/runners/snpemodel.cc @@ -33,7 +33,7 @@ SNPEModel::SNPEModel(const std::string path, float *_output, size_t _output_size // load model std::unique_ptr 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 diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 78907ecc18..9d1efe255d 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -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( diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 7a59e296e5..3a97fe1466 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -a683d689bd74ba5ba7c10bfad237aacc04b978c3 +219a815856d8984cb4933d83db9a15bf7cd09f16 diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 9e55da1088..f6d0d6a3b9 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -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(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); diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 2ef2d9aa9d..34b55e8997 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -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 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; diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index aeccaa71c1..b2d00bf049 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -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(); } diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc index 6a449b594d..0b09db7bc6 100644 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -2,7 +2,6 @@ #include #include -#include #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(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>(); - - 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(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); +} diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h index ed9d0c980b..2326fb3724 100644 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -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 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: diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc index fa37c015f7..7aa731898c 100644 --- a/selfdrive/ui/qt/request_repeater.cc +++ b/selfdrive/ui/qt/request_repeater.cc @@ -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); } }); diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 705c2f2172..74fd05ed7b 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -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: diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 71fc466c20..05b61e1f76 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -18,8 +18,6 @@ private: void openSettings(int index = 0, const QString ¶m = ""); void closeSettings(); - Device device; - QStackedLayout *main_layout; HomeWindow *homeWindow; SettingsWindow *settingsWindow; diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index c4586c1ff5..f978df852d 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -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; +} diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 2519f8ea2c..a97ef35789 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -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, diff --git a/system/sensord/rawgps/rawgpsd.py b/system/sensord/rawgps/rawgpsd.py index 3e50090d0e..f588e6b153 100755 --- a/system/sensord/rawgps/rawgpsd.py +++ b/system/sensord/rawgps/rawgpsd.py @@ -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) diff --git a/system/sensord/rawgps/test_rawgps.py b/system/sensord/rawgps/test_rawgps.py index 918c0e9f10..2132b77009 100755 --- a/system/sensord/rawgps/test_rawgps.py +++ b/system/sensord/rawgps/test_rawgps.py @@ -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()