diff --git a/src/main.cpp b/src/main.cpp index 1ca52c0592..333db08cd9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -792,6 +792,11 @@ void setup() // We do this as early as possible because this loads preferences from flash // but we need to do this after main cpu init (esp32setup), because we need the random seed set nodeDB = new NodeDB; +#ifdef ARCH_ESP32 + // Config is loaded now, and Bluetooth has not been initialized yet. If the + // saved config will keep Bluetooth inactive, return its reserved memory early. + esp32ReleaseBluetoothMemoryIfUnused(); +#endif // Initialize transmit history to persist broadcast throttle timers across reboots TransmitHistory::getInstance()->loadFromDisk(); diff --git a/src/main.h b/src/main.h index 661fdd9fb7..3458691298 100644 --- a/src/main.h +++ b/src/main.h @@ -11,7 +11,7 @@ #include "mesh/generated/meshtastic/telemetry.pb.h" #include #include -#if defined(ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) +#if defined(ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH #include "nimble/NimbleBluetooth.h" extern NimbleBluetooth *nimbleBluetooth; #endif @@ -114,6 +114,9 @@ extern bool runASAP; extern bool pauseBluetoothLogging; void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearBonds(), enterDfuMode(); +#ifdef ARCH_ESP32 +void esp32ReleaseBluetoothMemoryIfUnused(); +#endif meshtastic_DeviceMetadata getDeviceMetadata(); #if !MESHTASTIC_EXCLUDE_I2C diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 8bcfcd878d..39ae6b7885 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -1753,6 +1753,7 @@ void disableBluetooth() #ifdef ARCH_ESP32 if (nimbleBluetooth) nimbleBluetooth->deinit(); + esp32ReleaseBluetoothMemoryIfUnused(); #elif defined(ARCH_NRF52) if (nrf52Bluetooth) nrf52Bluetooth->shutdown(); diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index bcd618c7e9..2e0313ad0d 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -8,6 +8,11 @@ #include "nimble/NimbleBluetooth.h" #endif +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH && __has_include() +#include +#define CAN_RELEASE_BT_MEMORY 1 +#endif + #include #if HAS_WIFI @@ -30,9 +35,53 @@ void variant_shutdown() __attribute__((weak)); void variant_shutdown() {} +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH +static bool bluetoothMemoryReleased; +static bool bluetoothMemoryReleaseWarned; +#endif + +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH +static bool isNetworkConfiguredToDisableBluetooth() +{ +#if HAS_WIFI + return isWifiAvailable(); +#elif defined(USE_WS5500) || defined(USE_CH390D) + return config.network.wifi_enabled; +#else + return false; +#endif +} + +static bool shouldReleaseBluetoothMemory() +{ + // On ESP32 targets WiFi and BLE share radio resources. When WiFi is configured for this boot, + // BLE will not be started, so its reserved memory can be returned to the heap until reboot. + if (isNetworkConfiguredToDisableBluetooth()) { + return true; + } + return !config.bluetooth.enabled; +} + +static const char *getBluetoothReleaseReason() +{ + if (isNetworkConfiguredToDisableBluetooth()) { + return "WiFi is enabled"; + } + return "Bluetooth is disabled"; +} +#endif + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { + if (enable && bluetoothMemoryReleased) { + if (!shouldReleaseBluetoothMemory() && !bluetoothMemoryReleaseWarned) { + bluetoothMemoryReleaseWarned = true; + LOG_WARN("Bluetooth memory has been released; reboot to re-enable Bluetooth"); + } + return; + } + #if defined(USE_WS5500) || defined(USE_CH390D) if ((config.bluetooth.enabled == true) && (config.network.wifi_enabled == false)) #elif HAS_WIFI @@ -58,6 +107,29 @@ void setBluetoothEnable(bool enable) {} void updateBatteryLevel(uint8_t level) {} #endif +void esp32ReleaseBluetoothMemoryIfUnused() +{ +#ifdef CAN_RELEASE_BT_MEMORY + if (bluetoothMemoryReleased || !shouldReleaseBluetoothMemory()) { + return; + } + + const int32_t heapBefore = ESP.getHeapSize(); + const int32_t freeBefore = ESP.getFreeHeap(); + + // ESP_BT_MODE_BTDM releases all BT/BLE controller and host memory for this boot. + // It is intentionally irreversible until reboot, matching the runtime config behavior. + esp_err_t err = esp_bt_mem_release(ESP_BT_MODE_BTDM); + if (err == ESP_OK) { + bluetoothMemoryReleased = true; + LOG_INFO("Released BTDM memory because %s: heap %+d, free %+d", getBluetoothReleaseReason(), + (int32_t)ESP.getHeapSize() - heapBefore, (int32_t)ESP.getFreeHeap() - freeBefore); + } else { + LOG_WARN("BTDM memory release failed: %d", err); + } +#endif +} + void getMacAddr(uint8_t *dmac) { #if defined(CONFIG_IDF_TARGET_ESP32C6) && defined(CONFIG_SOC_IEEE802154_SUPPORTED)