2024-03-03 02:07:29 +00:00
|
|
|
#include "configuration.h"
|
|
|
|
|
|
|
|
#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
|
|
|
|
#include "EInkDynamicDisplay.h"
|
|
|
|
|
|
|
|
// Constructor
|
|
|
|
EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
|
|
|
: EInkDisplay(address, sda, scl, geometry, i2cBus)
|
|
|
|
{
|
|
|
|
// If tracking ghost pixels, grab memory
|
|
|
|
#ifdef EINK_LIMIT_GHOSTING_PX
|
|
|
|
dirtyPixels = new uint8_t[EInkDisplay::displayBufferSize](); // Init with zeros
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destructor
|
|
|
|
EInkDynamicDisplay::~EInkDynamicDisplay()
|
|
|
|
{
|
|
|
|
// If we were tracking ghost pixels, free the memory
|
|
|
|
#ifdef EINK_LIMIT_GHOSTING_PX
|
|
|
|
delete[] dirtyPixels;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// Screen requests a BACKGROUND frame
|
|
|
|
void EInkDynamicDisplay::display()
|
|
|
|
{
|
2024-03-09 13:30:16 +00:00
|
|
|
addFrameFlag(BACKGROUND);
|
2024-03-03 02:07:29 +00:00
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Screen requests a RESPONSIVE frame
|
|
|
|
bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit)
|
|
|
|
{
|
2024-03-09 13:30:16 +00:00
|
|
|
addFrameFlag(RESPONSIVE);
|
2024-03-03 02:07:29 +00:00
|
|
|
return update(); // (Unutilized) Base class promises to return true if update ran
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add flag for the next frame
|
2024-03-09 13:30:16 +00:00
|
|
|
void EInkDynamicDisplay::addFrameFlag(frameFlagTypes flag)
|
2024-03-03 02:07:29 +00:00
|
|
|
{
|
|
|
|
// OR the new flag into the existing flags
|
|
|
|
this->frameFlags = (frameFlagTypes)(this->frameFlags | flag);
|
|
|
|
}
|
|
|
|
|
|
|
|
// GxEPD2 code to set fast refresh
|
|
|
|
void EInkDynamicDisplay::configForFastRefresh()
|
|
|
|
{
|
|
|
|
// Variant-specific code can go here
|
|
|
|
#if defined(PRIVATE_HW)
|
|
|
|
#else
|
|
|
|
// Otherwise:
|
|
|
|
adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height());
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// GxEPD2 code to set full refresh
|
|
|
|
void EInkDynamicDisplay::configForFullRefresh()
|
|
|
|
{
|
|
|
|
// Variant-specific code can go here
|
|
|
|
#if defined(PRIVATE_HW)
|
|
|
|
#else
|
|
|
|
// Otherwise:
|
|
|
|
adafruitDisplay->setFullWindow();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run any relevant GxEPD2 code, so next update will use correct refresh type
|
|
|
|
void EInkDynamicDisplay::applyRefreshMode()
|
|
|
|
{
|
|
|
|
// Change from FULL to FAST
|
|
|
|
if (currentConfig == FULL && refresh == FAST) {
|
|
|
|
configForFastRefresh();
|
|
|
|
currentConfig = FAST;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Change from FAST back to FULL
|
|
|
|
else if (currentConfig == FAST && refresh == FULL) {
|
|
|
|
configForFullRefresh();
|
|
|
|
currentConfig = FULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update fastRefreshCount
|
|
|
|
void EInkDynamicDisplay::adjustRefreshCounters()
|
|
|
|
{
|
|
|
|
if (refresh == FAST)
|
|
|
|
fastRefreshCount++;
|
|
|
|
|
|
|
|
else if (refresh == FULL)
|
|
|
|
fastRefreshCount = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trigger the display update by calling base class
|
|
|
|
bool EInkDynamicDisplay::update()
|
|
|
|
{
|
2024-03-06 14:26:31 +00:00
|
|
|
// Detemine the refresh mode to use, and start the update
|
2024-03-03 02:07:29 +00:00
|
|
|
bool refreshApproved = determineMode();
|
|
|
|
if (refreshApproved)
|
|
|
|
EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
|
2024-03-06 14:26:31 +00:00
|
|
|
|
|
|
|
#if defined(HAS_EINK_ASYNCFULL)
|
|
|
|
if (refreshApproved)
|
|
|
|
endOrDetach(); // Either endUpdate() right now (fast refresh), or set the async flag (full refresh)
|
|
|
|
#endif
|
|
|
|
|
2024-03-09 14:07:13 +00:00
|
|
|
storeAndReset(); // Store the result of this loop for next time
|
2024-03-06 14:26:31 +00:00
|
|
|
return refreshApproved; // (Unutilized) Base class promises to return true if update ran
|
2024-03-03 02:07:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Assess situation, pick a refresh type
|
|
|
|
bool EInkDynamicDisplay::determineMode()
|
|
|
|
{
|
2024-03-09 14:43:07 +00:00
|
|
|
checkInitialized();
|
2024-03-06 14:26:31 +00:00
|
|
|
checkForPromotion();
|
|
|
|
#if defined(HAS_EINK_ASYNCFULL)
|
|
|
|
checkAsyncFullRefresh();
|
|
|
|
#endif
|
2024-03-03 02:07:29 +00:00
|
|
|
checkRateLimiting();
|
|
|
|
|
2024-03-06 14:26:31 +00:00
|
|
|
// If too soon for a new frame, or display busy, abort early
|
2024-03-09 14:07:13 +00:00
|
|
|
if (refresh == SKIPPED)
|
2024-03-03 02:07:29 +00:00
|
|
|
return false; // No refresh
|
|
|
|
|
|
|
|
// -- New frame is due --
|
|
|
|
|
|
|
|
resetRateLimiting(); // Once determineMode() ends, will have to wait again
|
|
|
|
hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check
|
2024-03-06 14:26:31 +00:00
|
|
|
LOG_DEBUG("determineMode(): "); // Begin log entry
|
2024-03-03 02:07:29 +00:00
|
|
|
|
|
|
|
// Once mode determined, any remaining checks will bypass
|
|
|
|
checkCosmetic();
|
|
|
|
checkDemandingFast();
|
|
|
|
#ifdef EINK_LIMIT_GHOSTING_PX
|
|
|
|
checkExcessiveGhosting();
|
|
|
|
#endif
|
|
|
|
checkFrameMatchesPrevious();
|
2024-03-09 14:38:39 +00:00
|
|
|
checkConsecutiveFastRefreshes();
|
2024-03-03 02:07:29 +00:00
|
|
|
checkFastRequested();
|
|
|
|
|
|
|
|
if (refresh == UNSPECIFIED)
|
|
|
|
LOG_WARN("There was a flaw in the determineMode() logic.\n");
|
|
|
|
|
|
|
|
// -- Decision has been reached --
|
|
|
|
applyRefreshMode();
|
|
|
|
adjustRefreshCounters();
|
|
|
|
|
|
|
|
#ifdef EINK_LIMIT_GHOSTING_PX
|
|
|
|
// Full refresh clears any ghosting
|
|
|
|
if (refresh == FULL)
|
|
|
|
resetGhostPixelTracking();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Return - call a refresh or not?
|
2024-03-09 14:07:13 +00:00
|
|
|
if (refresh == SKIPPED)
|
2024-03-03 02:07:29 +00:00
|
|
|
return false; // Don't trigger a refresh
|
2024-03-09 14:07:13 +00:00
|
|
|
else
|
2024-03-03 02:07:29 +00:00
|
|
|
return true; // Do trigger a refresh
|
2024-03-09 14:07:13 +00:00
|
|
|
}
|
|
|
|
|
2024-03-09 14:43:07 +00:00
|
|
|
// 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);
|
2024-03-03 02:07:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-06 14:26:31 +00:00
|
|
|
// Was a frame skipped (rate, display busy) that should have been a FAST refresh?
|
|
|
|
void EInkDynamicDisplay::checkForPromotion()
|
2024-03-03 02:07:29 +00:00
|
|
|
{
|
2024-03-06 14:26:31 +00:00
|
|
|
// If a frame was skipped (rate, display busy), then promote a BACKGROUND frame
|
|
|
|
// Because we DID want a RESPONSIVE/COSMETIC/DEMAND_FULL frame last time, we just didn't get it
|
|
|
|
|
|
|
|
switch (previousReason) {
|
|
|
|
case ASYNC_REFRESH_BLOCKED_DEMANDFAST:
|
2024-03-09 13:30:16 +00:00
|
|
|
addFrameFlag(DEMAND_FAST);
|
2024-03-06 14:26:31 +00:00
|
|
|
break;
|
|
|
|
case ASYNC_REFRESH_BLOCKED_COSMETIC:
|
2024-03-09 13:30:16 +00:00
|
|
|
addFrameFlag(COSMETIC);
|
2024-03-06 14:26:31 +00:00
|
|
|
break;
|
|
|
|
case ASYNC_REFRESH_BLOCKED_RESPONSIVE:
|
|
|
|
case EXCEEDED_RATELIMIT_FAST:
|
2024-03-09 13:30:16 +00:00
|
|
|
addFrameFlag(RESPONSIVE);
|
2024-03-06 14:26:31 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2024-03-03 02:07:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is it too soon for another frame of this type?
|
|
|
|
void EInkDynamicDisplay::checkRateLimiting()
|
|
|
|
{
|
|
|
|
uint32_t now = millis();
|
|
|
|
|
|
|
|
// Sanity check: millis() overflow - just let the update run..
|
|
|
|
if (previousRunMs > now)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Skip update: too soon for BACKGROUND
|
|
|
|
if (frameFlags == BACKGROUND) {
|
|
|
|
if (now - previousRunMs < EINK_LIMIT_RATE_BACKGROUND_SEC * 1000) {
|
|
|
|
refresh = SKIPPED;
|
|
|
|
reason = EXCEEDED_RATELIMIT_FULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No rate-limit for these special cases
|
|
|
|
if (frameFlags & COSMETIC || frameFlags & DEMAND_FAST)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Skip update: too soon for RESPONSIVE
|
|
|
|
if (frameFlags & RESPONSIVE) {
|
|
|
|
if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) {
|
|
|
|
refresh = SKIPPED;
|
|
|
|
reason = EXCEEDED_RATELIMIT_FAST;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is this frame COSMETIC (splash screens?)
|
|
|
|
void EInkDynamicDisplay::checkCosmetic()
|
|
|
|
{
|
|
|
|
// If a decision was already reached, don't run the check
|
|
|
|
if (refresh != UNSPECIFIED)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// A full refresh is requested for cosmetic purposes: we have a decision
|
|
|
|
if (frameFlags & COSMETIC) {
|
|
|
|
refresh = FULL;
|
|
|
|
reason = FLAGGED_COSMETIC;
|
2024-03-09 15:07:51 +00:00
|
|
|
LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC, frameFlags=0x%x\n", frameFlags);
|
2024-03-03 02:07:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is this a one-off special circumstance, where we REALLY want a fast refresh?
|
|
|
|
void EInkDynamicDisplay::checkDemandingFast()
|
|
|
|
{
|
|
|
|
// If a decision was already reached, don't run the check
|
|
|
|
if (refresh != UNSPECIFIED)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// A fast refresh is demanded: we have a decision
|
|
|
|
if (frameFlags & DEMAND_FAST) {
|
|
|
|
refresh = FAST;
|
|
|
|
reason = FLAGGED_DEMAND_FAST;
|
2024-03-09 15:07:51 +00:00
|
|
|
LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST, frameFlags=0x%x\n", frameFlags);
|
2024-03-03 02:07:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Does the new frame match the currently displayed image?
|
|
|
|
void EInkDynamicDisplay::checkFrameMatchesPrevious()
|
|
|
|
{
|
|
|
|
// If a decision was already reached, don't run the check
|
|
|
|
if (refresh != UNSPECIFIED)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If frame is *not* a duplicate, abort the check
|
|
|
|
if (imageHash != previousImageHash)
|
|
|
|
return;
|
|
|
|
|
|
|
|
#if !defined(EINK_BACKGROUND_USES_FAST)
|
|
|
|
// If BACKGROUND, and last update was FAST: redraw the same image in FULL (for display health + image quality)
|
|
|
|
if (frameFlags == BACKGROUND && fastRefreshCount > 0) {
|
|
|
|
refresh = FULL;
|
|
|
|
reason = REDRAW_WITH_FULL;
|
2024-03-09 15:07:51 +00:00
|
|
|
LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL, frameFlags=0x%x\n", frameFlags);
|
2024-03-03 02:07:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Not redrawn, not COSMETIC, not DEMAND_FAST
|
|
|
|
refresh = SKIPPED;
|
|
|
|
reason = FRAME_MATCHED_PREVIOUS;
|
2024-03-09 14:38:39 +00:00
|
|
|
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;
|
2024-03-09 15:07:51 +00:00
|
|
|
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH, frameFlags=0x%x\n", frameFlags);
|
2024-03-09 14:38:39 +00:00
|
|
|
}
|
2024-03-03 02:07:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// No objections, we can perform fast-refresh, if desired
|
|
|
|
void EInkDynamicDisplay::checkFastRequested()
|
|
|
|
{
|
|
|
|
if (refresh != UNSPECIFIED)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (frameFlags == BACKGROUND) {
|
|
|
|
#ifdef EINK_BACKGROUND_USES_FAST
|
|
|
|
// If we want BACKGROUND to use fast. (FULL only when a limit is hit)
|
|
|
|
refresh = FAST;
|
|
|
|
reason = BACKGROUND_USES_FAST;
|
2024-03-09 15:07:51 +00:00
|
|
|
LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount,
|
|
|
|
frameFlags);
|
2024-03-03 02:07:29 +00:00
|
|
|
#else
|
|
|
|
// If we do want to use FULL for BACKGROUND updates
|
|
|
|
refresh = FULL;
|
|
|
|
reason = FLAGGED_BACKGROUND;
|
|
|
|
LOG_DEBUG("refresh=FULL, reason=FLAGGED_BACKGROUND\n");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sanity: confirm that we did ask for a RESPONSIVE frame.
|
|
|
|
if (frameFlags & RESPONSIVE) {
|
|
|
|
refresh = FAST;
|
|
|
|
reason = NO_OBJECTIONS;
|
2024-03-09 15:07:51 +00:00
|
|
|
LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount, frameFlags);
|
2024-03-03 02:07:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset the timer used for rate-limiting
|
|
|
|
void EInkDynamicDisplay::resetRateLimiting()
|
|
|
|
{
|
|
|
|
previousRunMs = millis();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a hash of this frame, to compare against previous update
|
|
|
|
void EInkDynamicDisplay::hashImage()
|
|
|
|
{
|
|
|
|
imageHash = 0;
|
|
|
|
|
|
|
|
// Sum all bytes of the image buffer together
|
|
|
|
for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
|
|
|
|
imageHash += buffer[b];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the results of determineMode() for future use, and reset for next call
|
|
|
|
void EInkDynamicDisplay::storeAndReset()
|
|
|
|
{
|
2024-03-09 14:07:13 +00:00
|
|
|
previousFrameFlags = frameFlags;
|
2024-03-03 02:07:29 +00:00
|
|
|
previousRefresh = refresh;
|
|
|
|
previousReason = reason;
|
|
|
|
|
|
|
|
// Only store image hash if the display will update
|
|
|
|
if (refresh != SKIPPED) {
|
|
|
|
previousImageHash = imageHash;
|
|
|
|
}
|
|
|
|
|
|
|
|
frameFlags = BACKGROUND;
|
|
|
|
refresh = UNSPECIFIED;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef EINK_LIMIT_GHOSTING_PX
|
|
|
|
// Count how many ghost pixels the new image will display
|
|
|
|
void EInkDynamicDisplay::countGhostPixels()
|
|
|
|
{
|
|
|
|
// If a decision was already reached, don't run the check
|
|
|
|
if (refresh != UNSPECIFIED)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Start a new count
|
|
|
|
ghostPixelCount = 0;
|
|
|
|
|
|
|
|
// Check new image, bit by bit, for any white pixels at locations marked "dirty"
|
|
|
|
for (uint16_t i = 0; i < displayBufferSize; i++) {
|
|
|
|
for (uint8_t bit = 0; bit < 7; bit++) {
|
|
|
|
|
|
|
|
const bool dirty = (dirtyPixels[i] >> bit) & 1; // Has pixel location been drawn to since full-refresh?
|
|
|
|
const bool shouldBeBlank = !((buffer[i] >> bit) & 1); // Is pixel location white in the new image?
|
|
|
|
|
|
|
|
// If pixel is (or has been) black since last full-refresh, and now is white: ghosting
|
|
|
|
if (dirty && shouldBeBlank)
|
|
|
|
ghostPixelCount++;
|
|
|
|
|
|
|
|
// Update the dirty status for this pixel - will this location become a ghost if set white in future?
|
|
|
|
if (!dirty && !shouldBeBlank)
|
|
|
|
dirtyPixels[i] |= (1 << bit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG_DEBUG("ghostPixels=%hu, ", ghostPixelCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if ghost pixel count exceeds the defined limit
|
|
|
|
void EInkDynamicDisplay::checkExcessiveGhosting()
|
|
|
|
{
|
|
|
|
// If a decision was already reached, don't run the check
|
|
|
|
if (refresh != UNSPECIFIED)
|
|
|
|
return;
|
|
|
|
|
|
|
|
countGhostPixels();
|
|
|
|
|
|
|
|
// If too many ghost pixels, select full refresh
|
|
|
|
if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) {
|
|
|
|
refresh = FULL;
|
|
|
|
reason = EXCEEDED_GHOSTINGLIMIT;
|
2024-03-09 15:07:51 +00:00
|
|
|
LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT, frameFlags=0x%x\n", frameFlags);
|
2024-03-03 02:07:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the dirty pixels array. Call when full-refresh cleans the display.
|
|
|
|
void EInkDynamicDisplay::resetGhostPixelTracking()
|
|
|
|
{
|
|
|
|
// Copy the current frame into dirtyPixels[] from the display buffer
|
|
|
|
memcpy(dirtyPixels, EInkDisplay::buffer, EInkDisplay::displayBufferSize);
|
|
|
|
}
|
|
|
|
#endif // EINK_LIMIT_GHOSTING_PX
|
|
|
|
|
2024-03-06 14:26:31 +00:00
|
|
|
#ifdef HAS_EINK_ASYNCFULL
|
|
|
|
// Check the status of an "async full-refresh", and run the finish-up code if the hardware is ready
|
|
|
|
void EInkDynamicDisplay::checkAsyncFullRefresh()
|
|
|
|
{
|
|
|
|
// No refresh taking place, continue with determineMode()
|
|
|
|
if (!asyncRefreshRunning)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Full refresh still running
|
|
|
|
if (adafruitDisplay->epd2.isBusy()) {
|
|
|
|
// No refresh
|
|
|
|
refresh = SKIPPED;
|
|
|
|
|
|
|
|
// Set the reason, marking what type of frame we're skipping
|
|
|
|
if (frameFlags & DEMAND_FAST)
|
|
|
|
reason = ASYNC_REFRESH_BLOCKED_DEMANDFAST;
|
|
|
|
else if (frameFlags & COSMETIC)
|
|
|
|
reason = ASYNC_REFRESH_BLOCKED_COSMETIC;
|
|
|
|
else if (frameFlags & RESPONSIVE)
|
|
|
|
reason = ASYNC_REFRESH_BLOCKED_RESPONSIVE;
|
|
|
|
else
|
|
|
|
reason = ASYNC_REFRESH_BLOCKED_BACKGROUND;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we 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("Async full-refresh complete\n");
|
|
|
|
|
|
|
|
// Note: this code only works because of a modification to meshtastic/GxEPD2.
|
|
|
|
// It is only equipped to intercept calls to nextPage()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Figure out who runs the post-update code
|
|
|
|
void EInkDynamicDisplay::endOrDetach()
|
|
|
|
{
|
2024-03-09 14:48:59 +00:00
|
|
|
if (refresh == FULL) {
|
2024-03-06 14:26:31 +00:00
|
|
|
asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop.
|
2024-03-09 14:48:59 +00:00
|
|
|
|
|
|
|
if (frameFlags & BLOCKING)
|
|
|
|
awaitRefresh();
|
|
|
|
else
|
|
|
|
LOG_DEBUG("Async full-refresh begins\n");
|
2024-03-06 14:26:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fast Refresh
|
|
|
|
else
|
|
|
|
EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves.
|
|
|
|
}
|
2024-03-09 14:48:59 +00:00
|
|
|
|
|
|
|
// Hold control while an async refresh runs
|
|
|
|
void EInkDynamicDisplay::awaitRefresh()
|
|
|
|
{
|
|
|
|
// Continually poll the BUSY pin
|
|
|
|
while (adafruitDisplay->epd2.isBusy())
|
|
|
|
yield();
|
|
|
|
|
|
|
|
// End the full-refresh process
|
|
|
|
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
|
|
|
|
}
|
2024-03-06 14:26:31 +00:00
|
|
|
#endif // HAS_EINK_ASYNCFULL
|
|
|
|
|
2024-03-03 02:07:29 +00:00
|
|
|
#endif // USE_EINK_DYNAMICDISPLAY
|