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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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()