Compare commits

...

6 Commits

Author SHA1 Message Date
Ben Meadors 92973913b8 Merge branch 'develop' into tryfix-nimble-crash 2026-06-10 18:45:38 -05:00
Ben Meadors 44c96bedd4 Merge branch 'develop' into tryfix-nimble-crash 2026-06-10 17:13:22 -05:00
Ben Meadors b50773d4ef Merge branch 'develop' into tryfix-nimble-crash 2026-06-10 08:08:11 -05:00
Ben Meadors c72ae290d5 Merge branch 'develop' into tryfix-nimble-crash 2026-06-10 07:46:28 -05:00
Ben Meadors a0d2770a08 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-09 08:15:38 -05:00
Ben Meadors b383d96954 Fix BLE advertising management to prevent crashes during reconnections 2026-06-09 07:40:16 -05:00
+43 -1
View File
@@ -21,6 +21,7 @@
#include "PowerStatus.h"
#include "host/ble_gap.h"
#include "host/ble_hs.h"
#include "host/ble_store.h"
namespace
@@ -47,6 +48,12 @@ BLEServer *bleServer;
static bool passkeyShowing;
static std::atomic<uint16_t> nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection"
// Set by onDisconnect (NimBLE task) to ask the main task to (re)start advertising. We must
// not call NimBLE GAP APIs directly from the disconnect callback: a phone reconnecting with a
// stale bond (e.g. after the device's NVS was wiped) triggers a MIC failure and a NimBLE host
// reset, and re-entering ble_gap_adv_* while the host is mid-reset crashes (LoadProhibited).
static std::atomic<bool> pendingStartAdvertising{false};
static void clearPairingDisplay()
{
if (!passkeyShowing) {
@@ -155,6 +162,23 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
protected:
virtual int32_t runOnce() override
{
// Restart advertising here (main task) if a disconnect asked us to. We defer this out
// of the NimBLE onDisconnect callback and gate it on ble_hs_synced() so we never
// re-enter the GAP API while the host is mid-reset -- the crash that happens when a
// phone reconnects with a stale bond to a freshly-wiped device.
if (pendingStartAdvertising) {
if (checkIsConnected()) {
pendingStartAdvertising = false; // a new physical connection beat us to it; nothing to do
} else if (ble_hs_synced()) {
pendingStartAdvertising = false;
if (nimbleBluetooth) {
nimbleBluetooth->startAdvertising();
}
} else {
return 200; // host still re-syncing after a reset; retry shortly
}
}
while (runOnceHasWorkToDo()) {
/*
PROCESS fromPhoneQueue BEFORE toPhoneQueue:
@@ -592,6 +616,16 @@ class NimbleBluetoothSecurityCallback : public BLESecurityCallbacks
}
void onAuthenticationComplete(ble_gap_conn_desc *desc) override
{
// The library calls this on every BLE_GAP_EVENT_ENC_CHANGE, whether encryption
// succeeded or failed. A phone reconnecting with a stale bond (e.g. after the device's
// NVS was wiped) produces a *failed* encryption change here. Don't latch a
// connected/authenticated state in that case, or the notify/RSSI/battery paths will
// operate on a link that is actually being torn down.
if (desc == nullptr || !desc->sec_state.encrypted) {
LOG_WARN("BLE encryption change without an encrypted link; ignoring");
return;
}
LOG_INFO("BLE authentication complete");
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
@@ -667,7 +701,15 @@ class NimbleBluetoothServerCallback : public BLEServerCallbacks
nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE;
ble->startAdvertising();
// Defer advertising restart to the main task (runOnce). Do NOT call startAdvertising()
// from here: if this disconnect was caused by a host reset (a phone reconnecting with a
// stale bond after an NVS wipe triggers a MIC failure -> host reset), re-entering the
// NimBLE GAP API now crashes. runOnce restarts once ble_hs_synced() is true again.
pendingStartAdvertising = true;
if (bluetoothPhoneAPI) {
bluetoothPhoneAPI->setIntervalFromNow(0);
}
concurrency::mainDelay.interrupt(); // wake the main loop to service the restart
}
};