diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index f61cf891e..b396446fa 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -463,10 +463,33 @@ void EInkDynamicDisplay::onNotify(uint32_t notification) } #ifdef HAS_EINK_ASYNCFULL -// Run the post-update code if the hardware is ready +// Public: wait for an refresh already in progress, then run the post-update code. See Screen::setScreensaverFrames() +void EInkDynamicDisplay::joinAsyncRefresh() +{ + // If no async refresh running, nothing to do + if (!asyncRefreshRunning) + return; + + LOG_DEBUG("Joining an async refresh in progress\n"); + + // Continually poll the BUSY pin + while (adafruitDisplay->epd2.isBusy()) + yield(); + + // If asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done + adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code + EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override) + asyncRefreshRunning = false; // Unset the flag + LOG_DEBUG("Refresh complete\n"); + + // Note: this code only works because of a modification to meshtastic/GxEPD2. + // It is only equipped to intercept calls to nextPage() +} + +// Called from NotifiedWorkerThread. Run the post-update code if the hardware is ready void EInkDynamicDisplay::pollAsyncRefresh() { - // We shouldn't be here.. + // In theory, this condition should never be met if (!asyncRefreshRunning) return; diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 39953b62a..8f3ce205a 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -119,20 +119,30 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo // Conditional - async full refresh - only with modified meshtastic/GxEPD2 #if defined(HAS_EINK_ASYNCFULL) + public: + void joinAsyncRefresh(); // Main thread joins an async refresh already in progress. Blocks, then runs post-update code + + protected: void pollAsyncRefresh(); // Run the post-update code if the hardware is ready void checkBusyAsyncRefresh(); // Check if display is busy running an async full-refresh (rejecting new frames) void awaitRefresh(); // Hold control while an async refresh runs void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() bool asyncRefreshRunning = false; // Flag, checked by checkBusyAsyncRefresh() #else + public: + void joinAsyncRefresh() {} // Dummy method + + protected: void pollAsyncRefresh() {} // Dummy method. In theory, not reachable #endif }; -// Tidier calls to addFrameFlag() from outside class +// Hide the ugly casts used in Screen.cpp #define EINK_ADD_FRAMEFLAG(display, flag) static_cast(display)->addFrameFlag(EInkDynamicDisplay::flag) +#define EINK_JOIN_ASYNCREFRESH(display) static_cast(display)->joinAsyncRefresh() #else // !USE_EINK_DYNAMICDISPLAY // Dummy-macro, removes the need for include guards #define EINK_ADD_FRAMEFLAG(display, flag) +#define EINK_JOIN_ASYNCREFRESH(display) #endif \ No newline at end of file diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 52829d1f7..2087b8daf 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1349,6 +1349,12 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) static FrameCallback screensaverFrame; static OverlayCallback screensaverOverlay; +#if defined(HAS_EINK_ASYNCFULL) && defined(USE_EINK_DYNAMICDISPLAY) + // Join (await) a currently running async refresh, then run the post-update code. + // Avoid skipping of screensaver frame. Would otherwise be handled by NotifiedWorkerThread. + EINK_JOIN_ASYNCREFRESH(dispdev); +#endif + // If: one-off screensaver frame passed as argument. Handles doDeepSleep() if (einkScreensaver != NULL) { screensaverFrame = einkScreensaver; @@ -1370,10 +1376,9 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver) ui->update(); } while (ui->getUiState()->lastUpdate < startUpdate); -#ifndef USE_EINK_DYNAMICDISPLAY - // Retrofit to EInkDisplay class - delay(10); - screen->forceDisplay(); + // Old EInkDisplay class +#if !defined(USE_EINK_DYNAMICDISPLAY) + static_cast(dispdev)->forceDisplay(0); // Screen::forceDisplay(), but override rate-limit #endif // Prepare now for next frame, shown when display wakes @@ -1490,8 +1495,11 @@ void Screen::handleShutdownScreen() { LOG_DEBUG("showing shutdown screen\n"); showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Use fast-refresh for next frame, no skip please +#ifdef USE_EINK + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update + handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?) +#endif auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { drawFrameText(display, state, x, y, "Shutting down..."); @@ -1499,14 +1507,17 @@ void Screen::handleShutdownScreen() static FrameCallback frames[] = {frame}; setFrameImmediateDraw(frames); - forceDisplay(); } void Screen::handleRebootScreen() { LOG_DEBUG("showing reboot screen\n"); showingNormalScreen = false; - EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame +#ifdef USE_EINK + EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please + EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update + handleSetOn(true); // Power-on to show rebooting screen (PowerFSM should handle?) +#endif auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { drawFrameText(display, state, x, y, "Rebooting..."); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 971146012..d03ba4320 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -150,7 +150,7 @@ class Screen : public concurrency::OSThread // We handle off commands immediately, because they might be called because the CPU is shutting down handleSetOn(false, einkScreensaver); else - enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); + enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON}); } /** diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index e1914a184..759cbb404 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -1,5 +1,6 @@ #include "NRF52Bluetooth.h" #include "BluetoothCommon.h" +#include "PowerFSM.h" #include "configuration.h" #include "main.h" #include "mesh/PhoneAPI.h" @@ -318,6 +319,7 @@ void NRF52Bluetooth::onConnectionSecured(uint16_t conn_handle) bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) { LOG_INFO("BLE pairing process started with passkey %.3s %.3s\n", passkey, passkey + 3); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); screen->startBluetoothPinScreen(configuredPasskey); if (match_request) {