From f8a8d1247786fe19f19cd07dce75e702d9d463f7 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 28 Aug 2026 22:32:01 +0000 Subject: [PATCH] fix(ble): stop BLE from coming back up during the pre-reboot window (#11650) * fix(ble): stop BLE from coming back up during the pre-reboot window Saving a reboot-requiring config over BLE (e.g. screen timeout) made the node disconnect, re-advertise, let the phone reconnect, and then drop it again at the reset. Two causes: nRF52: admin messages from the phone run synchronously on Bluefruit's BLE event task, so the BLE_GAP_EVT_DISCONNECTED caused by shutdown() is only processed after we return - and that handler restarts advertising because restartOnDisconnect(true) was never cleared. Stopping advertising first is a no-op while a connection is live (the SoftDevice isn't advertising), so the deferred event brought it straight back. Clear the restart flag and stop advertising before dropping the link, mirroring nRF54L15's ble_enabled gate. This also closes a main-thread race on the shutdown path where Advertising.stop() could land between connection teardown and Bluefruit's auto-restart within the same event dispatch. PowerFSM (all platforms): darkEnter/onEnter/powerEnter/powerExit/serialExit unconditionally re-enable BLE, so any state transition inside the reboot window - a button press while the banner is up, the screen timeout, USB plug/unplug - turned BLE back on after AdminModule had deliberately torn it down. Route them through a helper that skips the re-enable while rebootAtMsec/shutdownAtMsec is armed; every writer of those deadlines is an imminent restart. * style: trim rationale comments to house 1-2 line limit The full mechanism is in the original commit message and PR description. --- src/PowerFSM.cpp | 21 ++++++++++++++++----- src/platform/nrf52/NRF52Bluetooth.cpp | 6 +++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 5367293f22..400aabc676 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -195,6 +195,17 @@ static void lsExit() t5BacklightWakeFromSleep(); } +/// Skip the BLE re-enable while a reboot/shutdown is armed: AdminModule tears BLE down before +/// scheduling the restart, and a state transition in that window would otherwise bring it back up. +static void setBluetoothEnableUnlessRestarting() +{ + if (rebootAtMsec || shutdownAtMsec) { + LOG_POWERFSM("Skip BLE enable, restart pending"); + return; + } + setBluetoothEnable(true); +} + static void nbEnter() { LOG_POWERFSM("State: nbEnter"); @@ -211,7 +222,7 @@ static void nbEnter() static void darkEnter() { LOG_POWERFSM("State: darkEnter"); - setBluetoothEnable(true); + setBluetoothEnableUnlessRestarting(); if (screen) screen->setOn(false); // Screen timeout enters DARK; ensure backlight also turns off. @@ -235,7 +246,7 @@ static void serialExit() { LOG_POWERFSM("State: serialExit"); // Turn bluetooth back on when we leave serial stream API - setBluetoothEnable(true); + setBluetoothEnableUnlessRestarting(); } static void powerEnter() @@ -248,7 +259,7 @@ static void powerEnter() } else { if (screen) screen->setOn(true); - setBluetoothEnable(true); + setBluetoothEnableUnlessRestarting(); // within enter() the function getState() returns the state we came from } } @@ -266,7 +277,7 @@ static void powerIdle() static void powerExit() { LOG_POWERFSM("State: powerExit"); - setBluetoothEnable(true); + setBluetoothEnableUnlessRestarting(); } static void onEnter() @@ -274,7 +285,7 @@ static void onEnter() LOG_POWERFSM("State: onEnter"); if (screen) screen->setOn(true); - setBluetoothEnable(true); + setBluetoothEnableUnlessRestarting(); } static void onIdle() diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 85a29e05a9..608fb1b7bb 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -245,8 +245,12 @@ void NRF52Bluetooth::shutdown() // Shutdown bluetooth for minimum power draw LOG_INFO("Disable NRF52 bluetooth"); Bluefruit.Security.setPairPasskeyCallback(NRF52Bluetooth::onUnwantedPairing); // Actively refuse (during factory reset) - disconnect(); + + // Clear the auto-restart flag before dropping the link: our DISCONNECTED event is only processed + // after this callback returns and would re-start advertising. startAdv()/resumeAdvertising() re-set it. + Bluefruit.Advertising.restartOnDisconnect(false); Bluefruit.Advertising.stop(); + disconnect(); } void NRF52Bluetooth::startDisabled() {