mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-07 08:07:40 +03:00
Compare commits
5 Commits
7a33cd9e09
...
e4698f2985
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4698f2985 | ||
|
|
7ebb3c84c5 | ||
|
|
7e7c51949e | ||
|
|
50b2a37fb7 | ||
|
|
6600681082 |
56
README.md
56
README.md
@ -34,7 +34,6 @@ This project is **not affiliated with Xteink**; it's built as a community projec
|
||||
- [ ] EPUB picker with cover art
|
||||
- [x] Custom sleep screen
|
||||
- [x] Cover sleep screen
|
||||
- [x] Calendar Mode (automated image display from URL)
|
||||
- [x] Wifi book upload
|
||||
- [x] Wifi OTA updates
|
||||
- [x] Configurable font, layout, and display options
|
||||
@ -44,61 +43,6 @@ This project is **not affiliated with Xteink**; it's built as a community projec
|
||||
|
||||
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
||||
|
||||
## Calendar Mode
|
||||
|
||||
Calendar Mode transforms your CrossPoint Reader into an automated display that periodically fetches and displays a BMP image from a URL. This is useful for:
|
||||
|
||||
- Calendar/schedule displays
|
||||
- Weather dashboards
|
||||
- Information displays
|
||||
- Digital signage
|
||||
|
||||
### How It Works
|
||||
|
||||
1. When enabled, the device wakes from deep sleep on a timer (1-24 hours configurable)
|
||||
2. Connects to saved WiFi network
|
||||
3. Fetches a BMP image from the configured URL
|
||||
4. Displays the image as the sleep screen
|
||||
5. Returns to deep sleep until the next refresh
|
||||
|
||||
### Configuration
|
||||
|
||||
In **Settings**, you'll find:
|
||||
|
||||
| Setting | Description |
|
||||
|---------|-------------|
|
||||
| **Calendar Mode** | Enable/disable the feature (ON/OFF) |
|
||||
| **Calendar Refresh (hours)** | How often to refresh (1-24 hours) |
|
||||
| **Calendar Server URL** | URL that returns a BMP image |
|
||||
| **Test Calendar Now** | Immediately test the fetch and display |
|
||||
|
||||
### Image Requirements
|
||||
|
||||
The server URL must return a valid BMP image:
|
||||
|
||||
- **Format**: BMP (Windows bitmap)
|
||||
- **Bit depth**: 1, 2, 8, 24, or 32 bpp (8-bit grayscale recommended)
|
||||
- **Dimensions**: 480 x 800 pixels (device screen size)
|
||||
- **Orientation**: Top-down (negative height in BMP header) recommended
|
||||
|
||||
### Setting Up a Server
|
||||
|
||||
You'll need a server that generates and serves BMP images. Example options:
|
||||
|
||||
- **Cloudflare Worker**: Serverless function that generates images on-demand
|
||||
- **Local server**: Python/Node.js script serving static or dynamic images
|
||||
- **Static hosting**: Pre-generated images on any web server
|
||||
|
||||
The device makes a simple HTTP GET request to the URL and expects raw BMP data in response.
|
||||
|
||||
### Power Consumption
|
||||
|
||||
Calendar Mode uses ESP32-C3 deep sleep between refreshes:
|
||||
- Deep sleep: ~10-20µA
|
||||
- Active (WiFi + fetch): ~100-150mA for ~10-30 seconds
|
||||
|
||||
With a 4-hour refresh interval, expect several months of battery life.
|
||||
|
||||
## Installing
|
||||
|
||||
### Web (latest firmware)
|
||||
|
||||
@ -16,7 +16,8 @@ class CrossPointSettings {
|
||||
CrossPointSettings& operator=(const CrossPointSettings&) = delete;
|
||||
|
||||
// Should match with SettingsActivity text
|
||||
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 };
|
||||
// Note: CALENDAR is internal-only, not user-selectable in Settings
|
||||
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4, CALENDAR = 5 };
|
||||
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1 };
|
||||
|
||||
// Status bar display type enum
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#include <Serialization.h>
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t STATE_FILE_VERSION = 2;
|
||||
constexpr uint8_t STATE_FILE_VERSION = 3;
|
||||
constexpr char STATE_FILE[] = "/.crosspoint/state.bin";
|
||||
} // namespace
|
||||
|
||||
@ -20,6 +20,7 @@ bool CrossPointState::saveToFile() const {
|
||||
serialization::writePod(outputFile, STATE_FILE_VERSION);
|
||||
serialization::writeString(outputFile, openEpubPath);
|
||||
serialization::writePod(outputFile, lastSleepImage);
|
||||
serialization::writePod(outputFile, lastCalendarFetch);
|
||||
outputFile.close();
|
||||
return true;
|
||||
}
|
||||
@ -44,6 +45,9 @@ bool CrossPointState::loadFromFile() {
|
||||
} else {
|
||||
lastSleepImage = 0;
|
||||
}
|
||||
if (version >= 3) {
|
||||
serialization::readPod(inputFile, lastCalendarFetch);
|
||||
}
|
||||
|
||||
inputFile.close();
|
||||
return true;
|
||||
|
||||
@ -9,6 +9,7 @@ class CrossPointState {
|
||||
public:
|
||||
std::string openEpubPath;
|
||||
uint8_t lastSleepImage;
|
||||
uint32_t lastCalendarFetch = 0; // Unix epoch of last successful calendar fetch
|
||||
~CrossPointState() = default;
|
||||
|
||||
// Get singleton instance
|
||||
|
||||
@ -20,6 +20,10 @@ void SleepActivity::onEnter() {
|
||||
return renderBlankSleepScreen();
|
||||
}
|
||||
|
||||
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CALENDAR) {
|
||||
return renderCalendarSleepScreen();
|
||||
}
|
||||
|
||||
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM) {
|
||||
return renderCustomSleepScreen();
|
||||
}
|
||||
@ -119,6 +123,21 @@ void SleepActivity::renderCustomSleepScreen() const {
|
||||
renderDefaultSleepScreen();
|
||||
}
|
||||
|
||||
void SleepActivity::renderCalendarSleepScreen() const {
|
||||
// Calendar mode: read /sleep.bmp directly, bypassing /sleep/ directory
|
||||
FsFile file;
|
||||
if (SdMan.openFileForRead("SLP", "/sleep.bmp", file)) {
|
||||
Bitmap bitmap(file, true);
|
||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||
Serial.printf("[%lu] [SLP] Loading calendar: /sleep.bmp\n", millis());
|
||||
renderBitmapSleepScreen(bitmap);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Fallback if no calendar image
|
||||
renderDefaultSleepScreen();
|
||||
}
|
||||
|
||||
void SleepActivity::renderDefaultSleepScreen() const {
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
@ -13,6 +13,7 @@ class SleepActivity final : public Activity {
|
||||
void renderPopup(const char* message) const;
|
||||
void renderDefaultSleepScreen() const;
|
||||
void renderCustomSleepScreen() const;
|
||||
void renderCalendarSleepScreen() const;
|
||||
void renderCoverSleepScreen() const;
|
||||
void renderBitmapSleepScreen(const Bitmap& bitmap) const;
|
||||
void renderBlankSleepScreen() const;
|
||||
|
||||
@ -5,16 +5,18 @@
|
||||
#include <SDCardManager.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_sleep.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "../../CrossPointSettings.h"
|
||||
#include "../../CrossPointState.h"
|
||||
#include "../../WifiCredentialStore.h"
|
||||
#include "../../fontIds.h"
|
||||
#include "../boot_sleep/SleepActivity.h"
|
||||
|
||||
// External functions from main.cpp
|
||||
extern void exitActivity();
|
||||
extern void enterNewActivity(Activity* activity);
|
||||
extern void enterDeepSleep();
|
||||
extern void enterCalendarDeepSleep(uint8_t refreshHours);
|
||||
|
||||
void CalendarActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
@ -22,9 +24,9 @@ void CalendarActivity::onEnter() {
|
||||
stateStartTime = millis();
|
||||
|
||||
Serial.printf("[%lu] [CAL] Calendar mode starting\n", millis());
|
||||
renderStatus("Starting...");
|
||||
|
||||
// Begin WiFi connection
|
||||
// Show status and begin WiFi connection
|
||||
renderStatus("Connecting...");
|
||||
startWifiConnection();
|
||||
}
|
||||
|
||||
@ -44,10 +46,6 @@ void CalendarActivity::startWifiConnection() {
|
||||
const auto& cred = credentials[0];
|
||||
Serial.printf("[%lu] [CAL] Connecting to: %s\n", millis(), cred.ssid.c_str());
|
||||
|
||||
char statusMsg[64];
|
||||
snprintf(statusMsg, sizeof(statusMsg), "Connecting to %s...", cred.ssid.c_str());
|
||||
renderStatus(statusMsg);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(cred.ssid.c_str(), cred.password.c_str());
|
||||
|
||||
@ -60,12 +58,6 @@ bool CalendarActivity::checkWifiConnection() { return WiFi.status() == WL_CONNEC
|
||||
bool CalendarActivity::fetchAndSaveImage() {
|
||||
Serial.printf("[%lu] [CAL] Fetching image from: %s\n", millis(), SETTINGS.calendarServerUrl);
|
||||
|
||||
// Check if URL is set
|
||||
if (strlen(SETTINGS.calendarServerUrl) == 0) {
|
||||
Serial.printf("[%lu] [CAL] ERROR: Calendar Server URL is empty!\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
HTTPClient http;
|
||||
http.begin(SETTINGS.calendarServerUrl);
|
||||
http.setTimeout(30000);
|
||||
@ -134,61 +126,20 @@ bool CalendarActivity::fetchAndSaveImage() {
|
||||
return totalWritten > 0;
|
||||
}
|
||||
|
||||
void CalendarActivity::renderSleepScreen() {
|
||||
Serial.printf("[%lu] [CAL] Rendering sleep screen\n", millis());
|
||||
|
||||
// Force sleep screen mode to CUSTOM to use our downloaded image
|
||||
SETTINGS.sleepScreen = CrossPointSettings::CUSTOM;
|
||||
|
||||
// Create and enter SleepActivity to render the image
|
||||
exitActivity();
|
||||
enterNewActivity(new SleepActivity(renderer, mappedInput));
|
||||
}
|
||||
|
||||
void CalendarActivity::scheduleWakeAndSleep() {
|
||||
Serial.printf("[%lu] [CAL] Scheduling wake in %d hours\n", millis(), SETTINGS.calendarRefreshHours);
|
||||
|
||||
// Disconnect WiFi to save power
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
|
||||
// Calculate sleep duration in microseconds
|
||||
uint64_t sleepDurationUs = (uint64_t)SETTINGS.calendarRefreshHours * 60ULL * 60ULL * 1000000ULL;
|
||||
|
||||
Serial.printf("[%lu] [CAL] Sleep duration: %llu us (%d hours)\n", millis(), sleepDurationUs,
|
||||
SETTINGS.calendarRefreshHours);
|
||||
|
||||
// Enable timer wakeup
|
||||
esp_sleep_enable_timer_wakeup(sleepDurationUs);
|
||||
|
||||
// Also keep GPIO wakeup for power button (allows manual wake for normal use)
|
||||
esp_deep_sleep_enable_gpio_wakeup(1ULL << 3, ESP_GPIO_WAKEUP_GPIO_LOW); // Power button pin
|
||||
|
||||
Serial.printf("[%lu] [CAL] Entering deep sleep\n", millis());
|
||||
|
||||
// Enter deep sleep
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
void CalendarActivity::handleError(const char* message) {
|
||||
Serial.printf("[%lu] [CAL] Error: %s\n", millis(), message);
|
||||
errorMessage = message;
|
||||
state = CalendarState::ERROR;
|
||||
stateStartTime = millis();
|
||||
|
||||
// Show error on screen
|
||||
char errorMsg[128];
|
||||
snprintf(errorMsg, sizeof(errorMsg), "Error: %s", message);
|
||||
renderStatus(errorMsg);
|
||||
// For now, just log - we'll use default sleep screen on error
|
||||
}
|
||||
|
||||
void CalendarActivity::renderStatus(const char* status) {
|
||||
Serial.printf("[%lu] [CAL] Status: %s\n", millis(), status);
|
||||
|
||||
// Show status on screen
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 50, "Calendar Mode", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 100, status, true);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, renderer.getScreenHeight() / 2, status, true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
|
||||
@ -201,7 +152,15 @@ void CalendarActivity::loop() {
|
||||
case CalendarState::CONNECTING_WIFI:
|
||||
if (checkWifiConnection()) {
|
||||
Serial.printf("[%lu] [CAL] WiFi connected, IP: %s\n", millis(), WiFi.localIP().toString().c_str());
|
||||
renderStatus("WiFi connected, fetching...");
|
||||
|
||||
// Sync time via NTP
|
||||
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
|
||||
struct tm timeinfo;
|
||||
if (getLocalTime(&timeinfo, 5000)) {
|
||||
Serial.printf("[%lu] [CAL] NTP time synced\n", millis());
|
||||
}
|
||||
|
||||
renderStatus("Fetching...");
|
||||
state = CalendarState::FETCHING_IMAGE;
|
||||
stateStartTime = millis();
|
||||
} else if (millis() - stateStartTime > WIFI_TIMEOUT_MS) {
|
||||
@ -212,6 +171,16 @@ void CalendarActivity::loop() {
|
||||
case CalendarState::FETCHING_IMAGE:
|
||||
if (fetchAndSaveImage()) {
|
||||
Serial.printf("[%lu] [CAL] Image saved successfully\n", millis());
|
||||
|
||||
// Save fetch timestamp
|
||||
time_t now;
|
||||
time(&now);
|
||||
if (now > 1700000000) { // Sanity check - after Nov 2023
|
||||
APP_STATE.lastCalendarFetch = now;
|
||||
APP_STATE.saveToFile();
|
||||
Serial.printf("[%lu] [CAL] Saved fetch timestamp: %lu\n", millis(), (unsigned long)now);
|
||||
}
|
||||
|
||||
renderStatus("Image saved!");
|
||||
state = CalendarState::RENDERING;
|
||||
} else {
|
||||
@ -226,14 +195,9 @@ void CalendarActivity::loop() {
|
||||
break;
|
||||
|
||||
case CalendarState::RENDERING:
|
||||
renderSleepScreen();
|
||||
// After SleepActivity renders, we need to schedule sleep
|
||||
state = CalendarState::SCHEDULING_SLEEP;
|
||||
break;
|
||||
|
||||
case CalendarState::SCHEDULING_SLEEP:
|
||||
scheduleWakeAndSleep();
|
||||
// Should never reach here - device sleeps
|
||||
Serial.printf("[%lu] [CAL] Rendering complete, entering deep sleep\n", millis());
|
||||
enterCalendarDeepSleep(SETTINGS.calendarRefreshHours);
|
||||
// Never reaches here - device enters deep sleep
|
||||
break;
|
||||
|
||||
case CalendarState::ERROR:
|
||||
@ -243,7 +207,7 @@ void CalendarActivity::loop() {
|
||||
state = CalendarState::RENDERING;
|
||||
} else {
|
||||
// No cached image - just sleep with default screen and try again later
|
||||
scheduleWakeAndSleep();
|
||||
enterCalendarDeepSleep(SETTINGS.calendarRefreshHours);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
* 7. Enter deep sleep
|
||||
*/
|
||||
|
||||
enum class CalendarState { INIT, CONNECTING_WIFI, FETCHING_IMAGE, SAVING_IMAGE, RENDERING, SCHEDULING_SLEEP, ERROR };
|
||||
enum class CalendarState { INIT, CONNECTING_WIFI, FETCHING_IMAGE, RENDERING, ERROR };
|
||||
|
||||
class CalendarActivity final : public Activity {
|
||||
public:
|
||||
@ -39,8 +39,6 @@ class CalendarActivity final : public Activity {
|
||||
void startWifiConnection();
|
||||
bool checkWifiConnection();
|
||||
bool fetchAndSaveImage();
|
||||
void renderSleepScreen();
|
||||
void scheduleWakeAndSleep();
|
||||
void handleError(const char* message);
|
||||
void renderStatus(const char* status);
|
||||
|
||||
|
||||
@ -5,24 +5,19 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "../calendar/CalendarActivity.h"
|
||||
#include "CalibreSettingsActivity.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "OtaUpdateActivity.h"
|
||||
#include "activities/calendar/CalendarActivity.h"
|
||||
#include "activities/util/KeyboardEntryActivity.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
// Define the static settings list
|
||||
namespace {
|
||||
constexpr int settingsCount = 24;
|
||||
constexpr int settingsCount = 23;
|
||||
const SettingInfo settingsList[settingsCount] = {
|
||||
// Should match with SLEEP_SCREEN_MODE
|
||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
||||
// Calendar mode settings
|
||||
SettingInfo::Toggle("Calendar Mode", &CrossPointSettings::calendarModeEnabled),
|
||||
SettingInfo::Value("Calendar Refresh (hours)", &CrossPointSettings::calendarRefreshHours, {1, 24, 1}),
|
||||
SettingInfo::Action("Calendar Server URL"), SettingInfo::Action("Test Calendar Now"),
|
||||
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
|
||||
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
|
||||
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
||||
@ -47,7 +42,11 @@ const SettingInfo settingsList[settingsCount] = {
|
||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
||||
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
||||
SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Check for updates")};
|
||||
// Calendar mode settings
|
||||
SettingInfo::Toggle("Calendar Mode", &CrossPointSettings::calendarModeEnabled),
|
||||
SettingInfo::Value("Calendar Refresh (hrs)", &CrossPointSettings::calendarRefreshHours, {1, 24, 1}),
|
||||
SettingInfo::Action("Test Calendar Now"), SettingInfo::Action("Calibre Settings"),
|
||||
SettingInfo::Action("Check for updates")};
|
||||
} // namespace
|
||||
|
||||
void SettingsActivity::taskTrampoline(void* param) {
|
||||
@ -160,25 +159,6 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
updateRequired = true;
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
} else if (strcmp(setting.name, "Calendar Server URL") == 0) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "Calendar Server URL", SETTINGS.calendarServerUrl, 10,
|
||||
255, // maxLength
|
||||
false, // not password
|
||||
[this](const std::string& url) {
|
||||
strncpy(SETTINGS.calendarServerUrl, url.c_str(), sizeof(SETTINGS.calendarServerUrl) - 1);
|
||||
SETTINGS.calendarServerUrl[sizeof(SETTINGS.calendarServerUrl) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
} else if (strcmp(setting.name, "Test Calendar Now") == 0) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
@ -235,11 +215,6 @@ void SettingsActivity::render() const {
|
||||
valueText = settingsList[i].enumValues[value];
|
||||
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
||||
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
||||
} else if (settingsList[i].type == SettingType::ACTION) {
|
||||
// Show [Set]/[Not Set] for URL-based action settings
|
||||
if (strcmp(settingsList[i].name, "Calendar Server URL") == 0) {
|
||||
valueText = (strlen(SETTINGS.calendarServerUrl) > 0) ? "[Set]" : "[Not Set]";
|
||||
}
|
||||
}
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), i != selectedSettingIndex);
|
||||
|
||||
55
src/main.cpp
55
src/main.cpp
@ -5,6 +5,7 @@
|
||||
#include <InputManager.h>
|
||||
#include <SDCardManager.h>
|
||||
#include <SPI.h>
|
||||
#include <WiFi.h>
|
||||
#include <builtinFonts/all.h>
|
||||
#include <esp_sleep.h>
|
||||
|
||||
@ -198,6 +199,11 @@ void waitForPowerRelease() {
|
||||
|
||||
// Enter deep sleep mode
|
||||
void enterDeepSleep() {
|
||||
// If calendar mode enabled, use calendar sleep screen to show /sleep.bmp
|
||||
if (SETTINGS.calendarModeEnabled) {
|
||||
SETTINGS.sleepScreen = CrossPointSettings::CALENDAR;
|
||||
}
|
||||
|
||||
exitActivity();
|
||||
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
||||
|
||||
@ -211,6 +217,34 @@ void enterDeepSleep() {
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
// Calendar mode deep sleep - renders custom sleep screen and schedules timer wake
|
||||
void enterCalendarDeepSleep(uint8_t refreshHours) {
|
||||
// Set sleep screen to CALENDAR to use /sleep.bmp directly (bypasses /sleep/ directory)
|
||||
SETTINGS.sleepScreen = CrossPointSettings::CALENDAR;
|
||||
|
||||
// Transition to SleepActivity to render the custom image
|
||||
exitActivity();
|
||||
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
||||
|
||||
// Put display to sleep
|
||||
einkDisplay.deepSleep();
|
||||
|
||||
// Disconnect WiFi to save power
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
|
||||
// Calculate sleep duration
|
||||
uint64_t sleepDurationUs = (uint64_t)refreshHours * 60ULL * 60ULL * 1000000ULL;
|
||||
Serial.printf("[%lu] [CAL] Sleep duration: %llu us (%d hours)\n", millis(), sleepDurationUs, refreshHours);
|
||||
|
||||
// Enable timer + GPIO wakeup
|
||||
esp_sleep_enable_timer_wakeup(sleepDurationUs);
|
||||
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
||||
|
||||
Serial.printf("[%lu] [CAL] Entering deep sleep\n", millis());
|
||||
esp_deep_sleep_start(); // Never returns
|
||||
}
|
||||
|
||||
void onGoHome();
|
||||
void onGoToReader(const std::string& initialEpubPath) {
|
||||
exitActivity();
|
||||
@ -291,9 +325,29 @@ void setup() {
|
||||
}
|
||||
|
||||
SETTINGS.loadFromFile();
|
||||
APP_STATE.loadFromFile();
|
||||
|
||||
// Check if this is a timer wake for calendar mode
|
||||
esp_sleep_wakeup_cause_t wakeupCause = esp_sleep_get_wakeup_cause();
|
||||
|
||||
// Check if calendar needs refresh (backup for power loss)
|
||||
if (SETTINGS.calendarModeEnabled && wakeupCause != ESP_SLEEP_WAKEUP_TIMER) {
|
||||
// Not a timer wake - check if we need a backup fetch
|
||||
time_t now;
|
||||
time(&now);
|
||||
uint32_t elapsed = (now > APP_STATE.lastCalendarFetch) ? (now - APP_STATE.lastCalendarFetch) : UINT32_MAX;
|
||||
uint32_t thresholdSec = SETTINGS.calendarRefreshHours * 3600;
|
||||
|
||||
// If time is valid and elapsed > threshold, trigger backup fetch
|
||||
if (now > 1700000000 && elapsed > thresholdSec) {
|
||||
Serial.printf("[%lu] [ ] Calendar stale, triggering backup fetch\n", millis());
|
||||
setupDisplayAndFonts();
|
||||
exitActivity();
|
||||
enterNewActivity(new CalendarActivity(renderer, mappedInputManager));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (wakeupCause == ESP_SLEEP_WAKEUP_TIMER && SETTINGS.calendarModeEnabled) {
|
||||
Serial.printf("[%lu] [ ] Timer wake detected - entering calendar mode\n", millis());
|
||||
setupDisplayAndFonts();
|
||||
@ -313,7 +367,6 @@ void setup() {
|
||||
exitActivity();
|
||||
enterNewActivity(new BootActivity(renderer, mappedInputManager));
|
||||
|
||||
APP_STATE.loadFromFile();
|
||||
if (APP_STATE.openEpubPath.empty()) {
|
||||
onGoHome();
|
||||
} else {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user