Merge pull request #3356 from todd-herbert/eink-special-frames

Handle "special-frames" with EInkDynamicDisplay
This commit is contained in:
Thomas Göttgens 2024-03-11 21:43:39 +01:00 committed by GitHub
commit 6a27e62bcf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 124 additions and 61 deletions

View File

@ -25,19 +25,19 @@ EInkDynamicDisplay::~EInkDynamicDisplay()
// Screen requests a BACKGROUND frame // Screen requests a BACKGROUND frame
void EInkDynamicDisplay::display() void EInkDynamicDisplay::display()
{ {
setFrameFlag(BACKGROUND); addFrameFlag(BACKGROUND);
update(); update();
} }
// Screen requests a RESPONSIVE frame // Screen requests a RESPONSIVE frame
bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit) bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit)
{ {
setFrameFlag(RESPONSIVE); addFrameFlag(RESPONSIVE);
return update(); // (Unutilized) Base class promises to return true if update ran return update(); // (Unutilized) Base class promises to return true if update ran
} }
// Add flag for the next frame // Add flag for the next frame
void EInkDynamicDisplay::setFrameFlag(frameFlagTypes flag) void EInkDynamicDisplay::addFrameFlag(frameFlagTypes flag)
{ {
// OR the new flag into the existing flags // OR the new flag into the existing flags
this->frameFlags = (frameFlagTypes)(this->frameFlags | flag); this->frameFlags = (frameFlagTypes)(this->frameFlags | flag);
@ -96,20 +96,49 @@ bool EInkDynamicDisplay::update()
{ {
// Detemine the refresh mode to use, and start the update // Detemine the refresh mode to use, and start the update
bool refreshApproved = determineMode(); bool refreshApproved = determineMode();
if (refreshApproved) if (refreshApproved) {
EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
storeAndReset(); // Store the result of this loop for next time. Note: call *before* endOrDetach()
#if defined(HAS_EINK_ASYNCFULL) endOrDetach(); // endUpdate() right now, or set the async refresh flag (if FULL and HAS_EINK_ASYNCFULL)
if (refreshApproved) } else
endOrDetach(); // Either endUpdate() right now (fast refresh), or set the async flag (full refresh) storeAndReset(); // No update, no post-update code, just store the results
#endif
return refreshApproved; // (Unutilized) Base class promises to return true if update ran return refreshApproved; // (Unutilized) Base class promises to return true if update ran
} }
// Figure out who runs the post-update code
void EInkDynamicDisplay::endOrDetach()
{
// If the GxEPD2 version reports that it has the async modifications
#ifdef HAS_EINK_ASYNCFULL
if (previousRefresh == FULL) {
asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop.
if (previousFrameFlags & BLOCKING)
awaitRefresh();
else
LOG_DEBUG("Async full-refresh begins\n");
}
// Fast Refresh
else if (previousRefresh == FAST)
EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves.
// Fallback - If using an unmodified version of GxEPD2 for some reason
#else
if (previousRefresh == FULL || previousRefresh == FAST) { // If refresh wasn't skipped (on unspecified..)
LOG_WARN(
"GxEPD2 version has not been modified to support async refresh; using fallback behavior. Please update lib_deps in "
"variant's platformio.ini file\n");
EInkDisplay::endUpdate();
}
#endif
}
// Assess situation, pick a refresh type // Assess situation, pick a refresh type
bool EInkDynamicDisplay::determineMode() bool EInkDynamicDisplay::determineMode()
{ {
checkInitialized();
checkForPromotion(); checkForPromotion();
#if defined(HAS_EINK_ASYNCFULL) #if defined(HAS_EINK_ASYNCFULL)
checkAsyncFullRefresh(); checkAsyncFullRefresh();
@ -117,10 +146,8 @@ bool EInkDynamicDisplay::determineMode()
checkRateLimiting(); checkRateLimiting();
// If too soon for a new frame, or display busy, abort early // If too soon for a new frame, or display busy, abort early
if (refresh == SKIPPED) { if (refresh == SKIPPED)
storeAndReset();
return false; // No refresh return false; // No refresh
}
// -- New frame is due -- // -- New frame is due --
@ -131,11 +158,11 @@ bool EInkDynamicDisplay::determineMode()
// Once mode determined, any remaining checks will bypass // Once mode determined, any remaining checks will bypass
checkCosmetic(); checkCosmetic();
checkDemandingFast(); checkDemandingFast();
checkFrameMatchesPrevious();
checkConsecutiveFastRefreshes(); checkConsecutiveFastRefreshes();
#ifdef EINK_LIMIT_GHOSTING_PX #ifdef EINK_LIMIT_GHOSTING_PX
checkExcessiveGhosting(); checkExcessiveGhosting();
#endif #endif
checkFrameMatchesPrevious();
checkFastRequested(); checkFastRequested();
if (refresh == UNSPECIFIED) if (refresh == UNSPECIFIED)
@ -152,12 +179,27 @@ bool EInkDynamicDisplay::determineMode()
#endif #endif
// Return - call a refresh or not? // Return - call a refresh or not?
if (refresh == SKIPPED) { if (refresh == SKIPPED)
storeAndReset();
return false; // Don't trigger a refresh return false; // Don't trigger a refresh
} else { else
storeAndReset();
return true; // Do trigger a refresh return true; // Do trigger a refresh
}
// Is this the very first frame?
void EInkDynamicDisplay::checkInitialized()
{
if (!initialized) {
// Undo GxEPD2_BW::partialWindow(), if set by developer in EInkDisplay::connect()
configForFullRefresh();
// Clear any existing image, so we can draw logo with fast-refresh, but also to set GxEPD2_EPD::_initial_write
adafruitDisplay->clearScreen();
LOG_DEBUG("initialized, ");
initialized = true;
// Use a fast-refresh for the next frame; no skipping or else blank screen when waking from deep sleep
addFrameFlag(DEMAND_FAST);
} }
} }
@ -169,14 +211,14 @@ void EInkDynamicDisplay::checkForPromotion()
switch (previousReason) { switch (previousReason) {
case ASYNC_REFRESH_BLOCKED_DEMANDFAST: case ASYNC_REFRESH_BLOCKED_DEMANDFAST:
setFrameFlag(DEMAND_FAST); addFrameFlag(DEMAND_FAST);
break; break;
case ASYNC_REFRESH_BLOCKED_COSMETIC: case ASYNC_REFRESH_BLOCKED_COSMETIC:
setFrameFlag(COSMETIC); addFrameFlag(COSMETIC);
break; break;
case ASYNC_REFRESH_BLOCKED_RESPONSIVE: case ASYNC_REFRESH_BLOCKED_RESPONSIVE:
case EXCEEDED_RATELIMIT_FAST: case EXCEEDED_RATELIMIT_FAST:
setFrameFlag(RESPONSIVE); addFrameFlag(RESPONSIVE);
break; break;
default: default:
break; break;
@ -226,7 +268,7 @@ void EInkDynamicDisplay::checkCosmetic()
if (frameFlags & COSMETIC) { if (frameFlags & COSMETIC) {
refresh = FULL; refresh = FULL;
reason = FLAGGED_COSMETIC; reason = FLAGGED_COSMETIC;
LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC\n"); LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC, frameFlags=0x%x\n", frameFlags);
} }
} }
@ -241,22 +283,7 @@ void EInkDynamicDisplay::checkDemandingFast()
if (frameFlags & DEMAND_FAST) { if (frameFlags & DEMAND_FAST) {
refresh = FAST; refresh = FAST;
reason = FLAGGED_DEMAND_FAST; reason = FLAGGED_DEMAND_FAST;
LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST\n"); LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST, frameFlags=0x%x\n", frameFlags);
}
}
// Have too many fast-refreshes occured consecutively, since last full refresh?
void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
{
// If a decision was already reached, don't run the check
if (refresh != UNSPECIFIED)
return;
// If too many FAST refreshes consecutively - force a FULL refresh
if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
refresh = FULL;
reason = EXCEEDED_LIMIT_FASTREFRESH;
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH\n");
} }
} }
@ -276,7 +303,7 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious()
if (frameFlags == BACKGROUND && fastRefreshCount > 0) { if (frameFlags == BACKGROUND && fastRefreshCount > 0) {
refresh = FULL; refresh = FULL;
reason = REDRAW_WITH_FULL; reason = REDRAW_WITH_FULL;
LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL\n"); LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL, frameFlags=0x%x\n", frameFlags);
return; return;
} }
#endif #endif
@ -284,7 +311,22 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious()
// Not redrawn, not COSMETIC, not DEMAND_FAST // Not redrawn, not COSMETIC, not DEMAND_FAST
refresh = SKIPPED; refresh = SKIPPED;
reason = FRAME_MATCHED_PREVIOUS; reason = FRAME_MATCHED_PREVIOUS;
LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS\n"); LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x\n", frameFlags);
}
// Have too many fast-refreshes occured consecutively, since last full refresh?
void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
{
// If a decision was already reached, don't run the check
if (refresh != UNSPECIFIED)
return;
// If too many FAST refreshes consecutively - force a FULL refresh
if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
refresh = FULL;
reason = EXCEEDED_LIMIT_FASTREFRESH;
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH, frameFlags=0x%x\n", frameFlags);
}
} }
// No objections, we can perform fast-refresh, if desired // No objections, we can perform fast-refresh, if desired
@ -298,7 +340,8 @@ void EInkDynamicDisplay::checkFastRequested()
// If we want BACKGROUND to use fast. (FULL only when a limit is hit) // If we want BACKGROUND to use fast. (FULL only when a limit is hit)
refresh = FAST; refresh = FAST;
reason = BACKGROUND_USES_FAST; reason = BACKGROUND_USES_FAST;
LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu\n", fastRefreshCount); LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount,
frameFlags);
#else #else
// If we do want to use FULL for BACKGROUND updates // If we do want to use FULL for BACKGROUND updates
refresh = FULL; refresh = FULL;
@ -311,7 +354,7 @@ void EInkDynamicDisplay::checkFastRequested()
if (frameFlags & RESPONSIVE) { if (frameFlags & RESPONSIVE) {
refresh = FAST; refresh = FAST;
reason = NO_OBJECTIONS; reason = NO_OBJECTIONS;
LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu\n", fastRefreshCount); LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount, frameFlags);
} }
} }
@ -335,6 +378,7 @@ void EInkDynamicDisplay::hashImage()
// Store the results of determineMode() for future use, and reset for next call // Store the results of determineMode() for future use, and reset for next call
void EInkDynamicDisplay::storeAndReset() void EInkDynamicDisplay::storeAndReset()
{ {
previousFrameFlags = frameFlags;
previousRefresh = refresh; previousRefresh = refresh;
previousReason = reason; previousReason = reason;
@ -391,7 +435,7 @@ void EInkDynamicDisplay::checkExcessiveGhosting()
if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) { if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) {
refresh = FULL; refresh = FULL;
reason = EXCEEDED_GHOSTINGLIMIT; reason = EXCEEDED_GHOSTINGLIMIT;
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT\n"); LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT, frameFlags=0x%x\n", frameFlags);
} }
} }
@ -439,17 +483,17 @@ void EInkDynamicDisplay::checkAsyncFullRefresh()
// It is only equipped to intercept calls to nextPage() // It is only equipped to intercept calls to nextPage()
} }
// Figure out who runs the post-update code // Hold control while an async refresh runs
void EInkDynamicDisplay::endOrDetach() void EInkDynamicDisplay::awaitRefresh()
{ {
if (previousRefresh == FULL) { // Note: previousRefresh is the refresh from this loop. // Continually poll the BUSY pin
asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop. while (adafruitDisplay->epd2.isBusy())
LOG_DEBUG("Async full-refresh begins\n"); yield();
}
// Fast Refresh // End the full-refresh process
else adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves. EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
asyncRefreshRunning = false; // Unset the flag
} }
#endif // HAS_EINK_ASYNCFULL #endif // HAS_EINK_ASYNCFULL

