mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-07 16:17:38 +03:00
Merge 5d772229b5 into 21277e03eb
This commit is contained in:
commit
7a33cd9e09
56
README.md
56
README.md
@ -34,6 +34,7 @@ This project is **not affiliated with Xteink**; it's built as a community projec
|
|||||||
- [ ] EPUB picker with cover art
|
- [ ] EPUB picker with cover art
|
||||||
- [x] Custom sleep screen
|
- [x] Custom sleep screen
|
||||||
- [x] Cover sleep screen
|
- [x] Cover sleep screen
|
||||||
|
- [x] Calendar Mode (automated image display from URL)
|
||||||
- [x] Wifi book upload
|
- [x] Wifi book upload
|
||||||
- [x] Wifi OTA updates
|
- [x] Wifi OTA updates
|
||||||
- [x] Configurable font, layout, and display options
|
- [x] Configurable font, layout, and display options
|
||||||
@ -43,6 +44,61 @@ 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.
|
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
|
## Installing
|
||||||
|
|
||||||
### Web (latest firmware)
|
### Web (latest firmware)
|
||||||
|
|||||||
@ -12,9 +12,9 @@
|
|||||||
CrossPointSettings CrossPointSettings::instance;
|
CrossPointSettings CrossPointSettings::instance;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
constexpr uint8_t SETTINGS_FILE_VERSION = 2; // Incremented for calendar mode
|
||||||
// Increment this when adding new persisted settings fields
|
// Increment this when adding new persisted settings fields
|
||||||
constexpr uint8_t SETTINGS_COUNT = 18;
|
constexpr uint8_t SETTINGS_COUNT = 21; // Added 3 calendar settings
|
||||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -48,6 +48,10 @@ bool CrossPointSettings::saveToFile() const {
|
|||||||
serialization::writePod(outputFile, textAntiAliasing);
|
serialization::writePod(outputFile, textAntiAliasing);
|
||||||
serialization::writePod(outputFile, hideBatteryPercentage);
|
serialization::writePod(outputFile, hideBatteryPercentage);
|
||||||
serialization::writePod(outputFile, longPressChapterSkip);
|
serialization::writePod(outputFile, longPressChapterSkip);
|
||||||
|
// Calendar mode settings
|
||||||
|
serialization::writePod(outputFile, calendarModeEnabled);
|
||||||
|
serialization::writePod(outputFile, calendarRefreshHours);
|
||||||
|
serialization::writeString(outputFile, std::string(calendarServerUrl));
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
|
|
||||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||||
@ -116,6 +120,18 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, longPressChapterSkip);
|
serialization::readPod(inputFile, longPressChapterSkip);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
// Calendar mode settings
|
||||||
|
serialization::readPod(inputFile, calendarModeEnabled);
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
serialization::readPod(inputFile, calendarRefreshHours);
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
{
|
||||||
|
std::string urlStr;
|
||||||
|
serialization::readString(inputFile, urlStr);
|
||||||
|
strncpy(calendarServerUrl, urlStr.c_str(), sizeof(calendarServerUrl) - 1);
|
||||||
|
calendarServerUrl[sizeof(calendarServerUrl) - 1] = '\0';
|
||||||
|
}
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
} while (false);
|
} while (false);
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
|
|||||||
@ -93,6 +93,11 @@ class CrossPointSettings {
|
|||||||
// Long-press chapter skip on side buttons
|
// Long-press chapter skip on side buttons
|
||||||
uint8_t longPressChapterSkip = 1;
|
uint8_t longPressChapterSkip = 1;
|
||||||
|
|
||||||
|
// Calendar mode settings
|
||||||
|
uint8_t calendarModeEnabled = 0; // 0 = disabled, 1 = enabled
|
||||||
|
uint8_t calendarRefreshHours = 4; // Refresh interval in hours (1-24)
|
||||||
|
char calendarServerUrl[256] = ""; // URL to fetch BMP image from
|
||||||
|
|
||||||
~CrossPointSettings() = default;
|
~CrossPointSettings() = default;
|
||||||
|
|
||||||
// Get singleton instance
|
// Get singleton instance
|
||||||
|
|||||||
251
src/activities/calendar/CalendarActivity.cpp
Normal file
251
src/activities/calendar/CalendarActivity.cpp
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
#include "CalendarActivity.h"
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#include <SDCardManager.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <esp_sleep.h>
|
||||||
|
|
||||||
|
#include "../../CrossPointSettings.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();
|
||||||
|
|
||||||
|
void CalendarActivity::onEnter() {
|
||||||
|
Activity::onEnter();
|
||||||
|
state = CalendarState::INIT;
|
||||||
|
stateStartTime = millis();
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [CAL] Calendar mode starting\n", millis());
|
||||||
|
renderStatus("Starting...");
|
||||||
|
|
||||||
|
// Begin WiFi connection
|
||||||
|
startWifiConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalendarActivity::startWifiConnection() {
|
||||||
|
Serial.printf("[%lu] [CAL] Loading WiFi credentials\n", millis());
|
||||||
|
|
||||||
|
// Load saved credentials
|
||||||
|
WIFI_STORE.loadFromFile();
|
||||||
|
|
||||||
|
const auto& credentials = WIFI_STORE.getCredentials();
|
||||||
|
if (credentials.empty()) {
|
||||||
|
handleError("No saved WiFi");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use first saved network
|
||||||
|
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());
|
||||||
|
|
||||||
|
state = CalendarState::CONNECTING_WIFI;
|
||||||
|
stateStartTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CalendarActivity::checkWifiConnection() { return WiFi.status() == WL_CONNECTED; }
|
||||||
|
|
||||||
|
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);
|
||||||
|
http.setConnectTimeout(10000);
|
||||||
|
|
||||||
|
int httpCode = http.GET();
|
||||||
|
|
||||||
|
if (httpCode != HTTP_CODE_OK) {
|
||||||
|
Serial.printf("[%lu] [CAL] HTTP error: %d\n", millis(), httpCode);
|
||||||
|
http.end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int contentLength = http.getSize();
|
||||||
|
Serial.printf("[%lu] [CAL] Content length: %d bytes\n", millis(), contentLength);
|
||||||
|
|
||||||
|
// Open file for writing using SdMan
|
||||||
|
FsFile file;
|
||||||
|
if (!SdMan.openFileForWrite("CAL", "/sleep.bmp", file)) {
|
||||||
|
Serial.printf("[%lu] [CAL] Failed to open /sleep.bmp for writing\n", millis());
|
||||||
|
http.end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream response to file
|
||||||
|
WiFiClient* stream = http.getStreamPtr();
|
||||||
|
uint8_t buffer[512];
|
||||||
|
int totalWritten = 0;
|
||||||
|
|
||||||
|
while (http.connected() && (contentLength > 0 || contentLength == -1)) {
|
||||||
|
size_t available = stream->available();
|
||||||
|
if (available) {
|
||||||
|
size_t toRead = min(available, sizeof(buffer));
|
||||||
|
size_t bytesRead = stream->readBytes(buffer, toRead);
|
||||||
|
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
file.write(buffer, bytesRead);
|
||||||
|
totalWritten += bytesRead;
|
||||||
|
|
||||||
|
if (contentLength > 0) {
|
||||||
|
contentLength -= bytesRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for timeout
|
||||||
|
if (millis() - stateStartTime > HTTP_TIMEOUT_MS) {
|
||||||
|
Serial.printf("[%lu] [CAL] HTTP timeout during download\n", millis());
|
||||||
|
file.close();
|
||||||
|
http.end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break if we've received everything
|
||||||
|
if (contentLength == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
http.end();
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [CAL] Saved %d bytes to /sleep.bmp\n", millis(), totalWritten);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.displayBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalendarActivity::loop() {
|
||||||
|
switch (state) {
|
||||||
|
case CalendarState::INIT:
|
||||||
|
// Should not reach here - onEnter handles init
|
||||||
|
break;
|
||||||
|
|
||||||
|
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...");
|
||||||
|
state = CalendarState::FETCHING_IMAGE;
|
||||||
|
stateStartTime = millis();
|
||||||
|
} else if (millis() - stateStartTime > WIFI_TIMEOUT_MS) {
|
||||||
|
handleError("WiFi timeout");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CalendarState::FETCHING_IMAGE:
|
||||||
|
if (fetchAndSaveImage()) {
|
||||||
|
Serial.printf("[%lu] [CAL] Image saved successfully\n", millis());
|
||||||
|
renderStatus("Image saved!");
|
||||||
|
state = CalendarState::RENDERING;
|
||||||
|
} else {
|
||||||
|
// Check if we have a cached image
|
||||||
|
if (SdMan.exists("/sleep.bmp")) {
|
||||||
|
Serial.printf("[%lu] [CAL] Fetch failed, using cached image\n", millis());
|
||||||
|
state = CalendarState::RENDERING;
|
||||||
|
} else {
|
||||||
|
handleError("Fetch failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CalendarState::ERROR:
|
||||||
|
// Wait 3 seconds showing error, then sleep anyway (use cached image if available)
|
||||||
|
if (millis() - stateStartTime > 3000) {
|
||||||
|
if (SdMan.exists("/sleep.bmp")) {
|
||||||
|
state = CalendarState::RENDERING;
|
||||||
|
} else {
|
||||||
|
// No cached image - just sleep with default screen and try again later
|
||||||
|
scheduleWakeAndSleep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/activities/calendar/CalendarActivity.h
Normal file
49
src/activities/calendar/CalendarActivity.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../Activity.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CalendarActivity - Automated calendar image fetch and display
|
||||||
|
*
|
||||||
|
* This activity is triggered on timer wake (not power button wake).
|
||||||
|
* It connects to WiFi, fetches a BMP image from a configured URL,
|
||||||
|
* saves it as the sleep screen, and returns to deep sleep.
|
||||||
|
*
|
||||||
|
* Flow:
|
||||||
|
* 1. Load saved WiFi credentials
|
||||||
|
* 2. Connect to WiFi (timeout: 30s)
|
||||||
|
* 3. HTTP GET image from configured URL (timeout: 60s)
|
||||||
|
* 4. Save image to /sleep.bmp on SD card
|
||||||
|
* 5. Render sleep screen
|
||||||
|
* 6. Schedule timer wake for next refresh
|
||||||
|
* 7. Enter deep sleep
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum class CalendarState { INIT, CONNECTING_WIFI, FETCHING_IMAGE, SAVING_IMAGE, RENDERING, SCHEDULING_SLEEP, ERROR };
|
||||||
|
|
||||||
|
class CalendarActivity final : public Activity {
|
||||||
|
public:
|
||||||
|
explicit CalendarActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||||
|
: Activity("Calendar", renderer, mappedInput) {}
|
||||||
|
|
||||||
|
void onEnter() override;
|
||||||
|
void loop() override;
|
||||||
|
bool preventAutoSleep() override { return true; }
|
||||||
|
bool skipLoopDelay() override { return true; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
CalendarState state = CalendarState::INIT;
|
||||||
|
unsigned long stateStartTime = 0;
|
||||||
|
String errorMessage;
|
||||||
|
|
||||||
|
void startWifiConnection();
|
||||||
|
bool checkWifiConnection();
|
||||||
|
bool fetchAndSaveImage();
|
||||||
|
void renderSleepScreen();
|
||||||
|
void scheduleWakeAndSleep();
|
||||||
|
void handleError(const char* message);
|
||||||
|
void renderStatus(const char* status);
|
||||||
|
|
||||||
|
static constexpr unsigned long WIFI_TIMEOUT_MS = 30000;
|
||||||
|
static constexpr unsigned long HTTP_TIMEOUT_MS = 60000;
|
||||||
|
};
|
||||||
@ -9,14 +9,20 @@
|
|||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "OtaUpdateActivity.h"
|
#include "OtaUpdateActivity.h"
|
||||||
|
#include "activities/calendar/CalendarActivity.h"
|
||||||
|
#include "activities/util/KeyboardEntryActivity.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
// Define the static settings list
|
// Define the static settings list
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int settingsCount = 20;
|
constexpr int settingsCount = 24;
|
||||||
const SettingInfo settingsList[settingsCount] = {
|
const SettingInfo settingsList[settingsCount] = {
|
||||||
// Should match with SLEEP_SCREEN_MODE
|
// Should match with SLEEP_SCREEN_MODE
|
||||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
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("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
|
||||||
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
|
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
|
||||||
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
||||||
@ -41,8 +47,7 @@ const SettingInfo settingsList[settingsCount] = {
|
|||||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
||||||
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||||
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
||||||
SettingInfo::Action("Calibre Settings"),
|
SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Check for updates")};
|
||||||
SettingInfo::Action("Check for updates")};
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void SettingsActivity::taskTrampoline(void* param) {
|
void SettingsActivity::taskTrampoline(void* param) {
|
||||||
@ -155,6 +160,30 @@ void SettingsActivity::toggleCurrentSetting() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
xSemaphoreGive(renderingMutex);
|
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();
|
||||||
|
enterNewActivity(new CalendarActivity(renderer, mappedInput));
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only toggle if it's a toggle type and has a value pointer
|
// Only toggle if it's a toggle type and has a value pointer
|
||||||
@ -206,6 +235,11 @@ void SettingsActivity::render() const {
|
|||||||
valueText = settingsList[i].enumValues[value];
|
valueText = settingsList[i].enumValues[value];
|
||||||
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
||||||
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
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());
|
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);
|
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), i != selectedSettingIndex);
|
||||||
|
|||||||
12
src/main.cpp
12
src/main.cpp
@ -6,6 +6,7 @@
|
|||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <builtinFonts/all.h>
|
#include <builtinFonts/all.h>
|
||||||
|
#include <esp_sleep.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@ -16,6 +17,7 @@
|
|||||||
#include "activities/boot_sleep/BootActivity.h"
|
#include "activities/boot_sleep/BootActivity.h"
|
||||||
#include "activities/boot_sleep/SleepActivity.h"
|
#include "activities/boot_sleep/SleepActivity.h"
|
||||||
#include "activities/browser/OpdsBookBrowserActivity.h"
|
#include "activities/browser/OpdsBookBrowserActivity.h"
|
||||||
|
#include "activities/calendar/CalendarActivity.h"
|
||||||
#include "activities/home/HomeActivity.h"
|
#include "activities/home/HomeActivity.h"
|
||||||
#include "activities/network/CrossPointWebServerActivity.h"
|
#include "activities/network/CrossPointWebServerActivity.h"
|
||||||
#include "activities/reader/ReaderActivity.h"
|
#include "activities/reader/ReaderActivity.h"
|
||||||
@ -290,6 +292,16 @@ void setup() {
|
|||||||
|
|
||||||
SETTINGS.loadFromFile();
|
SETTINGS.loadFromFile();
|
||||||
|
|
||||||
|
// Check if this is a timer wake for calendar mode
|
||||||
|
esp_sleep_wakeup_cause_t wakeupCause = esp_sleep_get_wakeup_cause();
|
||||||
|
if (wakeupCause == ESP_SLEEP_WAKEUP_TIMER && SETTINGS.calendarModeEnabled) {
|
||||||
|
Serial.printf("[%lu] [ ] Timer wake detected - entering calendar mode\n", millis());
|
||||||
|
setupDisplayAndFonts();
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new CalendarActivity(renderer, mappedInputManager));
|
||||||
|
return; // Skip normal boot flow
|
||||||
|
}
|
||||||
|
|
||||||
// verify power button press duration after we've read settings.
|
// verify power button press duration after we've read settings.
|
||||||
verifyWakeupLongPress();
|
verifyWakeupLongPress();
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user