Add quick resume
This commit is contained in:
@@ -40,6 +40,12 @@ void enforceFixedShortActions(CrossPointSettings& settings) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void CrossPointSettings::normalizeDependentSettings(CrossPointSettings& settings) {
|
||||
if (settings.sleepScreen == SLEEP_SCREEN_MODE::QUICK_RESUME) {
|
||||
settings.quickResumeSleepScreen = QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
void CrossPointSettings::validateFrontButtonMapping(CrossPointSettings& settings) {
|
||||
const uint8_t mapping[] = {settings.frontButtonBack, settings.frontButtonConfirm, settings.frontButtonLeft,
|
||||
settings.frontButtonRight};
|
||||
|
||||
@@ -25,6 +25,7 @@ class CrossPointSettings {
|
||||
BLANK = 4,
|
||||
COVER_CUSTOM = 5,
|
||||
OVERLAY = 6,
|
||||
QUICK_RESUME = 7,
|
||||
SLEEP_SCREEN_MODE_COUNT
|
||||
};
|
||||
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, SLEEP_SCREEN_COVER_MODE_COUNT };
|
||||
@@ -35,6 +36,11 @@ class CrossPointSettings {
|
||||
INVERTED_BLACK_AND_WHITE = 2,
|
||||
SLEEP_SCREEN_COVER_FILTER_COUNT
|
||||
};
|
||||
enum QUICK_RESUME_SLEEP_SCREEN {
|
||||
QUICK_RESUME_NEVER = 0,
|
||||
QUICK_RESUME_AFTER_TIMEOUT = 1,
|
||||
QUICK_RESUME_SLEEP_SCREEN_COUNT
|
||||
};
|
||||
|
||||
// Status bar enum - legacy
|
||||
enum STATUS_BAR_MODE {
|
||||
@@ -198,6 +204,9 @@ class CrossPointSettings {
|
||||
uint8_t sleepCoverOverlay = 0;
|
||||
// Sleep image pick mode (random vs sequential walk-through)
|
||||
uint8_t sleepImagePickMode = PICK_RANDOM;
|
||||
// Quick Resume on Timeout: keep current page on display with a moon icon when sleeping by timeout,
|
||||
// and on wake restore the page directly (skipping the boot screen).
|
||||
uint8_t quickResumeSleepScreen = QUICK_RESUME_NEVER;
|
||||
// Status bar settings (statusBar, statusBarProgressBar, statusBarProgressBarThickness retained for migration only)
|
||||
uint8_t statusBar = FULL;
|
||||
uint8_t statusBarChapterPageCount = 1;
|
||||
@@ -379,6 +388,10 @@ class CrossPointSettings {
|
||||
|
||||
static void validateFrontButtonMapping(CrossPointSettings& settings);
|
||||
|
||||
// Enforce settings whose values depend on others (e.g. sleepScreen=QUICK_RESUME implies
|
||||
// quickResumeSleepScreen=ON). Call after any setting mutation that could invalidate the pair.
|
||||
static void normalizeDependentSettings(CrossPointSettings& settings);
|
||||
|
||||
float getReaderLineCompression() const;
|
||||
unsigned long getSleepTimeoutMs() const;
|
||||
int getRefreshFrequency() const;
|
||||
|
||||
@@ -96,6 +96,9 @@ class CrossPointState {
|
||||
uint8_t readerActivityLoadCount = 0;
|
||||
bool lastSleepFromReader = false;
|
||||
bool recentBooksGridView = false; // true = grid/thumbnail view, false = list view
|
||||
// When false, setup() skips the boot screen on wake and instead restores the
|
||||
// saved framebuffer overlaid with a loading icon (Quick Resume).
|
||||
bool showBootScreen = true;
|
||||
KOReaderSyncSessionState koReaderSyncSession;
|
||||
PendingBookmarkJumpState pendingBookmarkJump;
|
||||
~CrossPointState() = default;
|
||||
|
||||
@@ -87,6 +87,7 @@ bool JsonSettingsIO::saveState(const CrossPointState& s, const char* path) {
|
||||
doc["readerActivityLoadCount"] = s.readerActivityLoadCount;
|
||||
doc["lastSleepFromReader"] = s.lastSleepFromReader;
|
||||
doc["recentBooksGridView"] = s.recentBooksGridView;
|
||||
doc["showBootScreen"] = s.showBootScreen;
|
||||
// Information about a pending KOReader sync session
|
||||
JsonObject sync = doc["koReaderSyncSession"].to<JsonObject>();
|
||||
sync["active"] = s.koReaderSyncSession.active;
|
||||
@@ -146,6 +147,7 @@ bool JsonSettingsIO::loadState(CrossPointState& s, const char* json) {
|
||||
s.readerActivityLoadCount = doc["readerActivityLoadCount"] | (uint8_t)0;
|
||||
s.lastSleepFromReader = doc["lastSleepFromReader"] | false;
|
||||
s.recentBooksGridView = doc["recentBooksGridView"] | false;
|
||||
s.showBootScreen = doc["showBootScreen"] | true;
|
||||
JsonObject sync = doc["koReaderSyncSession"].as<JsonObject>();
|
||||
s.koReaderSyncSession.active = sync["active"] | false;
|
||||
s.koReaderSyncSession.epubPath = sync["epubPath"] | std::string("");
|
||||
@@ -344,6 +346,10 @@ bool JsonSettingsIO::loadSettings(CrossPointSettings& s, const char* json, bool*
|
||||
s.moveFinishedBooksToCompleted = doc["moveFinishedBooksToCompleted"] | (uint8_t)0;
|
||||
s.removeFinishedBooksFromRecents = doc["removeFinishedBooksFromRecents"] | (uint8_t)0;
|
||||
|
||||
const uint8_t quickResumeBeforeNormalize = s.quickResumeSleepScreen;
|
||||
CrossPointSettings::normalizeDependentSettings(s);
|
||||
if (s.quickResumeSleepScreen != quickResumeBeforeNormalize && needsResave) *needsResave = true;
|
||||
|
||||
LOG_DBG("CPS", "Settings loaded from file");
|
||||
|
||||
return true;
|
||||
|
||||
+3
-1
@@ -54,7 +54,7 @@ inline const std::vector<SettingInfo> list = {
|
||||
"sleepTimeout", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_SLEEP_SCREEN, &CrossPointSettings::sleepScreen,
|
||||
{StrId::STR_DARK, StrId::STR_LIGHT, StrId::STR_CUSTOM, StrId::STR_COVER, StrId::STR_NONE_OPT,
|
||||
StrId::STR_COVER_CUSTOM, StrId::STR_PAGE_OVERLAY},
|
||||
StrId::STR_COVER_CUSTOM, StrId::STR_PAGE_OVERLAY, StrId::STR_QUICK_RESUME},
|
||||
"sleepScreen", StrId::STR_CAT_DISPLAY)
|
||||
.withSubcategory(StrId::STR_MENU_DISP_SLEEP),
|
||||
SettingInfo::Enum(StrId::STR_SLEEP_COVER_MODE, &CrossPointSettings::sleepScreenCoverMode,
|
||||
@@ -68,6 +68,8 @@ inline const std::vector<SettingInfo> list = {
|
||||
"sleepCoverOverlay", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_SLEEP_IMAGE_PICK_MODE, &CrossPointSettings::sleepImagePickMode,
|
||||
{StrId::STR_RANDOM, StrId::STR_SEQUENTIAL}, "sleepImagePickMode", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_QUICK_RESUME_TIMEOUT, &CrossPointSettings::quickResumeSleepScreen,
|
||||
{StrId::STR_STATE_OFF, StrId::STR_STATE_ON}, "quickResumeSleepScreen", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage,
|
||||
{StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage",
|
||||
StrId::STR_CAT_DISPLAY)
|
||||
|
||||
@@ -351,8 +351,8 @@ void ActivityManager::returnFromChild() {
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityManager::goToSleep() {
|
||||
replaceActivity(std::make_unique<SleepActivity>(renderer, mappedInput));
|
||||
void ActivityManager::goToSleep(bool fromTimeout) {
|
||||
replaceActivity(std::make_unique<SleepActivity>(renderer, mappedInput, fromTimeout));
|
||||
loop(); // Important: sleep screen must be rendered immediately, the caller will go to sleep right after this returns
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,8 @@ class ActivityManager {
|
||||
void goToBrowser();
|
||||
void goToReader(std::string path);
|
||||
void goToKOReaderSync();
|
||||
void goToSleep();
|
||||
// fromTimeout=true marks this as an auto-sleep (used to gate Quick Resume on Timeout).
|
||||
void goToSleep(bool fromTimeout = false);
|
||||
void goToBoot();
|
||||
void goToFullScreenMessage(std::string message, EpdFontFamily::Style style = EpdFontFamily::REGULAR);
|
||||
void goToWeather();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "images/Logo120.h"
|
||||
#include "images/MoonIcon.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -353,6 +354,18 @@ size_t pickSleepImageIndex(size_t numFiles) {
|
||||
void SleepActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
RenderLock lock(*this);
|
||||
|
||||
// Quick Resume: paint a moon icon over the current page and keep the framebuffer
|
||||
// intact for the next wake. Applies always when the user picked Quick Resume as
|
||||
// sleep screen, or only on timeout sleeps when "Quick Resume on Timeout" is on.
|
||||
const bool renderQuickResume =
|
||||
SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::QUICK_RESUME ||
|
||||
(fromTimeout &&
|
||||
SETTINGS.quickResumeSleepScreen == CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT);
|
||||
if (renderQuickResume) {
|
||||
return renderLastScreenSleepScreen();
|
||||
}
|
||||
|
||||
// For OVERLAY mode the popup is suppressed so the frame buffer (reader page) stays intact
|
||||
if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::OVERLAY) {
|
||||
GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP));
|
||||
@@ -804,6 +817,15 @@ void SleepActivity::renderBlankSleepScreen() const {
|
||||
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||
}
|
||||
|
||||
void SleepActivity::renderLastScreenSleepScreen() const {
|
||||
// Keep whatever is currently in the framebuffer (the reader page) and overlay a small moon
|
||||
// icon to signal sleep. main.cpp persists the framebuffer to SD so the next wake can restore
|
||||
// it before the boot screen would otherwise paint.
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
renderer.drawImage(MoonIcon, 0, pageHeight - MOONICON_HEIGHT, MOONICON_WIDTH, MOONICON_HEIGHT);
|
||||
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||
}
|
||||
|
||||
void SleepActivity::renderOverlaySleepScreen() const {
|
||||
// Overlay pictures always use portrait orientation regardless of the reader's orientation preference.
|
||||
const auto savedOrientation = renderer.getOrientation();
|
||||
|
||||
@@ -16,8 +16,8 @@ struct BookOverlayInfo {
|
||||
|
||||
class SleepActivity final : public Activity {
|
||||
public:
|
||||
explicit SleepActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
: Activity("Sleep", renderer, mappedInput) {}
|
||||
explicit SleepActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, bool fromTimeout = false)
|
||||
: Activity("Sleep", renderer, mappedInput), fromTimeout(fromTimeout) {}
|
||||
void onEnter() override;
|
||||
|
||||
private:
|
||||
@@ -28,5 +28,9 @@ class SleepActivity final : public Activity {
|
||||
bool topAlignForCoverFit = false) const;
|
||||
void renderBlankSleepScreen() const;
|
||||
void renderOverlaySleepScreen() const;
|
||||
// Quick Resume: leaves the framebuffer (reader page) intact and overlays a small moon icon.
|
||||
void renderLastScreenSleepScreen() const;
|
||||
BookOverlayInfo getBookOverlayInfo(const std::string& bookPath) const;
|
||||
|
||||
const bool fromTimeout = false;
|
||||
};
|
||||
|
||||
@@ -286,6 +286,7 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
: FontSelectionActivity::Target::EPUB;
|
||||
startActivityForResult(std::make_unique<FontSelectionActivity>(renderer, mappedInput, target),
|
||||
[this](const ActivityResult&) {
|
||||
CrossPointSettings::normalizeDependentSettings(SETTINGS);
|
||||
SETTINGS.saveToFile();
|
||||
needsHalfRefresh = true;
|
||||
});
|
||||
@@ -294,6 +295,7 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
|
||||
if (setting.type == SettingType::ACTION) {
|
||||
auto resultHandler = [this](const ActivityResult& result) {
|
||||
CrossPointSettings::normalizeDependentSettings(SETTINGS);
|
||||
SETTINGS.saveToFile();
|
||||
needsHalfRefresh = true;
|
||||
const auto* menuResult = std::get_if<MenuResult>(&result.data);
|
||||
@@ -323,6 +325,7 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
}
|
||||
|
||||
setting.toggleValue();
|
||||
CrossPointSettings::normalizeDependentSettings(SETTINGS);
|
||||
SETTINGS.saveToFile();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// Image dimensions: 48x48, diagonal dots
|
||||
#define LOADINGICON_WIDTH 48
|
||||
#define LOADINGICON_HEIGHT 48
|
||||
|
||||
static const uint8_t LoadingIcon[] = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff,
|
||||
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff,
|
||||
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff,
|
||||
0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// Image dimensions: 48x48
|
||||
#define MOONICON_WIDTH 48
|
||||
#define MOONICON_HEIGHT 48
|
||||
|
||||
static const uint8_t MoonIcon[] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xF0, 0xFF, 0xFF,
|
||||
0xFF, 0xC0, 0x1F, 0xF0, 0x3F, 0xFF, 0xFF, 0x00, 0x1F, 0xF8, 0x0F, 0xFF, 0xFF, 0xF0, 0x7F, 0xFC, 0x07, 0xFF,
|
||||
0xFF, 0xF9, 0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0xF9, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xF9, 0xFF, 0xFE, 0x01, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFC, 0x00, 0xFF, 0xFF, 0xEF, 0xFF, 0xFC, 0x00, 0xFF,
|
||||
0xFF, 0xC7, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0xC3, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xC3, 0xFF, 0xF0, 0x00, 0x7F,
|
||||
0xFF, 0xC1, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xC0, 0x7F, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF,
|
||||
0xFF, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x03, 0xFF,
|
||||
0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF,
|
||||
0xFF, 0xFE, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF,
|
||||
0xFF, 0xFF, 0xF0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
+86
-12
@@ -38,6 +38,7 @@
|
||||
#include "activities/settings/SdFirmwareUpdateActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "images/LoadingIcon.h"
|
||||
#include "util/ButtonNavigator.h"
|
||||
#include "util/ScreenshotUtil.h"
|
||||
|
||||
@@ -150,8 +151,44 @@ void silentRestartToReader() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
// Enter deep sleep mode
|
||||
void enterDeepSleep() {
|
||||
// ---- Quick Resume framebuffer persistence ----
|
||||
//
|
||||
// Quick Resume keeps the last reader page on screen during deep sleep (just overlaid with a moon
|
||||
// icon) and skips the boot screen on wake by restoring the saved framebuffer + a loading icon.
|
||||
// The buffer is persisted to SD only when the user actually enabled the feature.
|
||||
constexpr char SLEEP_FRAME_FILE[] = "/.crosspoint/sleep_frame.bin";
|
||||
|
||||
static void saveSleepFrameBuffer() {
|
||||
FsFile file;
|
||||
if (!Storage.openFileForWrite("SLP", SLEEP_FRAME_FILE, file)) {
|
||||
return;
|
||||
}
|
||||
file.write(renderer.getFrameBuffer(), renderer.getBufferSize());
|
||||
file.close();
|
||||
}
|
||||
|
||||
// Restores the previously saved framebuffer into the display buffer. Returns false if the file is
|
||||
// missing or the size does not match — in that case the caller should fall back to a boot screen.
|
||||
// The file is always removed (success or failure) so a future cold boot does not see a stale frame.
|
||||
static bool loadSleepFrameBuffer() {
|
||||
FsFile file;
|
||||
if (!Storage.openFileForRead("SLP", SLEEP_FRAME_FILE, file)) {
|
||||
return false;
|
||||
}
|
||||
const size_t bufferSize = display.getBufferSize();
|
||||
const int bytesRead = file.read(display.getFrameBuffer(), bufferSize);
|
||||
file.close();
|
||||
Storage.remove(SLEEP_FRAME_FILE);
|
||||
return static_cast<size_t>(bytesRead) == bufferSize;
|
||||
}
|
||||
|
||||
// Earliest millis() value at which a held-power-button press is allowed to trigger sleep.
|
||||
// Set near the end of setup() so a wake-press held a bit too long does not bounce straight
|
||||
// back into deep sleep before the user sees the page.
|
||||
static unsigned long allowSleepAt = 0;
|
||||
|
||||
// Enter deep sleep mode. fromTimeout=true marks an auto-sleep (gates "Quick Resume on Timeout").
|
||||
void enterDeepSleep(bool fromTimeout = false) {
|
||||
LOG_DBG("MAIN", "enterDeepSleep called at millis=%lu, powerBtn isPressed=%d, rawPin=%d", millis(),
|
||||
gpio.isPressed(HalGPIO::BTN_POWER), digitalRead(InputManager::POWER_BUTTON_PIN) == LOW);
|
||||
HalPowerManager::Lock powerLock; // Ensure we are at normal CPU frequency for sleep preparation
|
||||
@@ -166,6 +203,13 @@ void enterDeepSleep() {
|
||||
if (APP_STATE.lastSleepFromReader) {
|
||||
APP_STATE.readerActivityLoadCount = 0;
|
||||
}
|
||||
|
||||
const bool isQuickResumeSleep =
|
||||
SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::QUICK_RESUME ||
|
||||
(fromTimeout &&
|
||||
SETTINGS.quickResumeSleepScreen == CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT);
|
||||
APP_STATE.showBootScreen = !isQuickResumeSleep;
|
||||
|
||||
APP_STATE.saveToFile();
|
||||
// Tear down WiFi so the modem power domain isn't held alive across deep sleep.
|
||||
// Wake from deep sleep is effectively a chip reset, so no state needs to survive.
|
||||
@@ -173,7 +217,13 @@ void enterDeepSleep() {
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
activityManager.goToSleep();
|
||||
activityManager.goToSleep(fromTimeout);
|
||||
|
||||
// Persist the moon-icon-overlaid framebuffer after goToSleep() has painted it.
|
||||
if (isQuickResumeSleep) {
|
||||
saveSleepFrameBuffer();
|
||||
}
|
||||
|
||||
halTiltSensor.deepSleep();
|
||||
|
||||
display.deepSleep();
|
||||
@@ -183,8 +233,8 @@ void enterDeepSleep() {
|
||||
powerManager.startDeepSleep(gpio, keepLpAlive);
|
||||
}
|
||||
|
||||
void setupDisplayAndFonts() {
|
||||
display.begin();
|
||||
void setupDisplayAndFonts(bool seamless = false) {
|
||||
display.begin(seamless);
|
||||
renderer.begin();
|
||||
activityManager.begin();
|
||||
LOG_DBG("MAIN", "Display initialized");
|
||||
@@ -354,6 +404,9 @@ void setup() {
|
||||
}
|
||||
|
||||
SETTINGS.loadFromFile();
|
||||
// APP_STATE is needed before display init so Quick Resume can skip the on-wake resync
|
||||
// and so the seamless-wake path can paint the LoadingIcon over the restored framebuffer.
|
||||
APP_STATE.loadFromFile();
|
||||
|
||||
HalSystem::checkPanic();
|
||||
HalSystem::clearPanic(); // TODO: move this to an activity when we have one to display the panic info
|
||||
@@ -368,20 +421,36 @@ 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();
|
||||
const bool seamlessWake = !isSilentReboot && !APP_STATE.showBootScreen;
|
||||
setupDisplayAndFonts(seamlessWake);
|
||||
|
||||
if (!isSilentReboot) {
|
||||
activityManager.goToBoot();
|
||||
} else {
|
||||
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.
|
||||
APP_STATE.showBootScreen = true;
|
||||
APP_STATE.saveToFile();
|
||||
}
|
||||
activityManager.goToBoot();
|
||||
}
|
||||
|
||||
APP_STATE.loadFromFile();
|
||||
HalClock::restore();
|
||||
RECENT_BOOKS.loadFromFile();
|
||||
GLOBAL_BOOKMARKS.load();
|
||||
@@ -420,6 +489,11 @@ void setup() {
|
||||
// Flush any pin state transitions that occurred during boot before entering the main loop
|
||||
mappedInputManager.update();
|
||||
buttonEventManager.drain();
|
||||
|
||||
// Block held-power sleep for the first 2 seconds. On Quick Resume the user often releases
|
||||
// the wake-press a fraction late; without this guard the loop() power-hold check would
|
||||
// immediately fire enterDeepSleep().
|
||||
allowSleepAt = millis() + 2000;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
@@ -496,7 +570,7 @@ void loop() {
|
||||
const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs();
|
||||
if (millis() - lastActivityTime >= sleepTimeoutMs) {
|
||||
LOG_DBG("SLP", "Auto-sleep triggered after %lu ms of inactivity", sleepTimeoutMs);
|
||||
enterDeepSleep();
|
||||
enterDeepSleep(/*fromTimeout=*/true);
|
||||
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
||||
return;
|
||||
}
|
||||
@@ -510,7 +584,7 @@ void loop() {
|
||||
powerHoldStart = millis();
|
||||
LOG_DBG("MAIN", "loop: power button press detected (fresh edge)");
|
||||
}
|
||||
if (gpio.isPressed(HalGPIO::BTN_POWER) && powerHoldStart > 0) {
|
||||
if (millis() >= allowSleepAt && gpio.isPressed(HalGPIO::BTN_POWER) && powerHoldStart > 0) {
|
||||
const unsigned long heldTime = millis() - powerHoldStart;
|
||||
if (heldTime > SETTINGS.getPowerButtonDuration()) {
|
||||
// If the screenshot combination is potentially being pressed, don't sleep
|
||||
|
||||
@@ -1505,6 +1505,7 @@ void CrossPointWebServer::handlePostSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
CrossPointSettings::normalizeDependentSettings(SETTINGS);
|
||||
SETTINGS.saveToFile();
|
||||
|
||||
LOG_DBG("WEB", "Applied %d setting(s)", applied);
|
||||
|
||||
Reference in New Issue
Block a user