fix: sleep from a WiFi activity instead of silent-rebooting (#2092)
Unify the two splash-skip signals (RTC silent-reboot flag, SD seamless-sleep flag) into one BootResume enum driving a single switch. Storage unchanged; behavior-preserving apart from the fix. Holding power to sleep from a WiFi activity (Font Download, OPDS, web server, Calibre, KOReader sync) rebooted to home instead of sleeping. goToSleep() runs the outgoing activity's onExit(), and those activities call silentRestart() to clear heap fragmentation, so the heap-defrag reboot fired before deep sleep could start. enterDeepSleep() now latches deepSleepInProgress before goToSleep(); silentRestart()/silentRestartToReader() no-op while it's set. Deep sleep is a full chip reset on wake, so it already clears the fragmentation the reboot existed for. Did you use AI tools to help write this code? partial
This commit is contained in:
+55
-20
@@ -142,7 +142,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)");
|
||||
@@ -156,6 +174,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)");
|
||||
@@ -251,6 +270,9 @@ void enterDeepSleep(bool fromTimeout = false) {
|
||||
|
||||
APP_STATE.saveToFile();
|
||||
|
||||
// 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);
|
||||
|
||||
if (isQuickResumeSleep) {
|
||||
@@ -400,27 +422,39 @@ 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);
|
||||
|
||||
setupDisplayAndFonts(isSilentReboot || /*seamless=*/!APP_STATE.showBootScreen);
|
||||
// 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;
|
||||
|
||||
// Silent reboot suppresses the boot splash and the X3 initial-full-sync
|
||||
// arming (see HalDisplay::begin), so the first Home paint is FAST_REFRESH
|
||||
// (~500ms) and input dispatches against the visible menu.
|
||||
if (!isSilentReboot) {
|
||||
if (APP_STATE.showBootScreen) {
|
||||
activityManager.goToBoot();
|
||||
} else if (loadSleepFrameBuffer()) {
|
||||
// Seamless wake: buffer restored, replace moon icon with loading icon
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
renderer.drawImage(LoadingIcon, 0, pageHeight - LOADINGICON_HEIGHT, LOADINGICON_WIDTH, LOADINGICON_HEIGHT);
|
||||
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||
APP_STATE.showBootScreen = true;
|
||||
APP_STATE.saveToFile();
|
||||
} else {
|
||||
// Frame buffer file missing — fall back to normal boot screen
|
||||
setupDisplayAndFonts(resume != BootResume::Splash);
|
||||
|
||||
switch (resume) {
|
||||
case BootResume::Silent:
|
||||
// Splash skipped: the routing block below picks the target activity; the
|
||||
// panel keeps showing the pre-reboot popup until that first paint lands.
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
if (recoveryFirmwareMode) {
|
||||
@@ -430,9 +464,10 @@ void setup() {
|
||||
} else if (HalSystem::isRebootFromPanic()) {
|
||||
// If we rebooted from a panic, go to crash report screen to show the panic info
|
||||
activityManager.goToCrashReport();
|
||||
} else if (isSilentReboot && snapshotTarget == SILENT_REBOOT_TARGET_READER && !APP_STATE.openEpubPath.empty()) {
|
||||
} else if (resume == BootResume::Silent && snapshotTarget == 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.
|
||||
@@ -451,7 +486,7 @@ void setup() {
|
||||
activityManager.goToReader(path);
|
||||
}
|
||||
|
||||
if (isSilentReboot) {
|
||||
if (resume == BootResume::Silent) {
|
||||
// Block until the first paint physically completes. refreshDisplay()
|
||||
// waits on the panel BUSY pin so when this returns the user can see the
|
||||
// new activity. Without the wait, an edge captured by gpio.update()
|
||||
|
||||
Reference in New Issue
Block a user