This commit is contained in:
jpirnay
2026-05-21 23:29:35 +02:00
parent 5bb3980dc0
commit cb3cfb6b22
+59 -27
View File
@@ -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_HOME = 0;
constexpr uint32_t SILENT_REBOOT_TARGET_READER = 1; 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() { void silentRestart() {
if (deepSleepInProgress) return; // sleeping supersedes the heap-defrag reboot
silentRebootTarget = SILENT_REBOOT_TARGET_HOME; silentRebootTarget = SILENT_REBOOT_TARGET_HOME;
silentRebootMagic = SILENT_REBOOT_MAGIC; silentRebootMagic = SILENT_REBOOT_MAGIC;
LOG_DBG("MAIN", "Silent restart (target=home)"); LOG_DBG("MAIN", "Silent restart (target=home)");
@@ -144,6 +162,7 @@ void silentRestart() {
} }
void silentRestartToReader() { void silentRestartToReader() {
if (deepSleepInProgress) return; // sleeping supersedes the heap-defrag reboot
silentRebootTarget = SILENT_REBOOT_TARGET_READER; silentRebootTarget = SILENT_REBOOT_TARGET_READER;
silentRebootMagic = SILENT_REBOOT_MAGIC; silentRebootMagic = SILENT_REBOOT_MAGIC;
LOG_DBG("MAIN", "Silent restart (target=reader)"); LOG_DBG("MAIN", "Silent restart (target=reader)");
@@ -217,6 +236,10 @@ void enterDeepSleep(bool fromTimeout = false) {
WiFi.disconnect(true); WiFi.disconnect(true);
WiFi.mode(WIFI_OFF); 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); activityManager.goToSleep(fromTimeout);
// Persist the moon-icon-overlaid framebuffer after goToSleep() has painted it. // 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 // First serial output only here to avoid timing inconsistencies for power button press duration verification
LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION); LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION);
const bool seamlessWake = !isSilentReboot && !APP_STATE.showBootScreen; // Resolve the single boot-presentation decision. Skipping the splash also
setupDisplayAndFonts(seamlessWake); // 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) { setupDisplayAndFonts(resume != BootResume::Splash);
// 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 switch (resume) {
// flip pixels the SDK *thinks* changed, leaving the old screen visible. Force the case BootResume::Silent:
// first paint to HALF_REFRESH so the panel cleanly repaints; subsequent paints // After a silent reboot the panel still shows the previous session's pixels but
// resume FAST as normal. // the SDK's RED-RAM diff buffer was cleared by begin(). A FAST refresh would only
renderer.setNextDisplayRefreshMode(HalDisplay::HALF_REFRESH); // flip pixels the SDK *thinks* changed, leaving the old screen visible. Force the
} else if (seamlessWake && loadSleepFrameBuffer()) { // first paint to HALF_REFRESH so the panel cleanly repaints; subsequent paints
// Quick Resume wake: framebuffer restored. Overlay a small loading icon to signal // resume FAST as normal.
// the device is busy reaching the reader (replacing the moon icon painted at sleep). renderer.setNextDisplayRefreshMode(HalDisplay::HALF_REFRESH);
const auto pageHeight = renderer.getScreenHeight(); break;
renderer.drawImage(LoadingIcon, 0, pageHeight - LOADINGICON_HEIGHT, LOADINGICON_WIDTH, LOADINGICON_HEIGHT); case BootResume::QuickResume:
renderer.displayBuffer(HalDisplay::HALF_REFRESH); // One-shot flag: re-arm the splash for the next non-quick-resume boot. Save
// Next sleep (whether crash, cold boot, or anything that doesn't go through enterDeepSleep) // before any painting so a hang in the blocking paint path can't strand
// must fall back to a proper boot screen. // us in a quick-resume-with-no-frame loop on the next boot.
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.
APP_STATE.showBootScreen = true; APP_STATE.showBootScreen = true;
APP_STATE.saveToFile(); APP_STATE.saveToFile();
} if (loadSleepFrameBuffer()) {
activityManager.goToBoot(); // 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(); HalClock::restore();
@@ -460,10 +492,10 @@ void setup() {
// Skip normal home/reader routing: jump straight into the SD firmware picker. // Skip normal home/reader routing: jump straight into the SD firmware picker.
activityManager.replaceActivity( activityManager.replaceActivity(
std::make_unique<SdFirmwareUpdateActivity>(renderer, mappedInputManager, /*recoveryMode=*/true)); std::make_unique<SdFirmwareUpdateActivity>(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()) { !APP_STATE.openEpubPath.empty()) {
activityManager.goToReader(APP_STATE.openEpubPath); 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 // 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 // through to the sleep-wake "resume reader" logic, which fires on stale
// openEpubPath + lastSleepFromReader from a prior session. // openEpubPath + lastSleepFromReader from a prior session.