BLE P2P stability: PSRAM zero-init, pool sizing, stuck-state recovery

Root cause: Bytes objects stored in PSRAM-allocated BLEInterface had
corrupted shared_ptr members from uninitialized memory, causing crashes
in processDiscoveredPeers(). Fixed by using heap_caps_calloc instead of
heap_caps_malloc for PSRAM placement-new allocation.

Additional fixes:
- Reduce pool sizes to fit memory budget (reassembler 134KB→17KB,
  fragmenters 8→4, handshakes 32→4, pending data 64→8)
- Store local MAC as BLEAddress struct instead of Bytes to avoid
  heap allocation in PSRAM-resident object
- Move setLocalMac after platform start (NimBLE needs to be running
  for valid random address), add lazy MAC init fallback in loop()
- Add stuck-state detector: resets GAP state machine if hardware
  is idle but state machine thinks it's busy
- Enhance getLocalAddress with 3 fallback methods (NimBLE API,
  ble_hs_id_copy_addr RANDOM, esp_read_mac efuse)
- Fix C++17 structured binding to C++11 compatibility
- Increase BLE task stack 8KB→12KB for string ops in debug logs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
torlando-tech
2026-02-22 20:57:05 -05:00
parent d58ac9573f
commit 769c9952bd
7 changed files with 114 additions and 22 deletions
+21 -5
View File
@@ -90,9 +90,6 @@ bool BLEInterface::start() {
// Set identity data for peripheral mode
_platform->setIdentityData(_local_identity);
// Set local MAC in peer manager
_peer_manager.setLocalMac(_platform->getLocalAddress().toBytes());
// Start platform
if (!_platform->start()) {
ERROR("BLEInterface: Failed to start BLE platform");
@@ -100,6 +97,14 @@ bool BLEInterface::start() {
return false;
}
// Set local MAC in peer manager (must be after start() when NimBLE has a valid address)
auto local_addr = _platform->getLocalAddress();
auto local_mac_bytes = local_addr.toBytes();
INFO("BLEInterface: Local address from platform: " + local_addr.toString() +
" bytes_size=" + std::to_string(local_mac_bytes.size()) +
" isZero=" + std::to_string(local_addr.isZero()));
_peer_manager.setLocalMac(local_mac_bytes);
_online = true;
_last_scan = 0; // Trigger immediate scan
_last_keepalive = Utilities::OS::time();
@@ -131,8 +136,19 @@ void BLEInterface::stop() {
void BLEInterface::loop() {
static double last_loop_log = 0;
static bool local_mac_set = false;
double now = Utilities::OS::time();
// Lazy init: set local MAC once NimBLE has a valid random address
if (!local_mac_set && _platform) {
auto addr = _platform->getLocalAddress();
if (!addr.isZero()) {
_peer_manager.setLocalMac(addr.toBytes());
local_mac_set = true;
INFO("BLEInterface: Local MAC resolved: " + addr.toString());
}
}
// Process any pending handshakes (deferred from callback for stack safety)
if (_pending_handshake_count > 0) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
@@ -827,7 +843,7 @@ void BLEInterface::processDiscoveredPeers() {
if (now - last_peer_log >= 10.0) {
auto all_peers = _peer_manager.getAllPeers();
DEBUG("BLEInterface: Peer count=" + std::to_string(all_peers.size()) +
" localMAC=" + _peer_manager.getLocalMac().toHex());
" localMAC=" + _peer_manager.getLocalMac().toString());
for (PeerInfo* peer : all_peers) {
bool should_initiate = _peer_manager.shouldInitiateConnection(peer->mac_address);
DEBUG("BLEInterface: Peer " + BLEAddress(peer->mac_address.data()).toString() +
@@ -1026,7 +1042,7 @@ bool BLEInterface::start_task(int priority, int core) {
BaseType_t result = xTaskCreatePinnedToCore(
ble_task,
"ble",
8192, // 8KB stack
12288, // 12KB stack (string ops in debug logs need headroom)
this,
priority,
&_task_handle,