mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-15 09:32:08 +00:00
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:
parent
daa4d387c6
commit
8187fa7115
@ -184,7 +184,6 @@ bool EInkDisplay::connect()
|
|||||||
// Init GxEPD2
|
// Init GxEPD2
|
||||||
adafruitDisplay->init();
|
adafruitDisplay->init();
|
||||||
adafruitDisplay->setRotation(3);
|
adafruitDisplay->setRotation(3);
|
||||||
adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh)
|
|
||||||
}
|
}
|
||||||
#elif defined(PCA10059)
|
#elif defined(PCA10059)
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
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
|
// If tracking ghost pixels, grab memory
|
||||||
#ifdef EINK_LIMIT_GHOSTING_PX
|
#ifdef EINK_LIMIT_GHOSTING_PX
|
||||||
@ -112,12 +112,15 @@ void EInkDynamicDisplay::endOrDetach()
|
|||||||
// If the GxEPD2 version reports that it has the async modifications
|
// If the GxEPD2 version reports that it has the async modifications
|
||||||
#ifdef HAS_EINK_ASYNCFULL
|
#ifdef HAS_EINK_ASYNCFULL
|
||||||
if (previousRefresh == FULL) {
|
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)
|
if (previousFrameFlags & BLOCKING)
|
||||||
awaitRefresh();
|
awaitRefresh();
|
||||||
else
|
else {
|
||||||
LOG_DEBUG("Async full-refresh begins\n");
|
// Async begins
|
||||||
|
LOG_DEBUG("Async full-refresh begins (dropping frames)\n");
|
||||||
|
notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true); // Hand-off to NotifiedWorkerThread
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast Refresh
|
// Fast Refresh
|
||||||
@ -141,7 +144,7 @@ bool EInkDynamicDisplay::determineMode()
|
|||||||
checkInitialized();
|
checkInitialized();
|
||||||
checkForPromotion();
|
checkForPromotion();
|
||||||
#if defined(HAS_EINK_ASYNCFULL)
|
#if defined(HAS_EINK_ASYNCFULL)
|
||||||
checkAsyncFullRefresh();
|
checkBusyAsyncRefresh();
|
||||||
#endif
|
#endif
|
||||||
checkRateLimiting();
|
checkRateLimiting();
|
||||||
|
|
||||||
@ -252,6 +255,7 @@ void EInkDynamicDisplay::checkRateLimiting()
|
|||||||
if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) {
|
if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) {
|
||||||
refresh = SKIPPED;
|
refresh = SKIPPED;
|
||||||
reason = EXCEEDED_RATELIMIT_FAST;
|
reason = EXCEEDED_RATELIMIT_FAST;
|
||||||
|
LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x\n", frameFlags);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,9 +451,44 @@ void EInkDynamicDisplay::resetGhostPixelTracking()
|
|||||||
}
|
}
|
||||||
#endif // EINK_LIMIT_GHOSTING_PX
|
#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
|
#ifdef HAS_EINK_ASYNCFULL
|
||||||
// Check the status of an "async full-refresh", and run the finish-up code if the hardware is ready
|
// Run the post-update code if the hardware is ready
|
||||||
void EInkDynamicDisplay::checkAsyncFullRefresh()
|
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()
|
// No refresh taking place, continue with determineMode()
|
||||||
if (!asyncRefreshRunning)
|
if (!asyncRefreshRunning)
|
||||||
@ -472,15 +511,6 @@ void EInkDynamicDisplay::checkAsyncFullRefresh()
|
|||||||
|
|
||||||
return;
|
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
|
// Hold control while an async refresh runs
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "EInkDisplay2.h"
|
#include "EInkDisplay2.h"
|
||||||
#include "GxEPD2_BW.h"
|
#include "GxEPD2_BW.h"
|
||||||
|
#include "concurrency/NotifiedWorkerThread.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Derives from the EInkDisplay adapter class.
|
Derives from the EInkDisplay adapter class.
|
||||||
@ -14,7 +15,7 @@
|
|||||||
(Full, Fast, Skip)
|
(Full, Fast, Skip)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class EInkDynamicDisplay : public EInkDisplay
|
class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWorkerThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
@ -61,13 +62,20 @@ class EInkDynamicDisplay : public EInkDisplay
|
|||||||
REDRAW_WITH_FULL,
|
REDRAW_WITH_FULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum notificationTypes : uint8_t { // What was onNotify() called for
|
||||||
|
NONE = 0, // This behavior (NONE=0) is fixed by NotifiedWorkerThread class
|
||||||
|
DUE_POLL_ASYNCREFRESH = 1,
|
||||||
|
};
|
||||||
|
const uint32_t intervalPollAsyncRefresh = 100;
|
||||||
|
|
||||||
|
void onNotify(uint32_t notification) override; // Handle any async tasks - overrides NotifiedWorkerThread
|
||||||
void configForFastRefresh(); // GxEPD2 code to set fast-refresh
|
void configForFastRefresh(); // GxEPD2 code to set fast-refresh
|
||||||
void configForFullRefresh(); // GxEPD2 code to set full-refresh
|
void configForFullRefresh(); // GxEPD2 code to set full-refresh
|
||||||
bool determineMode(); // Assess situation, pick a refresh type
|
bool determineMode(); // Assess situation, pick a refresh type
|
||||||
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()
|
void endOrDetach(); // Run the post-update code, or delegate it off to checkBusyAsyncRefresh()
|
||||||
|
|
||||||
// Checks as part of determineMode()
|
// Checks as part of determineMode()
|
||||||
void checkInitialized(); // Is this the very first frame?
|
void checkInitialized(); // Is this the very first frame?
|
||||||
@ -111,10 +119,13 @@ 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 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 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 checkBusyAsyncRefresh()
|
||||||
|
#else
|
||||||
|
void pollAsyncRefresh() {} // Dummy method. In theory, not reachable
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -262,14 +262,65 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
|||||||
|
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
/// 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 drawDeepSleepScreen(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
|
// 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, COSMETIC);
|
||||||
EINK_ADD_FRAMEFLAG(display, BLOCKING);
|
EINK_ADD_FRAMEFLAG(display, BLOCKING);
|
||||||
|
|
||||||
|
LOG_DEBUG("Drawing deep sleep screen\n");
|
||||||
drawIconScreen("Sleeping...", display, state, x, y);
|
drawIconScreen("Sleeping...", display, state, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used on eink displays when screen updates are paused
|
||||||
|
static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("Drawing screensaver overlay\n");
|
||||||
|
|
||||||
|
EINK_ADD_FRAMEFLAG(display, COSMETIC); // Take the opportunity for a full-refresh
|
||||||
|
|
||||||
|
// Config
|
||||||
|
display->setFont(FONT_SMALL);
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
const char *pauseText = "Screen Paused";
|
||||||
|
const char *idText = owner.short_name;
|
||||||
|
constexpr uint16_t padding = 5;
|
||||||
|
constexpr uint8_t dividerGap = 1;
|
||||||
|
constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in.
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText));
|
||||||
|
const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText));
|
||||||
|
const uint16_t boxWidth = padding + idTextWidth + padding + padding + pauseTextWidth + padding;
|
||||||
|
const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding;
|
||||||
|
|
||||||
|
// Position
|
||||||
|
const int16_t boxLeft = (display->width() / 2) - (boxWidth / 2) + random(-imprecision, imprecision + 1);
|
||||||
|
// const int16_t boxRight = boxLeft + boxWidth - 1;
|
||||||
|
const int16_t boxTop = (display->height() / 2) - (boxHeight / 2 + random(-imprecision, imprecision + 1));
|
||||||
|
const int16_t boxBottom = boxTop + boxHeight - 1;
|
||||||
|
const int16_t idTextLeft = boxLeft + padding;
|
||||||
|
const int16_t idTextTop = boxTop + padding;
|
||||||
|
const int16_t pauseTextLeft = boxLeft + padding + idTextWidth + padding + padding;
|
||||||
|
const int16_t pauseTextTop = boxTop + padding;
|
||||||
|
const int16_t dividerX = boxLeft + padding + idTextWidth + padding;
|
||||||
|
const int16_t dividerTop = boxTop + 1 + dividerGap;
|
||||||
|
const int16_t dividerBottom = boxBottom - 1 - dividerGap;
|
||||||
|
|
||||||
|
// Draw: box
|
||||||
|
display->setColor(EINK_WHITE);
|
||||||
|
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Clear a slightly oversized area for the box
|
||||||
|
display->setColor(EINK_BLACK);
|
||||||
|
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
|
||||||
|
|
||||||
|
// Draw: Text
|
||||||
|
display->drawString(idTextLeft, idTextTop, idText);
|
||||||
|
display->drawString(pauseTextLeft, pauseTextTop, pauseText);
|
||||||
|
display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold
|
||||||
|
|
||||||
|
// Draw: divider
|
||||||
|
display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
@ -948,18 +999,17 @@ Screen::~Screen()
|
|||||||
void Screen::doDeepSleep()
|
void Screen::doDeepSleep()
|
||||||
{
|
{
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
static FrameCallback sleepFrames[] = {drawSleepScreen};
|
setOn(false, drawDeepSleepScreen);
|
||||||
static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]);
|
|
||||||
ui->setFrames(sleepFrames, sleepFrameCount);
|
|
||||||
ui->update();
|
|
||||||
#ifdef PIN_EINK_EN
|
#ifdef PIN_EINK_EN
|
||||||
digitalWrite(PIN_EINK_EN, LOW); // power off backlight
|
digitalWrite(PIN_EINK_EN, LOW); // power off backlight
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#else
|
||||||
|
// Without E-Ink display:
|
||||||
setOn(false);
|
setOn(false);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::handleSetOn(bool on)
|
void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||||
{
|
{
|
||||||
if (!useDisplay)
|
if (!useDisplay)
|
||||||
return;
|
return;
|
||||||
@ -978,6 +1028,10 @@ void Screen::handleSetOn(bool on)
|
|||||||
setInterval(0); // Draw ASAP
|
setInterval(0); // Draw ASAP
|
||||||
runASAP = true;
|
runASAP = true;
|
||||||
} else {
|
} else {
|
||||||
|
#ifdef USE_EINK
|
||||||
|
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
|
||||||
|
setScreensaverFrames(einkScreensaver);
|
||||||
|
#endif
|
||||||
LOG_INFO("Turning off screen\n");
|
LOG_INFO("Turning off screen\n");
|
||||||
dispdev->displayOff();
|
dispdev->displayOff();
|
||||||
#ifdef T_WATCH_S3
|
#ifdef T_WATCH_S3
|
||||||
@ -1028,6 +1082,7 @@ void Screen::setup()
|
|||||||
logo_timeout *= 2;
|
logo_timeout *= 2;
|
||||||
|
|
||||||
// Add frames.
|
// Add frames.
|
||||||
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
|
||||||
static FrameCallback bootFrames[] = {drawBootScreen};
|
static FrameCallback bootFrames[] = {drawBootScreen};
|
||||||
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
||||||
ui->setFrames(bootFrames, bootFrameCount);
|
ui->setFrames(bootFrames, bootFrameCount);
|
||||||
@ -1283,6 +1338,58 @@ void Screen::setWelcomeFrames()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_EINK
|
||||||
|
/// Determine which screensaver frame to use, then set the FrameCallback
|
||||||
|
void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
|
||||||
|
{
|
||||||
|
// Remember current frame, restore position at power-on
|
||||||
|
uint8_t frameNumber = ui->getUiState()->currentFrame;
|
||||||
|
|
||||||
|
// Retain specified frame / overlay callback beyond scope of this method
|
||||||
|
static FrameCallback screensaverFrame;
|
||||||
|
static OverlayCallback screensaverOverlay;
|
||||||
|
|
||||||
|
// If: one-off screensaver frame passed as argument. Handles doDeepSleep()
|
||||||
|
if (einkScreensaver != NULL) {
|
||||||
|
screensaverFrame = einkScreensaver;
|
||||||
|
ui->setFrames(&screensaverFrame, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, display the usual "overlay" screensaver
|
||||||
|
else {
|
||||||
|
screensaverOverlay = drawScreensaverOverlay;
|
||||||
|
ui->setOverlays(&screensaverOverlay, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request new frame, ASAP
|
||||||
|
setFastFramerate();
|
||||||
|
uint64_t startUpdate;
|
||||||
|
do {
|
||||||
|
startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
|
||||||
|
delay(1);
|
||||||
|
ui->update();
|
||||||
|
} while (ui->getUiState()->lastUpdate < startUpdate);
|
||||||
|
|
||||||
|
#ifndef USE_EINK_DYNAMICDISPLAY
|
||||||
|
// Retrofit to EInkDisplay class
|
||||||
|
delay(10);
|
||||||
|
screen->forceDisplay();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Prepare now for next frame, shown when display wakes
|
||||||
|
ui->setOverlays(NULL, 0); // Clear overlay
|
||||||
|
setFrames(); // Return to normal display updates
|
||||||
|
ui->switchToFrame(frameNumber); // Attempt to return to same frame after power-on
|
||||||
|
|
||||||
|
// Pick a refresh method, for when display wakes
|
||||||
|
#ifdef EINK_HASQUIRK_GHOSTING
|
||||||
|
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // Really ugly to see ghosting from "screen paused"
|
||||||
|
#else
|
||||||
|
EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE); // Really nice to wake screen with a fast-refresh
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// restore our regular frame list
|
// restore our regular frame list
|
||||||
void Screen::setFrames()
|
void Screen::setFrames()
|
||||||
{
|
{
|
||||||
@ -1383,7 +1490,8 @@ 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
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: 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
|
||||||
|
|
||||||
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...");
|
||||||
@ -1391,6 +1499,7 @@ void Screen::handleShutdownScreen()
|
|||||||
static FrameCallback frames[] = {frame};
|
static FrameCallback frames[] = {frame};
|
||||||
|
|
||||||
setFrameImmediateDraw(frames);
|
setFrameImmediateDraw(frames);
|
||||||
|
forceDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::handleRebootScreen()
|
void Screen::handleRebootScreen()
|
||||||
|
@ -73,6 +73,10 @@ class Screen
|
|||||||
#define MILES_TO_FEET 5280
|
#define MILES_TO_FEET 5280
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Intuitive colors. E-Ink display is inverted from OLED(?)
|
||||||
|
#define EINK_BLACK OLEDDISPLAY_COLOR::WHITE
|
||||||
|
#define EINK_WHITE OLEDDISPLAY_COLOR::BLACK
|
||||||
|
|
||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -139,12 +143,12 @@ class Screen : public concurrency::OSThread
|
|||||||
// Not thread safe - must be called before any other methods are called.
|
// Not thread safe - must be called before any other methods are called.
|
||||||
void setup();
|
void setup();
|
||||||
|
|
||||||
/// Turns the screen on/off.
|
/// Turns the screen on/off. Optionally, pass a custom screensaver frame for E-Ink
|
||||||
void setOn(bool on)
|
void setOn(bool on, FrameCallback einkScreensaver = NULL)
|
||||||
{
|
{
|
||||||
if (!on)
|
if (!on)
|
||||||
handleSetOn(
|
// We handle off commands immediately, because they might be called because the CPU is shutting down
|
||||||
false); // We handle off commands immediately, because they might be called because the CPU is shutting down
|
handleSetOn(false, einkScreensaver);
|
||||||
else
|
else
|
||||||
enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
|
enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
|
||||||
}
|
}
|
||||||
@ -321,6 +325,11 @@ class Screen : public concurrency::OSThread
|
|||||||
|
|
||||||
void setWelcomeFrames();
|
void setWelcomeFrames();
|
||||||
|
|
||||||
|
#ifdef USE_EINK
|
||||||
|
/// Draw an image to remain on E-Ink display after screen off
|
||||||
|
void setScreensaverFrames(FrameCallback einkScreensaver = NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Updates the UI.
|
/// Updates the UI.
|
||||||
//
|
//
|
||||||
@ -351,7 +360,7 @@ class Screen : public concurrency::OSThread
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Implementations of various commands, called from doTask().
|
// Implementations of various commands, called from doTask().
|
||||||
void handleSetOn(bool on);
|
void handleSetOn(bool on, FrameCallback einkScreensaver = NULL);
|
||||||
void handleOnPress();
|
void handleOnPress();
|
||||||
void handleShowNextFrame();
|
void handleShowNextFrame();
|
||||||
void handleShowPrevFrame();
|
void handleShowPrevFrame();
|
||||||
|
@ -14,6 +14,8 @@ build_flags =
|
|||||||
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
|
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
|
||||||
-D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
|
-D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
|
||||||
-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.
|
||||||
|
-D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
|
||||||
|
-D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32s3_base.lib_deps}
|
${esp32s3_base.lib_deps}
|
||||||
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
|
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
|
||||||
|
@ -14,6 +14,7 @@ build_flags =
|
|||||||
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
|
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
|
||||||
-D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
|
-D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
|
||||||
;-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.
|
||||||
|
-D EINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32s3_base.lib_deps}
|
${esp32s3_base.lib_deps}
|
||||||
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
|
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a
|
||||||
|
Loading…
Reference in New Issue
Block a user