View File

@ -28,8 +28,9 @@ class EInkDynamicDisplay : public EInkDisplay
RESPONSIVE = (1 << 1), // For frames via forceDisplay() RESPONSIVE = (1 << 1), // For frames via forceDisplay()
COSMETIC = (1 << 2), // For splashes COSMETIC = (1 << 2), // For splashes
DEMAND_FAST = (1 << 3), // Special case only DEMAND_FAST = (1 << 3), // Special case only
BLOCKING = (1 << 4), // Modifier - block while refresh runs
}; };
void setFrameFlag(frameFlagTypes flag); void addFrameFlag(frameFlagTypes flag);
// Set the correct frame flag, then call universal "update()" method // Set the correct frame flag, then call universal "update()" method
void display() override; void display() override;
@ -48,7 +49,6 @@ class EInkDynamicDisplay : public EInkDisplay
ASYNC_REFRESH_BLOCKED_COSMETIC, ASYNC_REFRESH_BLOCKED_COSMETIC,
ASYNC_REFRESH_BLOCKED_RESPONSIVE, ASYNC_REFRESH_BLOCKED_RESPONSIVE,
ASYNC_REFRESH_BLOCKED_BACKGROUND, ASYNC_REFRESH_BLOCKED_BACKGROUND,
DISPLAY_NOT_READY_FOR_FULL,
EXCEEDED_RATELIMIT_FAST, EXCEEDED_RATELIMIT_FAST,
EXCEEDED_RATELIMIT_FULL, EXCEEDED_RATELIMIT_FULL,
FLAGGED_COSMETIC, FLAGGED_COSMETIC,
@ -67,14 +67,16 @@ class EInkDynamicDisplay : public EInkDisplay
void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type
void adjustRefreshCounters(); // Update fastRefreshCount void adjustRefreshCounters(); // Update fastRefreshCount
bool update(); // Trigger the display update - determine mode, then call base class bool update(); // Trigger the display update - determine mode, then call base class
void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh()
// Checks as part of determineMode() // Checks as part of determineMode()
void checkInitialized(); // Is this the very first frame?
void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh? void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh?
void checkRateLimiting(); // Is this frame too soon? void checkRateLimiting(); // Is this frame too soon?
void checkCosmetic(); // Was the COSMETIC flag set? void checkCosmetic(); // Was the COSMETIC flag set?
void checkDemandingFast(); // Was the DEMAND_FAST flag set? void checkDemandingFast(); // Was the DEMAND_FAST flag set?
void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively?
void checkFrameMatchesPrevious(); // Does the new frame match the existing display image? void checkFrameMatchesPrevious(); // Does the new frame match the existing display image?
void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively?
void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND? void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND?
void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting
@ -82,14 +84,16 @@ class EInkDynamicDisplay : public EInkDisplay
void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call
// What we are determining for this frame // What we are determining for this frame
frameFlagTypes frameFlags = BACKGROUND; // Frame type(s) - determineMode() input frameFlagTypes frameFlags = BACKGROUND; // Frame characteristics - determineMode() input
refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output
reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used
// What happened last time determineMode() ran // What happened last time determineMode() ran
refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome frameFlagTypes previousFrameFlags = BACKGROUND; // (Previous) Frame flags
reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome
reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason
bool initialized = false; // Have we drawn at least one frame yet?
uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting) uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting)
uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed! uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed!
uint32_t previousImageHash = 0; // Hash of the previous update's frame uint32_t previousImageHash = 0; // Hash of the previous update's frame
@ -108,10 +112,16 @@ class EInkDynamicDisplay : public EInkDisplay
// Conditional - async full refresh - only with modified meshtastic/GxEPD2 // Conditional - async full refresh - only with modified meshtastic/GxEPD2
#if defined(HAS_EINK_ASYNCFULL) #if defined(HAS_EINK_ASYNCFULL)
void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready
void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh() void awaitRefresh(); // Hold control while an async refresh runs
void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay()
bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh() bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh()
#endif #endif
}; };
// Tidier calls to addFrameFlag() from outside class
#define EINK_ADD_FRAMEFLAG(display, flag) static_cast<EInkDynamicDisplay *>(display)->addFrameFlag(EInkDynamicDisplay::flag)
#else // !USE_EINK_DYNAMICDISPLAY
// Dummy-macro, removes the need for include guards
#define EINK_ADD_FRAMEFLAG(display, flag)
#endif #endif

