From cb3cfb6b2291428a9b2920e68902507ca7cda4bb Mon Sep 17 00:00:00 2001 From: jpirnay Date: Thu, 21 May 2026 23:29:35 +0200 Subject: [PATCH] Fixes --- src/main.cpp | 86 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index dfc3c235..2c3528a7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -135,7 +135,25 @@ constexpr uint32_t SILENT_REBOOT_MAGIC = 0xC1EAB007; constexpr uint32_t SILENT_REBOOT_TARGET_HOME = 0; constexpr uint32_t SILENT_REBOOT_TARGET_READER = 1; +// How the device is coming back to life, resolved once at boot. Both resume +// flows suppress the splash and leave the panel holding its pre-boot frame; a +// plain boot shows the splash. See setup() for the resolution. +enum class BootResume : uint8_t { + Splash, // cold boot, flash, panic, or plain reboot + Silent, // heap-defrag ESP.restart() (RTC flag; lost on power loss) + QuickResume, // wake from a quick-resume deep sleep (SD flag; survives power loss) +}; + +// Latched true once enterDeepSleep() commits to sleeping, before it tears down +// the current activity. WiFi activities call silentRestart() in onExit() to +// clear heap fragmentation on the way out, but deep sleep is a full chip reset +// on wake and already clears the heap, so rebooting here would just power the +// device back up against the user's sleep gesture. Never cleared: +// startDeepSleep() does not return, so a set latch only ends at the wakeup reset. +static bool deepSleepInProgress = false; + void silentRestart() { + if (deepSleepInProgress) return; // sleeping supersedes the heap-defrag reboot silentRebootTarget = SILENT_REBOOT_TARGET_HOME; silentRebootMagic = SILENT_REBOOT_MAGIC; LOG_DBG("MAIN", "Silent restart (target=home)"); @@ -144,6 +162,7 @@ void silentRestart() { } void silentRestartToReader() { + if (deepSleepInProgress) return; // sleeping supersedes the heap-defrag reboot silentRebootTarget = SILENT_REBOOT_TARGET_READER; silentRebootMagic = SILENT_REBOOT_MAGIC; LOG_DBG("MAIN", "Silent restart (target=reader)"); @@ -217,6 +236,10 @@ void enterDeepSleep(bool fromTimeout = false) { WiFi.disconnect(true); WiFi.mode(WIFI_OFF); } + + // Commit to sleeping before goToSleep() runs the outgoing activity's onExit(): + // a WiFi activity would otherwise silentRestart() here and reboot instead. + deepSleepInProgress = true; activityManager.goToSleep(fromTimeout); // Persist the moon-icon-overlaid framebuffer after goToSleep() has painted it. @@ -421,34 +444,43 @@ void setup() { // First serial output only here to avoid timing inconsistencies for power button press duration verification LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION); - const bool seamlessWake = !isSilentReboot && !APP_STATE.showBootScreen; - setupDisplayAndFonts(seamlessWake); + // Resolve the single boot-presentation decision. Skipping the splash also + // skips the panel-clearing pass and the X3 initial-full-sync arming (see + // HalDisplay::begin), so the first paint is FAST_REFRESH (~500ms) over the + // retained frame and input dispatches against a visible UI. + const BootResume resume = isSilentReboot ? BootResume::Silent + : !APP_STATE.showBootScreen ? BootResume::QuickResume + : BootResume::Splash; - if (isSilentReboot) { - // After a silent reboot the panel still shows the previous session's pixels but - // the SDK's RED-RAM diff buffer was cleared by begin(). A FAST refresh would only - // flip pixels the SDK *thinks* changed, leaving the old screen visible. Force the - // first paint to HALF_REFRESH so the panel cleanly repaints; subsequent paints - // resume FAST as normal. - renderer.setNextDisplayRefreshMode(HalDisplay::HALF_REFRESH); - } else if (seamlessWake && loadSleepFrameBuffer()) { - // Quick Resume wake: framebuffer restored. Overlay a small loading icon to signal - // the device is busy reaching the reader (replacing the moon icon painted at sleep). - const auto pageHeight = renderer.getScreenHeight(); - renderer.drawImage(LoadingIcon, 0, pageHeight - LOADINGICON_HEIGHT, LOADINGICON_WIDTH, LOADINGICON_HEIGHT); - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - // Next sleep (whether crash, cold boot, or anything that doesn't go through enterDeepSleep) - // must fall back to a proper boot screen. - APP_STATE.showBootScreen = true; - APP_STATE.saveToFile(); - } else { - if (seamlessWake) { - // We promised a seamless wake but the framebuffer file was missing or unreadable — - // reset the flag so future cycles don't hit the same bad state. + setupDisplayAndFonts(resume != BootResume::Splash); + + switch (resume) { + case BootResume::Silent: + // After a silent reboot the panel still shows the previous session's pixels but + // the SDK's RED-RAM diff buffer was cleared by begin(). A FAST refresh would only + // flip pixels the SDK *thinks* changed, leaving the old screen visible. Force the + // first paint to HALF_REFRESH so the panel cleanly repaints; subsequent paints + // resume FAST as normal. + renderer.setNextDisplayRefreshMode(HalDisplay::HALF_REFRESH); + break; + case BootResume::QuickResume: + // One-shot flag: re-arm the splash for the next non-quick-resume boot. Save + // before any painting so a hang in the blocking paint path can't strand + // us in a quick-resume-with-no-frame loop on the next boot. APP_STATE.showBootScreen = true; APP_STATE.saveToFile(); - } - activityManager.goToBoot(); + if (loadSleepFrameBuffer()) { + // Frame restored: swap the sleep moon for the loading icon. + const auto pageHeight = renderer.getScreenHeight(); + renderer.drawImage(LoadingIcon, 0, pageHeight - LOADINGICON_HEIGHT, LOADINGICON_WIDTH, LOADINGICON_HEIGHT); + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + } else { + activityManager.goToBoot(); // frame file missing, fall back to the splash + } + break; + case BootResume::Splash: + activityManager.goToBoot(); + break; } HalClock::restore(); @@ -460,10 +492,10 @@ void setup() { // Skip normal home/reader routing: jump straight into the SD firmware picker. activityManager.replaceActivity( std::make_unique(renderer, mappedInputManager, /*recoveryMode=*/true)); - } else if (isSilentReboot && silentRebootTargetSnapshot == SILENT_REBOOT_TARGET_READER && + } else if (resume == BootResume::Silent && silentRebootTargetSnapshot == SILENT_REBOOT_TARGET_READER && !APP_STATE.openEpubPath.empty()) { activityManager.goToReader(APP_STATE.openEpubPath); - } else if (isSilentReboot) { + } else if (resume == BootResume::Silent) { // target == home (or reader with no open book): land on home — don't fall // through to the sleep-wake "resume reader" logic, which fires on stale // openEpubPath + lastSleepFromReader from a prior session.