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.
This commit is contained in:
Justin Mitchell
2026-07-14 05:04:22 -04:00
parent 078d9ef535
commit c1a396c1ba
3 changed files with 31 additions and 0 deletions
@@ -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);
@@ -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<WifiSelectionActivity>(renderer, mappedInput),
[this](const ActivityResult& result) { onWifiSelectionComplete(!result.isCancelled); });
@@ -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