View File

@ -260,6 +260,10 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
/// Used on eink displays while in deep sleep /// Used on eink displays while in deep sleep
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{ {
// Next frame should use full-refresh, and block while running, else device will sleep before async callback
EINK_ADD_FRAMEFLAG(display, COSMETIC);
EINK_ADD_FRAMEFLAG(display, BLOCKING);
drawIconScreen("Sleeping...", display, state, x, y); drawIconScreen("Sleeping...", display, state, x, y);
} }
#endif #endif
@ -1170,6 +1174,7 @@ int32_t Screen::runOnce()
break; break;
case Cmd::STOP_BLUETOOTH_PIN_SCREEN: case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
case Cmd::STOP_BOOT_SCREEN: case Cmd::STOP_BOOT_SCREEN:
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
setFrames(); setFrames();
break; break;
case Cmd::PRINT: case Cmd::PRINT:
@ -1350,6 +1355,7 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin)
{ {
LOG_DEBUG("showing bluetooth screen\n"); LOG_DEBUG("showing bluetooth screen\n");
showingNormalScreen = false; showingNormalScreen = false;
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
static FrameCallback frames[] = {drawFrameBluetooth}; static FrameCallback frames[] = {drawFrameBluetooth};
snprintf(btPIN, sizeof(btPIN), "%06u", pin); snprintf(btPIN, sizeof(btPIN), "%06u", pin);
@ -1367,6 +1373,7 @@ void Screen::handleShutdownScreen()
{ {
LOG_DEBUG("showing shutdown screen\n"); LOG_DEBUG("showing shutdown screen\n");
showingNormalScreen = false; showingNormalScreen = false;
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
drawFrameText(display, state, x, y, "Shutting down..."); drawFrameText(display, state, x, y, "Shutting down...");
@ -1380,6 +1387,7 @@ void Screen::handleRebootScreen()
{ {
LOG_DEBUG("showing reboot screen\n"); LOG_DEBUG("showing reboot screen\n");
showingNormalScreen = false; showingNormalScreen = false;
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
drawFrameText(display, state, x, y, "Rebooting..."); drawFrameText(display, state, x, y, "Rebooting...");
@ -1392,6 +1400,7 @@ void Screen::handleStartFirmwareUpdateScreen()
{ {
LOG_DEBUG("showing firmware screen\n"); LOG_DEBUG("showing firmware screen\n");
showingNormalScreen = false; showingNormalScreen = false;
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
static FrameCallback frames[] = {drawFrameFirmware}; static FrameCallback frames[] = {drawFrameFirmware};
setFrameImmediateDraw(frames); setFrameImmediateDraw(frames);

View File

@ -16,7 +16,7 @@ build_flags =
-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
https://github.com/meshtastic/GxEPD2/ https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
adafruit/Adafruit BusIO@^1.13.2 adafruit/Adafruit BusIO@^1.13.2
lewisxhe/PCF8563_Library@^1.0.1 lewisxhe/PCF8563_Library@^1.0.1
upload_speed = 115200 upload_speed = 115200

View File

@ -16,7 +16,7 @@ build_flags =
;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
https://github.com/meshtastic/GxEPD2/ https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
adafruit/Adafruit BusIO@^1.13.2 adafruit/Adafruit BusIO@^1.13.2
lewisxhe/PCF8563_Library@^1.0.1 lewisxhe/PCF8563_Library@^1.0.1
upload_speed = 115200 upload_speed = 115200

View File

@ -20,7 +20,7 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo>
lib_deps = lib_deps =
${nrf52840_base.lib_deps} ${nrf52840_base.lib_deps}
https://github.com/meshtastic/GxEPD2 https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
adafruit/Adafruit BusIO@^1.13.2 adafruit/Adafruit BusIO@^1.13.2
lewisxhe/PCF8563_Library@^1.0.1 lewisxhe/PCF8563_Library@^1.0.1
;upload_protocol = fs ;upload_protocol = fs