E-Ink Screensaver (#3477)

* fix Wireless Paper double-clear screen at boot

* log when flooded with "responsive" frames

* show the "resuming" screen when waking from deep-sleep

* rename drawDeepSleepScreen
avoid future confusion with "Screen Paused" screen

* show a screensaver frame when screen off
The frame shown during deep sleep is now also passed through showScreensaverFrames()

* Add macros for E-Ink color values.
OLEDDISPLAY_COLOR is inverted. Result of light-mode on E-Ink vs dark-mode on OLED?

* adapt drawDeepSleepScreen to new screensaver convention

* Mark Wireless Paper V1.1 as having problems with ghosting
Any other issues can be marked in a similar way, then handled in code where relevant

* Change screensaver from fullscreen logo to overlay

* identify "quirks" rather than "problems"

* move async refresh polling from display() to a NotifiedWorkerThread

* Prevent skipping of deep-sleep screen
(Hopefully)

* Redesign screensaver overlay
Now displays short name

* Optimize refresh for different displays

* Support older EInkDisplay class

* Don't assume text alignment

* fix spelling of a quirk macro
(No impact to code, but avoids future issues)

* Handle impossibly unlikely millis() overflow error
Should have just let it go, but here we are..

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
This commit is contained in:
todd-herbert
2024-03-29 12:31:11 +13:00
committed by GitHub
parent daa4d387c6
commit 8187fa7115
7 changed files with 204 additions and 43 deletions

View File

@@ -5,7 +5,7 @@
// Constructor
EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
: EInkDisplay(address, sda, scl, geometry, i2cBus)
: EInkDisplay(address, sda, scl, geometry, i2cBus), NotifiedWorkerThread("EInkDynamicDisplay")
{
// If tracking ghost pixels, grab memory
#ifdef EINK_LIMIT_GHOSTING_PX
@@ -112,12 +112,15 @@ 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.
asyncRefreshRunning = true; // Set the flag - checked in determineMode(); cleared by onNotify()
if (previousFrameFlags & BLOCKING)
awaitRefresh();
else
LOG_DEBUG("Async full-refresh begins\n");
else {
// Async begins
LOG_DEBUG("Async full-refresh begins (dropping frames)\n");
notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true); // Hand-off to NotifiedWorkerThread
}
}
// Fast Refresh
@@ -141,7 +144,7 @@ bool EInkDynamicDisplay::determineMode()
checkInitialized();
checkForPromotion();
#if defined(HAS_EINK_ASYNCFULL)
checkAsyncFullRefresh();
checkBusyAsyncRefresh();
#endif
checkRateLimiting();
@@ -252,6 +255,7 @@ void EInkDynamicDisplay::checkRateLimiting()
if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) {
refresh = SKIPPED;
reason = EXCEEDED_RATELIMIT_FAST;
LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x\n", frameFlags);
return;
}
}
@@ -447,9 +451,44 @@ void EInkDynamicDisplay::resetGhostPixelTracking()
}
#endif // EINK_LIMIT_GHOSTING_PX
// Handle any asyc tasks
void EInkDynamicDisplay::onNotify(uint32_t notification)
{
// Which task
switch (notification) {
case DUE_POLL_ASYNCREFRESH:
pollAsyncRefresh();
break;
}
}
#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()
// Run the post-update code if the hardware is ready
void EInkDynamicDisplay::pollAsyncRefresh()
{
// We shouldn't be here..
if (!asyncRefreshRunning)
return;
// Still running, check back later
if (adafruitDisplay->epd2.isBusy()) {
// Schedule next call of pollAsyncRefresh()
NotifiedWorkerThread::notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true);
return;
}
// 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("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()
}
// Check the status of "async full-refresh"; skip if running
void EInkDynamicDisplay::checkBusyAsyncRefresh()
{
// No refresh taking place, continue with determineMode()
if (!asyncRefreshRunning)
@@ -472,15 +511,6 @@ void EInkDynamicDisplay::checkAsyncFullRefresh()
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()
}
// Hold control while an async refresh runs