mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-20 09:43:03 +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:
@@ -262,14 +262,65 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
|
||||
#ifdef USE_EINK
|
||||
/// 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
|
||||
EINK_ADD_FRAMEFLAG(display, COSMETIC);
|
||||
EINK_ADD_FRAMEFLAG(display, BLOCKING);
|
||||
|
||||
LOG_DEBUG("Drawing deep sleep screen\n");
|
||||
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
|
||||
|
||||
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
@@ -948,18 +999,17 @@ Screen::~Screen()
|
||||
void Screen::doDeepSleep()
|
||||
{
|
||||
#ifdef USE_EINK
|
||||
static FrameCallback sleepFrames[] = {drawSleepScreen};
|
||||
static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]);
|
||||
ui->setFrames(sleepFrames, sleepFrameCount);
|
||||
ui->update();
|
||||
setOn(false, drawDeepSleepScreen);
|
||||
#ifdef PIN_EINK_EN
|
||||
digitalWrite(PIN_EINK_EN, LOW); // power off backlight
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
// Without E-Ink display:
|
||||
setOn(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Screen::handleSetOn(bool on)
|
||||
void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
||||
{
|
||||
if (!useDisplay)
|
||||
return;
|
||||
@@ -978,6 +1028,10 @@ void Screen::handleSetOn(bool on)
|
||||
setInterval(0); // Draw ASAP
|
||||
runASAP = true;
|
||||
} else {
|
||||
#ifdef USE_EINK
|
||||
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
|
||||
setScreensaverFrames(einkScreensaver);
|
||||
#endif
|
||||
LOG_INFO("Turning off screen\n");
|
||||
dispdev->displayOff();
|
||||
#ifdef T_WATCH_S3
|
||||
@@ -1028,6 +1082,7 @@ void Screen::setup()
|
||||
logo_timeout *= 2;
|
||||
|
||||
// Add frames.
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
|
||||
static FrameCallback bootFrames[] = {drawBootScreen};
|
||||
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
||||
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
|
||||
void Screen::setFrames()
|
||||
{
|
||||
@@ -1383,7 +1490,8 @@ void Screen::handleShutdownScreen()
|
||||
{
|
||||
LOG_DEBUG("showing shutdown screen\n");
|
||||
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 {
|
||||
drawFrameText(display, state, x, y, "Shutting down...");
|
||||
@@ -1391,6 +1499,7 @@ void Screen::handleShutdownScreen()
|
||||
static FrameCallback frames[] = {frame};
|
||||
|
||||
setFrameImmediateDraw(frames);
|
||||
forceDisplay();
|
||||
}
|
||||
|
||||
void Screen::handleRebootScreen()
|
||||
|
||||
Reference in New Issue
Block a user