diff --git a/opendbc_repo/opendbc/car/hyundai/radar_interface.py b/opendbc_repo/opendbc/car/hyundai/radar_interface.py index 49a7646b3..9ca71d468 100644 --- a/opendbc_repo/opendbc/car/hyundai/radar_interface.py +++ b/opendbc_repo/opendbc/car/hyundai/radar_interface.py @@ -63,6 +63,15 @@ class RadarInterface(RadarInterfaceBase): self.ioniq_6_radar_probe_updates = 0 self.rcp = get_radar_can_parser(CP, self.radar_config) + # Precompute (addr, "RADAR_TRACK_xxx") pairs once. _update runs on the + # CAN-driven card loop (core 4, shared with controlsd/selfdrived), so avoid + # rebuilding 32 f-strings per radar frame. + self.track_addrs: list[tuple[int, str]] = [] + if self.radar_config is not None: + self.track_addrs = [(addr, f"RADAR_TRACK_{addr:x}") + for addr in range(self.radar_config.start_addr, + self.radar_config.start_addr + self.radar_config.msg_count)] + def update(self, can_strings): if self.ioniq_6_radar_probe and self.rcp is not None and not self.ioniq_6_radar_probe_logged: vls = self.rcp.update(can_strings) @@ -104,10 +113,13 @@ class RadarInterface(RadarInterfaceBase): if self.radar_config is None: return ret - for addr in range(self.radar_config.start_addr, self.radar_config.start_addr + self.radar_config.msg_count): - msg = self.rcp.vl[f"RADAR_TRACK_{addr:x}"] + radar_type = self.radar_config.radar_type + vl = self.rcp.vl - if self.radar_config.radar_type == "mrr30": + for addr, track_name in self.track_addrs: + msg = vl[track_name] + + if radar_type == "mrr30": for i in ("1", "2"): track_key = addr * 2 + int(i) - 1 if track_key not in self.pts: @@ -128,24 +140,32 @@ class RadarInterface(RadarInterfaceBase): del self.pts[track_key] continue + if radar_type == "mrr35": + # Most of the 32 channels are empty each frame. Only allocate a point + # when the channel is valid; drop it otherwise. Avoids the per-frame + # alloc-then-delete churn on the ~27 idle channels. + if msg["STATE"] in (3, 4): + pt = self.pts.get(addr) + if pt is None: + pt = structs.RadarData.RadarPoint() + pt.trackId = self.track_id + self.track_id += 1 + self.pts[addr] = pt + pt.measured = True + pt.dRel = msg["LONG_DIST"] + pt.yRel = msg["LAT_DIST"] + pt.vRel = msg["REL_SPEED"] + pt.aRel = msg["REL_ACCEL"] + pt.yvRel = float("nan") + elif addr in self.pts: + del self.pts[addr] + continue + if addr not in self.pts: self.pts[addr] = structs.RadarData.RadarPoint() self.pts[addr].trackId = self.track_id self.track_id += 1 - if self.radar_config.radar_type == "mrr35": - valid = msg["STATE"] in (3, 4) - if valid: - self.pts[addr].measured = True - self.pts[addr].dRel = msg["LONG_DIST"] - self.pts[addr].yRel = msg["LAT_DIST"] - self.pts[addr].vRel = msg["REL_SPEED"] - self.pts[addr].aRel = msg["REL_ACCEL"] - self.pts[addr].yvRel = float("nan") - else: - del self.pts[addr] - continue - valid = msg['STATE'] in (3, 4) if valid: azimuth = math.radians(msg['AZIMUTH']) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index a6355b3a2..cb2ecb7e2 100644 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -957,10 +957,16 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # ten times the regular interval, or the average interval is more than 10% too high. EventName.commIssue: { ET.SOFT_DISABLE: soft_disable_alert("Communication Issue Between Processes"), + ET.PERMANENT: Alert("Communication Issue Between Processes", "", + AlertStatus.normal, AlertSize.small, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 1., creation_delay=30.), ET.NO_ENTRY: comm_issue_alert, }, EventName.commIssueAvgFreq: { ET.SOFT_DISABLE: soft_disable_alert("Low Communication Rate Between Processes"), + ET.PERMANENT: Alert("Low Communication Rate Between Processes", "", + AlertStatus.normal, AlertSize.small, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 1., creation_delay=30.), ET.NO_ENTRY: NoEntryAlert("Low Communication Rate Between Processes"), }, diff --git a/selfdrive/ui/main.cc b/selfdrive/ui/main.cc index 4903a3db3..35ee73528 100644 --- a/selfdrive/ui/main.cc +++ b/selfdrive/ui/main.cc @@ -1,17 +1,46 @@ #include +#include #include #include +#include "common/swaglog.h" +#include "common/util.h" #include "system/hardware/hw.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/window.h" +// Qt 5.12.8's qErrnoWarning() emits QtCriticalMsg then calls abort() directly, +// bypassing the fatal-message path. Intercept critical+fatal Wayland messages +// before the unconditional abort() fires and clean-exit so the manager restarts +// us quickly instead of going through the slow abort/crash-handler path. +void waylandAwareMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + if (type == QtCriticalMsg || type == QtFatalMsg) { + QByteArray bytes = msg.toUtf8(); + if (bytes.contains("ayland") || bytes.contains("wl_display")) { + swagLogMessageHandler(type, context, msg); + LOGE("UI WAYLAND EXIT: %s", bytes.constData()); + _exit(0); // clean exit; manager restarts us + } + } + swagLogMessageHandler(type, context, msg); + // Non-Wayland fatal: let Qt abort normally; crash_handler will capture it. +} + int main(int argc, char *argv[]) { setpriority(PRIO_PROCESS, 0, -20); - qInstallMessageHandler(swagLogMessageHandler); + // Pin the UI to the little cores (0-3). The realtime control loop + // (card/controlsd/selfdrived) runs SCHED_FIFO on core 4; without this pin the + // kernel can schedule the UI there, and a UI stall/restart spike preempts + // selfdrived, starving its 100 Hz loop and firing the "System Lagging" alert. + // Set before any threads spawn so children inherit the affinity. + if (!Hardware::PC()) { + util::set_core_affinity({0, 1, 2, 3}); + } + + qInstallMessageHandler(waylandAwareMessageHandler); initApp(argc, argv); QTranslator translator; diff --git a/selfdrive/ui/qt/offroad/firehose.cc b/selfdrive/ui/qt/offroad/firehose.cc index aa158b4c7..fb96d8cb3 100644 --- a/selfdrive/ui/qt/offroad/firehose.cc +++ b/selfdrive/ui/qt/offroad/firehose.cc @@ -82,7 +82,7 @@ FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent) // Set up the API request for firehose stats const QString dongle_id = QString::fromStdString(Params().get("DongleId")); firehose_stats = new RequestRepeater(this, CommaApi::BASE_URL + "/v1/devices/" + dongle_id + "/firehose_stats", - "ApiCache_FirehoseStats", 30, true); + "ApiCache_FirehoseStats", 30, false); QObject::connect(firehose_stats, &RequestRepeater::requestDone, [=](const QString &response, bool success) { if (success) { QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); diff --git a/selfdrive/ui/qt/prime_state.cc b/selfdrive/ui/qt/prime_state.cc index f12daf1e3..4dc17d757 100644 --- a/selfdrive/ui/qt/prime_state.cc +++ b/selfdrive/ui/qt/prime_state.cc @@ -16,7 +16,7 @@ PrimeState::PrimeState(QObject* parent) : QObject(parent) { if (auto dongleId = getDongleId()) { QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/"; - RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_Device", 5); + RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_Device", 60); QObject::connect(repeater, &RequestRepeater::requestDone, this, &PrimeState::handleReply); } diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 3279e77d5..9170c905f 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -391,6 +391,11 @@ void UIState::update() { if (!watchdog_kick(nanos_since_boot())) { LOGE("UI watchdog kick failed at frame %llu", static_cast(sm->frame)); } + // Re-pin to the little cores: power-save can offline our core and the + // kernel may rebalance us onto core 4 (the realtime control loop). + if (!Hardware::PC()) { + util::set_core_affinity({0, 1, 2, 3}); + } } ui_stall_progress(UIStallPhase::AFTER_WATCHDOG, sm->frame); emit uiUpdate(*this, *starpilotUIState()); diff --git a/starpilot/ui/starpilot_ui.cc b/starpilot/ui/starpilot_ui.cc index 79aa1240d..c7ef16a06 100644 --- a/starpilot/ui/starpilot_ui.cc +++ b/starpilot/ui/starpilot_ui.cc @@ -40,21 +40,26 @@ static void update_state(StarPilotUIState *fs) { } capnp::Text::Reader toggles = starpilotPlan.getStarpilotToggles(); QByteArray current_toggles(toggles.cStr(), toggles.size()); - static QByteArray previous_toggles; - if (previous_toggles != current_toggles) { - QJsonParseError parse_error; - QJsonDocument toggles_doc = QJsonDocument::fromJson(current_toggles, &parse_error); - if (parse_error.error == QJsonParseError::NoError && toggles_doc.isObject()) { - QJsonObject updated_toggles = starpilot_scene.starpilot_toggles; - const QJsonObject parsed_toggles = toggles_doc.object(); - for (auto it = parsed_toggles.begin(); it != parsed_toggles.end(); ++it) { - updated_toggles.insert(it.key(), it.value()); + // starpilot_process only broadcasts the full toggles JSON periodically and + // sends an empty string on every other frame. Skip the empty broadcasts so + // we don't parse "" (QJsonParseError "illegal value") every frame. + if (!current_toggles.trimmed().isEmpty()) { + static QByteArray previous_toggles; + if (previous_toggles != current_toggles) { + QJsonParseError parse_error; + QJsonDocument toggles_doc = QJsonDocument::fromJson(current_toggles, &parse_error); + if (parse_error.error == QJsonParseError::NoError && toggles_doc.isObject()) { + QJsonObject updated_toggles = starpilot_scene.starpilot_toggles; + const QJsonObject parsed_toggles = toggles_doc.object(); + for (auto it = parsed_toggles.begin(); it != parsed_toggles.end(); ++it) { + updated_toggles.insert(it.key(), it.value()); + } + starpilot_scene.starpilot_toggles = updated_toggles; + } else { + qWarning() << "Ignoring invalid StarPilot toggles JSON:" << parse_error.errorString(); } - starpilot_scene.starpilot_toggles = updated_toggles; - } else { - qWarning() << "Ignoring invalid StarPilot toggles JSON:" << parse_error.errorString(); + previous_toggles = current_toggles; } - previous_toggles = current_toggles; } } diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 420a90823..2c56c7b11 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -10,7 +10,7 @@ from openpilot.system.hardware import HARDWARE, PC, TICI from openpilot.system.manager.process import PythonProcess, NativeProcess, DaemonProcess WEBCAM = os.getenv("USE_WEBCAM") is not None -UI_WATCHDOG_MAX_DT = int(os.getenv("UI_WATCHDOG_MAX_DT", "5")) +UI_WATCHDOG_MAX_DT = int(os.getenv("UI_WATCHDOG_MAX_DT", "10")) def driverview(started: bool, params: Params, CP: car.CarParams, starpilot_toggles: SimpleNamespace) -> bool: return started or params.get_bool("IsDriverViewEnabled") diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 59f5ac0b5..fc1eb8a7c 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -122,9 +122,13 @@ def downloader_loop(event): if alt_path is not None and os.path.exists(alt_path): shutil.copyfile(alt_path, ASSIST_DATA_FILE) + sm = messaging.SubMaster(['deviceState']) + try: while not os.path.exists(ASSIST_DATA_FILE) and not event.is_set(): - download_assistance() + sm.update(0) + if sm['deviceState'].networkType != log.DeviceState.NetworkType.none: + download_assistance() event.wait(timeout=10) except KeyboardInterrupt: pass