From c1a396c1bac0263cd7d82da1287aaa3619d2e277 Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Tue, 14 Jul 2026 05:04:22 -0400 Subject: [PATCH] Stop BLE before initializing WiFi in activities Explicitly stop the BLE stack before bringing up WiFi in CrossPointWebServerActivity, FontDownloadActivity, and OtaUpdateActivity. On ESP32-C3, the shared radio and heap between BLE and WiFi stacks causes WiFi to be permanently starved if initialized while NimBLE's ~50KB is still resident, as the WiFi driver sizes its RX/TX buffer pools at init time. --- .../network/CrossPointWebServerActivity.cpp | 15 +++++++++++++++ src/activities/settings/FontDownloadActivity.cpp | 8 ++++++++ src/activities/settings/OtaUpdateActivity.cpp | 8 ++++++++ 3 files changed, 31 insertions(+) diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp index 8be6d1e0..e0f1c25c 100644 --- a/src/activities/network/CrossPointWebServerActivity.cpp +++ b/src/activities/network/CrossPointWebServerActivity.cpp @@ -18,6 +18,8 @@ #include "fontIds.h" #include "util/QrUtils.h" +#include "BleInput.h" + namespace { // AP Mode configuration constexpr const char* AP_SSID = "CrossPoint-Reader"; @@ -141,6 +143,12 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) if (mode == NetworkMode::JOIN_NETWORK) { // STA mode - launch WiFi selection LOG_DBG("WEBACT", "Turning on WiFi (STA mode)..."); + // Free the BLE stack BEFORE bringing WiFi up (matches WifiSelectionActivity): + // the C3 shares one radio and heap between the stacks, and the WiFi driver + // sizes its RX/TX buffer pools at init — initializing it with NimBLE's ~50 KB + // still resident leaves WiFi permanently starved even after the lifecycle + // stops BLE a loop later. No-op when BLE is already off. + bleinput::stop(); WiFi.mode(WIFI_STA); state = WebServerActivityState::WIFI_SELECTION; @@ -193,6 +201,13 @@ void CrossPointWebServerActivity::startAccessPoint() { LOG_DBG("WEBACT", "Starting Access Point mode..."); LOG_DBG("WEBACT", "Free heap before AP start: %d bytes", ESP.getFreeHeap()); + // Free the BLE stack BEFORE bringing WiFi up (matches WifiSelectionActivity): + // the C3 shares one radio and heap between the stacks, and the WiFi driver + // sizes its RX/TX buffer pools at init — initializing it with NimBLE's ~50 KB + // still resident leaves WiFi permanently starved even after the lifecycle + // stops BLE a loop later. No-op when BLE is already off. + bleinput::stop(); + // Configure and start the AP WiFi.mode(WIFI_AP); delay(100); diff --git a/src/activities/settings/FontDownloadActivity.cpp b/src/activities/settings/FontDownloadActivity.cpp index 0ccae794..02fab6cb 100644 --- a/src/activities/settings/FontDownloadActivity.cpp +++ b/src/activities/settings/FontDownloadActivity.cpp @@ -17,6 +17,8 @@ #include "fontIds.h" #include "network/HttpDownloader.h" +#include "BleInput.h" + FontDownloadActivity::FontDownloadActivity(GfxRenderer& renderer, MappedInputManager& mappedInput) : Activity("FontDownload", renderer, mappedInput), fontInstaller_(sdFontSystem.registry()) {} @@ -24,6 +26,12 @@ FontDownloadActivity::FontDownloadActivity(GfxRenderer& renderer, MappedInputMan void FontDownloadActivity::onEnter() { Activity::onEnter(); + // Free the BLE stack BEFORE bringing WiFi up (matches WifiSelectionActivity): + // the C3 shares one radio and heap between the stacks, and the WiFi driver + // sizes its RX/TX buffer pools at init — initializing it with NimBLE's ~50 KB + // still resident leaves WiFi permanently starved even after the lifecycle + // stops BLE a loop later. No-op when BLE is already off. + bleinput::stop(); WiFi.mode(WIFI_STA); startActivityForResult(std::make_unique(renderer, mappedInput), [this](const ActivityResult& result) { onWifiSelectionComplete(!result.isCancelled); }); diff --git a/src/activities/settings/OtaUpdateActivity.cpp b/src/activities/settings/OtaUpdateActivity.cpp index 905e1933..dbd48402 100644 --- a/src/activities/settings/OtaUpdateActivity.cpp +++ b/src/activities/settings/OtaUpdateActivity.cpp @@ -11,6 +11,8 @@ #include "fontIds.h" #include "network/OtaUpdater.h" +#include "BleInput.h" + void OtaUpdateActivity::onWifiSelectionComplete(const bool success) { if (!success) { LOG_ERR("OTA", "WiFi connection failed, exiting"); @@ -56,6 +58,12 @@ void OtaUpdateActivity::onEnter() { // Turn on WiFi immediately LOG_DBG("OTA", "Turning on WiFi..."); + // Free the BLE stack BEFORE bringing WiFi up (matches WifiSelectionActivity): + // the C3 shares one radio and heap between the stacks, and the WiFi driver + // sizes its RX/TX buffer pools at init — initializing it with NimBLE's ~50 KB + // still resident leaves WiFi permanently starved even after the lifecycle + // stops BLE a loop later. No-op when BLE is already off. + bleinput::stop(); WiFi.mode(WIFI_STA); // Launch WiFi selection subactivity