feat: user path to resume BT from low-memory pause via reader menu toggle

Field session: after a recovery teardown the heap settles at ~72 KB --
below the 100 KB start floor -- and never recovers on its own (the
defrag silent-restart only fires when a build FAILS, and builds succeed
now). BT stayed paused forever, the menu toggle claimed ON, and toggling
off/on changed nothing.

- Menu label tells the truth: ON / PAUSED (enabled but stack down) / OFF
  (new STR_STATE_PAUSED string)
- Toggling BT on below the heap floor silent-restarts into the current
  book: the fresh boot's ~118 KB passes the gate and BT auto-starts on
  resume. Explicit user intent is the right trigger for the defrag.
- Hoist the floor to bleinput::kStartMinFreeHeap, shared by the
  lifecycle gate and the toggle
This commit is contained in:
Nick
2026-07-02 17:25:34 -05:00
parent 551d29744a
commit 793685e76b
4 changed files with 31 additions and 7 deletions
+2
View File
@@ -299,6 +299,8 @@ STR_BT_NO_PAIRED: "No paired devices"
STR_BT_MAP_BUTTONS: "Map Remote Buttons"
STR_BT_PRESS_REMOTE: "Press a button on your remote"
STR_BT_CONNECTING_POPUP: "BT Connecting..."
STR_BT_PAUSED_LOW_MEM_POPUP: "BT paused (low memory)"
STR_STATE_PAUSED: "PAUSED"
STR_BT_PAGE_FORWARD: "Page Forward"
STR_BT_PAGE_BACK: "Page Back"
STR_BT_FORGET_PROMPT: "Hold Confirm to forget"
+6
View File
@@ -25,6 +25,12 @@ namespace bleinput {
// Advertised central name shown to peripherals during pairing.
inline constexpr const char* kHostName = "CrossPoint";
// Heap floor for starting the NimBLE stack (~57 KB) while leaving the reader's
// section-build pre-flight (40 KB) satisfiable afterwards. Shared by the main-loop
// lifecycle gate and the reader menu's toggle (which offers a defrag restart when a
// user turns BT on below the floor).
inline constexpr size_t kStartMinFreeHeap = 100 * 1024;
// Start the BLE HID host (idempotent). Returns false if BLE is compiled out or
// NimBLE init failed. Safe to call repeatedly.
bool ensureStarted();
@@ -2,9 +2,12 @@
#include <GfxRenderer.h>
#include <I18n.h>
#include <Logging.h>
#include "BleInput.h"
#include "CrossPointSettings.h"
#include "MappedInputManager.h"
#include "SilentRestart.h"
#include "components/UITheme.h"
#include "fontIds.h"
@@ -84,6 +87,15 @@ void EpubReaderMenuActivity::loop() {
// so start/stop has a single owner.
SETTINGS.bluetoothEnabled = SETTINGS.bluetoothEnabled ? 0 : 1;
SETTINGS.saveToFile();
// Turning BT on below the lifecycle's heap floor would otherwise sit in PAUSED
// until the heap happens to recover -- which a long session's fragmentation never
// gives back. The user asked for BT *now*: silent-restart into this book to
// defrag (fresh boot is ~118 KB free, comfortably above the floor), and BT
// auto-starts on the way back in.
if (SETTINGS.bluetoothEnabled && !BleHid.isRunning() && ESP.getFreeHeap() < bleinput::kStartMinFreeHeap) {
LOG_INF("ERM", "BT enabled below heap floor (%u); silent restart to defrag", ESP.getFreeHeap());
silentRestartToReader();
}
requestUpdate();
return;
}
@@ -138,8 +150,12 @@ void EpubReaderMenuActivity::render(RenderLock&&) {
// Render current page turn value on the right edge of the content area.
return pageTurnLabels[selectedPageTurnOption];
} else if (value == MenuAction::TOGGLE_BLUETOOTH) {
// Render current Bluetooth on/off state on the right edge.
return SETTINGS.bluetoothEnabled ? tr(STR_STATE_ON) : tr(STR_STATE_OFF);
// Render current Bluetooth state on the right edge. "Enabled but the stack
// isn't running" is the low-memory deferral -- show PAUSED, not a lie.
if (SETTINGS.bluetoothEnabled) {
return BleHid.isRunning() ? tr(STR_STATE_ON) : tr(STR_STATE_PAUSED);
}
return tr(STR_STATE_OFF);
} else {
return "";
}
+5 -5
View File
@@ -514,18 +514,18 @@ void updateBluetoothLifecycle() {
static constexpr uint32_t BLE_RESTART_COOLDOWN_MS = 30 * 1000;
const bool inCooldown = recoveryTeardownMs != 0 && millis() - recoveryTeardownMs < BLE_RESTART_COOLDOWN_MS;
// Heap gate: NimBLE needs ~57 KB, and the section-build pre-flight needs 40 KB free
// AFTER the stack is up -- so anything below ~100 KB just re-enters build recovery
// AFTER the stack is up -- so anything below the floor just re-enters build recovery
// and tears BLE straight back down. (A first cut used 70 KB for restarts; 70-57=13 KB
// left for builds, guaranteeing the oscillation above.) Defer and retry next loop;
// the build path's silent-restart defrag yields ~118 KB and passes this gate.
static constexpr size_t BLE_START_MIN_FREE_HEAP = 100 * 1024;
// the reader menu's toggle offers a defrag restart, and the build path's silent
// restart also yields ~118 KB and passes this gate.
static bool deferralAnnounced = false;
if (wanted && !BleHid.isRunning() && (inCooldown || ESP.getFreeHeap() < BLE_START_MIN_FREE_HEAP)) {
if (wanted && !BleHid.isRunning() && (inCooldown || ESP.getFreeHeap() < bleinput::kStartMinFreeHeap)) {
static uint32_t lastGateLogMs = 0;
if (millis() - lastGateLogMs > 10000) {
lastGateLogMs = millis();
LOG_INF("BLELC", "start deferred: heap %u floor %u cooldown=%d", ESP.getFreeHeap(),
(unsigned)BLE_START_MIN_FREE_HEAP, inCooldown ? 1 : 0);
(unsigned)bleinput::kStartMinFreeHeap, inCooldown ? 1 : 0);
}
// Tell the reader once per deferral episode that the remote is paused -- otherwise
// the only symptom is a remote that silently stopped working. Draws into the