Add BLE lifecycle pause mechanism for memory mgmt
Introduces a global pause flag to temporarily block BLE stack auto-start during large memory allocations (e.g., in EPUB rendering). Also adds auto-restart logic for BLE scanning when device list is empty, and extensive debug logging for BLE scan lifecycle troubleshooting.
This commit is contained in:
+1
-1
Submodule freeink-sdk updated: a913bb3003...12f9af14bb
@@ -94,6 +94,7 @@ build_flags =
|
|||||||
-DENABLE_SERIAL_LOG
|
-DENABLE_SERIAL_LOG
|
||||||
-DLOG_LEVEL=2 ; Set log level to debug for development builds
|
-DLOG_LEVEL=2 ; Set log level to debug for development builds
|
||||||
-DFREEINK_CAP_BLE_HID_HOST=1 ; BLE HID page-turner host (NimBLE)
|
-DFREEINK_CAP_BLE_HID_HOST=1 ; BLE HID page-turner host (NimBLE)
|
||||||
|
-DFREEINK_BLE_HID_SCAN_DEBUG=1 ; verbose BLE scan lifecycle/advertisement logs for bring-up
|
||||||
|
|
||||||
|
|
||||||
[env:gh_release]
|
[env:gh_release]
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
|
|
||||||
namespace bleinput {
|
namespace bleinput {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
volatile bool g_lifecyclePaused = false;
|
||||||
|
}
|
||||||
|
|
||||||
// NimBLE controller init/deinit hang (interrupt WDT) if run at the 10 MHz low-power
|
// NimBLE controller init/deinit hang (interrupt WDT) if run at the 10 MHz low-power
|
||||||
// frequency, so force normal CPU speed around both. Centralized here so every caller
|
// frequency, so force normal CPU speed around both. Centralized here so every caller
|
||||||
// (boot restore, settings toggle, reader toggle, sleep) is covered automatically.
|
// (boot restore, settings toggle, reader toggle, sleep) is covered automatically.
|
||||||
@@ -28,6 +32,10 @@ void stop() {
|
|||||||
BleHid.end();
|
BleHid.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setLifecyclePaused(bool paused) { g_lifecyclePaused = paused; }
|
||||||
|
|
||||||
|
bool lifecyclePaused() { return g_lifecyclePaused; }
|
||||||
|
|
||||||
bool encodeKey(const freeink::KeyEvent& ev, uint8_t& kind, uint8_t& value) {
|
bool encodeKey(const freeink::KeyEvent& ev, uint8_t& kind, uint8_t& value) {
|
||||||
if (ev.special != freeink::SpecialKey::None) {
|
if (ev.special != freeink::SpecialKey::None) {
|
||||||
kind = 0;
|
kind = 0;
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ bool ensureStarted();
|
|||||||
// Drop the active link (e.g. before deep sleep or when the user disables BT).
|
// Drop the active link (e.g. before deep sleep or when the user disables BT).
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
// Temporarily block the main-loop BLE lifecycle from auto-starting the stack.
|
||||||
|
// Used while a render path deliberately frees BLE RAM for a large allocation.
|
||||||
|
void setLifecyclePaused(bool paused);
|
||||||
|
bool lifecyclePaused();
|
||||||
|
|
||||||
// Encode a decoded key event into the stable (kind, value) identity used by the
|
// Encode a decoded key event into the stable (kind, value) identity used by the
|
||||||
// settings map. kind: 0 = SpecialKey, 1 = HID usage. Returns false when the event
|
// settings map. kind: 0 = SpecialKey, 1 = HID usage. Returns false when the event
|
||||||
// carries no usable identity (no special key and no usage code).
|
// carries no usable identity (no special key and no usage code).
|
||||||
|
|||||||
@@ -254,6 +254,10 @@ bool ActivityManager::isReaderActivity() const {
|
|||||||
(currentActivity && currentActivity->isReaderActivity());
|
(currentActivity && currentActivity->isReaderActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ActivityManager::currentKeepsBluetoothAlive() const {
|
||||||
|
return currentActivity && currentActivity->keepsBluetoothAlive();
|
||||||
|
}
|
||||||
|
|
||||||
void ActivityManager::requestGhostCleanup() {
|
void ActivityManager::requestGhostCleanup() {
|
||||||
if (currentActivity) currentActivity->requestGhostCleanup();
|
if (currentActivity) currentActivity->requestGhostCleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ class ActivityManager {
|
|||||||
|
|
||||||
bool preventAutoSleep() const;
|
bool preventAutoSleep() const;
|
||||||
bool isReaderActivity() const;
|
bool isReaderActivity() const;
|
||||||
|
bool currentKeepsBluetoothAlive() const;
|
||||||
// True if BLE should be resident for the current context: any reader (page-turner
|
// True if BLE should be resident for the current context: any reader (page-turner
|
||||||
// input) or the Bluetooth settings screen (pairing) is on the stack.
|
// input) or the Bluetooth settings screen (pairing) is on the stack.
|
||||||
bool bluetoothShouldBeActive() const;
|
bool bluetoothShouldBeActive() const;
|
||||||
|
|||||||
@@ -871,9 +871,11 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
|||||||
// Free the BLE stack, build, then restore it. The chapter is cached afterwards, so
|
// Free the BLE stack, build, then restore it. The chapter is cached afterwards, so
|
||||||
// this recovery runs at most once per uncached chapter; BT reconnects in a few s.
|
// this recovery runs at most once per uncached chapter; BT reconnects in a few s.
|
||||||
LOG_INF("ERS", "Section build failed with Bluetooth on; freeing BLE RAM and retrying");
|
LOG_INF("ERS", "Section build failed with Bluetooth on; freeing BLE RAM and retrying");
|
||||||
|
bleinput::setLifecyclePaused(true);
|
||||||
bleinput::stop();
|
bleinput::stop();
|
||||||
built = buildSection();
|
built = buildSection();
|
||||||
const bool bleOk = bleinput::ensureStarted();
|
const bool bleOk = bleinput::ensureStarted();
|
||||||
|
bleinput::setLifecyclePaused(false);
|
||||||
LOG_INF("ERS", "BLE restart after build: begin=%d", bleOk);
|
LOG_INF("ERS", "BLE restart after build: begin=%d", bleOk);
|
||||||
// Hold the "BT Connecting..." popup until the remote re-links, then force the
|
// Hold the "BT Connecting..." popup until the remote re-links, then force the
|
||||||
// page render below onto the ghost-cleanup (HALF) path so the popup clears
|
// page render below onto the ghost-cleanup (HALF) path so the popup clears
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <BleKeyboardHost.h>
|
#include <BleKeyboardHost.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
#include <Logging.h>
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
@@ -49,10 +50,15 @@ void BluetoothSettingsActivity::rebuildMenuRows() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothSettingsActivity::startScanView() {
|
void BluetoothSettingsActivity::startScanView() {
|
||||||
|
LOG_INF("BLEUI", "scan view: begin running=%d scanning=%d devices=%u paired=%u", BleHid.isRunning(),
|
||||||
|
BleHid.isScanning(), BleHid.deviceCount(), BleHid.pairedCount());
|
||||||
view = View::Scan;
|
view = View::Scan;
|
||||||
scanIndex = 0;
|
scanIndex = 0;
|
||||||
awaitingConnect = false;
|
awaitingConnect = false;
|
||||||
|
lastLoggedScanState = false;
|
||||||
|
lastLoggedDeviceCount = 0xFF;
|
||||||
BleHid.startScan(kScanMs);
|
BleHid.startScan(kScanMs);
|
||||||
|
LOG_INF("BLEUI", "scan view: startScan requested scanning=%d devices=%u", BleHid.isScanning(), BleHid.deviceCount());
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,9 +182,18 @@ void BluetoothSettingsActivity::loop() {
|
|||||||
if (view == View::Menu) {
|
if (view == View::Menu) {
|
||||||
handleMenuConfirm();
|
handleMenuConfirm();
|
||||||
} else if (view == View::Scan) {
|
} else if (view == View::Scan) {
|
||||||
if (!awaitingConnect && scanIndex < BleHid.deviceCount()) {
|
const int count = BleHid.deviceCount();
|
||||||
|
if (!awaitingConnect && count == 0 && !BleHid.isScanning()) {
|
||||||
|
LOG_INF("BLEUI", "scan view: restart scan requested");
|
||||||
|
BleHid.startScan(kScanMs);
|
||||||
|
LOG_INF("BLEUI", "scan view: restart scan state scanning=%d devices=%u", BleHid.isScanning(),
|
||||||
|
BleHid.deviceCount());
|
||||||
|
requestUpdate();
|
||||||
|
} else if (!awaitingConnect && scanIndex < count) {
|
||||||
if (BleHid.isScanning()) BleHid.stopScan();
|
if (BleHid.isScanning()) BleHid.stopScan();
|
||||||
const auto& d = BleHid.device(static_cast<uint8_t>(scanIndex));
|
const auto& d = BleHid.device(static_cast<uint8_t>(scanIndex));
|
||||||
|
LOG_INF("BLEUI", "scan view: connect addr=%s name='%s' rssi=%d type=%u hid=%d conn=%d", d.addr, d.name,
|
||||||
|
d.rssi, d.addrType, d.hid, d.connectable);
|
||||||
awaitingConnect = true;
|
awaitingConnect = true;
|
||||||
setBanner(tr(STR_CONNECTING));
|
setBanner(tr(STR_CONNECTING));
|
||||||
BleHid.connect(d.addr);
|
BleHid.connect(d.addr);
|
||||||
@@ -189,7 +204,16 @@ void BluetoothSettingsActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The scan list changes as devices are discovered — keep repainting while active.
|
// The scan list changes as devices are discovered — keep repainting while active.
|
||||||
if (view == View::Scan && BleHid.isScanning()) requestUpdate();
|
if (view == View::Scan) {
|
||||||
|
const bool scanning = BleHid.isScanning();
|
||||||
|
const uint8_t deviceCount = BleHid.deviceCount();
|
||||||
|
if (scanning != lastLoggedScanState || deviceCount != lastLoggedDeviceCount) {
|
||||||
|
LOG_INF("BLEUI", "scan view: state scanning=%d devices=%u", scanning, deviceCount);
|
||||||
|
lastLoggedScanState = scanning;
|
||||||
|
lastLoggedDeviceCount = deviceCount;
|
||||||
|
}
|
||||||
|
if (scanning) requestUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string BluetoothSettingsActivity::deviceLabel(int index) const {
|
std::string BluetoothSettingsActivity::deviceLabel(int index) const {
|
||||||
@@ -270,7 +294,8 @@ void BluetoothSettingsActivity::render(RenderLock&&) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Button hints differ by view (Menu selects; Scan and Paired both connect).
|
// Button hints differ by view (Menu selects; Scan and Paired both connect).
|
||||||
const char* confirm = view == View::Menu ? tr(STR_SELECT) : tr(STR_CONNECT);
|
const bool scanCanRestart = view == View::Scan && BleHid.deviceCount() == 0 && !BleHid.isScanning();
|
||||||
|
const char* confirm = view == View::Menu ? tr(STR_SELECT) : scanCanRestart ? "Scan" : tr(STR_CONNECT);
|
||||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), confirm, tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
const auto labels = mappedInput.mapLabels(tr(STR_BACK), confirm, tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ class BluetoothSettingsActivity final : public Activity {
|
|||||||
// Guards the Paired view's hold-to-forget so it fires once per hold and suppresses
|
// Guards the Paired view's hold-to-forget so it fires once per hold and suppresses
|
||||||
// the tap-to-connect on the same press.
|
// the tap-to-connect on the same press.
|
||||||
bool pairedActionTaken = false;
|
bool pairedActionTaken = false;
|
||||||
|
bool lastLoggedScanState = false;
|
||||||
|
uint8_t lastLoggedDeviceCount = 0xFF;
|
||||||
|
|
||||||
void rebuildMenuRows();
|
void rebuildMenuRows();
|
||||||
void handleMenuConfirm();
|
void handleMenuConfirm();
|
||||||
|
|||||||
+25
-7
@@ -496,16 +496,34 @@ void setup() {
|
|||||||
void updateBluetoothLifecycle() {
|
void updateBluetoothLifecycle() {
|
||||||
const bool wanted =
|
const bool wanted =
|
||||||
SETTINGS.bluetoothEnabled && activityManager.bluetoothShouldBeActive() && WiFi.getMode() == WIFI_MODE_NULL;
|
SETTINGS.bluetoothEnabled && activityManager.bluetoothShouldBeActive() && WiFi.getMode() == WIFI_MODE_NULL;
|
||||||
|
if (wanted && !BleHid.isRunning() && bleinput::lifecyclePaused()) return;
|
||||||
if (wanted && !BleHid.isRunning()) {
|
if (wanted && !BleHid.isRunning()) {
|
||||||
bleinput::ensureStarted();
|
LOG_INF("BLELC", "start requested enabled=%u reader=%d settings=%d wifi=%d paired=%u heap=%u maxAlloc=%u",
|
||||||
// Single place BLE starts for normal use (reader entry, BT toggled on, etc.), so the
|
SETTINGS.bluetoothEnabled, activityManager.isReaderActivity(), activityManager.currentKeepsBluetoothAlive(),
|
||||||
// "BT Connecting..." popup shows uniformly. Then clear it with a ghost-cleanup (HALF)
|
WiFi.getMode(), BleHid.pairedCount(), ESP.getFreeHeap(), ESP.getMaxAllocHeap());
|
||||||
// refresh so a grayscale reader page doesn't ghost over the popup.
|
RenderLock renderLock;
|
||||||
bleinput::showConnectingUntilLinked(renderer, mappedInputManager);
|
if (!bleinput::ensureStarted()) {
|
||||||
activityManager.requestGhostCleanup();
|
LOG_ERR("BLELC", "start failed heap=%u maxAlloc=%u", ESP.getFreeHeap(), ESP.getMaxAllocHeap());
|
||||||
activityManager.requestUpdate();
|
return;
|
||||||
|
}
|
||||||
|
LOG_INF("BLELC", "started paired=%u heap=%u maxAlloc=%u", BleHid.pairedCount(), ESP.getFreeHeap(),
|
||||||
|
ESP.getMaxAllocHeap());
|
||||||
|
HalPowerManager::Lock powerLock;
|
||||||
|
const bool showReconnectPopup =
|
||||||
|
activityManager.isReaderActivity() && !activityManager.currentKeepsBluetoothAlive() && BleHid.pairedCount() > 0;
|
||||||
|
if (showReconnectPopup) {
|
||||||
|
// Single place BLE starts for reader reconnect. Then clear it with a
|
||||||
|
// ghost-cleanup (HALF) refresh so a grayscale page doesn't ghost over the
|
||||||
|
// popup.
|
||||||
|
bleinput::showConnectingUntilLinked(renderer, mappedInputManager);
|
||||||
|
activityManager.requestGhostCleanup();
|
||||||
|
activityManager.requestUpdate();
|
||||||
|
}
|
||||||
} else if (!wanted && BleHid.isRunning()) {
|
} else if (!wanted && BleHid.isRunning()) {
|
||||||
|
LOG_INF("BLELC", "stop requested enabled=%u active=%d wifi=%d heap=%u maxAlloc=%u", SETTINGS.bluetoothEnabled,
|
||||||
|
activityManager.bluetoothShouldBeActive(), WiFi.getMode(), ESP.getFreeHeap(), ESP.getMaxAllocHeap());
|
||||||
bleinput::stop();
|
bleinput::stop();
|
||||||
|
LOG_INF("BLELC", "stopped heap=%u maxAlloc=%u", ESP.getFreeHeap(), ESP.getMaxAllocHeap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user