mod: fix clock bugs, add NTP auto-sync, show clock in all headers
- Fix SetTimeActivity immediately dismissing by changing wasReleased to wasPressed for all button inputs (matching other subactivities) - Extract NTP sync into shared TimeSync utility (startNtpSync, waitForNtpSync, stopNtpSync) and trigger non-blocking NTP sync on every WiFi connection - Move clock rendering into drawHeader (BaseTheme + LyraTheme) so it appears on all screens with a header, positioned symmetrically with the battery icon (12px margin, same Y offset, SMALL_FONT_ID) - Add per-minute auto-refresh on home screen so clock updates without button press - Add RTC time debug log on boot to verify time persistence across deep sleep Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -192,6 +192,19 @@ void HomeActivity::freeCoverBuffer() {
|
||||
}
|
||||
|
||||
void HomeActivity::loop() {
|
||||
// Refresh the screen when the displayed minute changes (clock update)
|
||||
if (SETTINGS.homeScreenClock != CrossPointSettings::CLOCK_OFF) {
|
||||
time_t now = time(nullptr);
|
||||
struct tm* t = localtime(&now);
|
||||
if (t != nullptr && t->tm_year > 100) {
|
||||
const int currentMinute = t->tm_hour * 60 + t->tm_min;
|
||||
if (lastRenderedMinute >= 0 && currentMinute != lastRenderedMinute) {
|
||||
requestUpdate();
|
||||
}
|
||||
lastRenderedMinute = currentMinute;
|
||||
}
|
||||
}
|
||||
|
||||
const int menuCount = getMenuItemCount();
|
||||
|
||||
buttonNavigator.onNext([this, menuCount] {
|
||||
@@ -240,23 +253,6 @@ void HomeActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.homeTopPadding}, nullptr);
|
||||
|
||||
// Draw clock in the header area (left side) if enabled
|
||||
if (SETTINGS.homeScreenClock != CrossPointSettings::CLOCK_OFF) {
|
||||
time_t now = time(nullptr);
|
||||
struct tm* t = localtime(&now);
|
||||
if (t != nullptr && t->tm_year > 100) {
|
||||
char timeBuf[16];
|
||||
if (SETTINGS.homeScreenClock == CrossPointSettings::CLOCK_24H) {
|
||||
snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d", t->tm_hour, t->tm_min);
|
||||
} else {
|
||||
int hour12 = t->tm_hour % 12;
|
||||
if (hour12 == 0) hour12 = 12;
|
||||
snprintf(timeBuf, sizeof(timeBuf), "%d:%02d %s", hour12, t->tm_min, t->tm_hour >= 12 ? "PM" : "AM");
|
||||
}
|
||||
renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding, metrics.topPadding, timeBuf, true);
|
||||
}
|
||||
}
|
||||
|
||||
GUI.drawRecentBookCover(renderer, Rect{0, metrics.homeTopPadding, pageWidth, metrics.homeCoverTileHeight},
|
||||
recentBooks, selectorIndex, coverRendered, coverBufferStored, bufferRestored,
|
||||
std::bind(&HomeActivity::storeCoverBuffer, this));
|
||||
|
||||
@@ -16,6 +16,7 @@ class HomeActivity final : public Activity {
|
||||
bool recentsLoaded = false;
|
||||
bool firstRenderDone = false;
|
||||
bool hasOpdsUrl = false;
|
||||
int lastRenderedMinute = -1; // Track displayed minute for clock auto-update
|
||||
bool coverRendered = false; // Track if cover has been rendered once
|
||||
bool coverBufferStored = false; // Track if cover buffer is stored
|
||||
uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "activities/util/KeyboardEntryActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "util/TimeSync.h"
|
||||
|
||||
void WifiSelectionActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
@@ -243,6 +244,9 @@ void WifiSelectionActivity::checkConnectionStatus() {
|
||||
connectedIP = ipStr;
|
||||
autoConnecting = false;
|
||||
|
||||
// Start NTP time sync in the background (non-blocking)
|
||||
TimeSync::startNtpSync();
|
||||
|
||||
// Save this as the last connected network - SD card operations need lock as
|
||||
// we use SPI for both
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <I18n.h>
|
||||
#include <Logging.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_sntp.h>
|
||||
|
||||
#include "KOReaderCredentialStore.h"
|
||||
#include "KOReaderDocumentId.h"
|
||||
@@ -12,34 +11,7 @@
|
||||
#include "activities/network/WifiSelectionActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
void syncTimeWithNTP() {
|
||||
// Stop SNTP if already running (can't reconfigure while running)
|
||||
if (esp_sntp_enabled()) {
|
||||
esp_sntp_stop();
|
||||
}
|
||||
|
||||
// Configure SNTP
|
||||
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
|
||||
esp_sntp_setservername(0, "pool.ntp.org");
|
||||
esp_sntp_init();
|
||||
|
||||
// Wait for time to sync (with timeout)
|
||||
int retry = 0;
|
||||
const int maxRetries = 50; // 5 seconds max
|
||||
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED && retry < maxRetries) {
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
retry++;
|
||||
}
|
||||
|
||||
if (retry < maxRetries) {
|
||||
LOG_DBG("KOSync", "NTP time synced");
|
||||
} else {
|
||||
LOG_DBG("KOSync", "NTP sync timeout, using fallback");
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
#include "util/TimeSync.h"
|
||||
|
||||
void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
||||
exitActivity();
|
||||
@@ -59,8 +31,8 @@ void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
||||
}
|
||||
requestUpdate();
|
||||
|
||||
// Sync time with NTP before making API requests
|
||||
syncTimeWithNTP();
|
||||
// Wait for NTP sync before making API requests (blocks up to 5s)
|
||||
TimeSync::waitForNtpSync();
|
||||
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
@@ -199,8 +171,8 @@ void KOReaderSyncActivity::onEnter() {
|
||||
xTaskCreate(
|
||||
[](void* param) {
|
||||
auto* self = static_cast<KOReaderSyncActivity*>(param);
|
||||
// Sync time first
|
||||
syncTimeWithNTP();
|
||||
// Wait for NTP sync before making API requests
|
||||
TimeSync::waitForNtpSync();
|
||||
{
|
||||
RenderLock lock(*self);
|
||||
self->statusMessage = tr(STR_CALC_HASH);
|
||||
|
||||
@@ -34,25 +34,25 @@ void SetTimeActivity::onExit() { Activity::onExit(); }
|
||||
|
||||
void SetTimeActivity::loop() {
|
||||
// Back button: discard and exit
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm button: apply time and exit
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
applyTime();
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
|
||||
// Left/Right: switch between hour and minute fields
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Left)) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||
selectedField = 0;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Right)) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||
selectedField = 1;
|
||||
requestUpdate();
|
||||
return;
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#include <Utf8.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "I18n.h"
|
||||
#include "RecentBooksStore.h"
|
||||
#include "components/UITheme.h"
|
||||
@@ -260,6 +262,23 @@ void BaseTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
|
||||
Rect{batteryX, rect.y + 5, BaseMetrics::values.batteryWidth, BaseMetrics::values.batteryHeight},
|
||||
showBatteryPercentage);
|
||||
|
||||
// Draw clock on the left side (symmetric with battery on the right)
|
||||
if (SETTINGS.homeScreenClock != CrossPointSettings::CLOCK_OFF) {
|
||||
time_t now = time(nullptr);
|
||||
struct tm* t = localtime(&now);
|
||||
if (t != nullptr && t->tm_year > 100) {
|
||||
char timeBuf[16];
|
||||
if (SETTINGS.homeScreenClock == CrossPointSettings::CLOCK_24H) {
|
||||
snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d", t->tm_hour, t->tm_min);
|
||||
} else {
|
||||
int hour12 = t->tm_hour % 12;
|
||||
if (hour12 == 0) hour12 = 12;
|
||||
snprintf(timeBuf, sizeof(timeBuf), "%d:%02d %s", hour12, t->tm_min, t->tm_hour >= 12 ? "PM" : "AM");
|
||||
}
|
||||
renderer.drawText(SMALL_FONT_ID, rect.x + 12, rect.y + 5, timeBuf, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (title) {
|
||||
int padding = rect.width - batteryX + BaseMetrics::values.batteryWidth;
|
||||
auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, title,
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include <Utf8.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "RecentBooksStore.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
@@ -113,6 +115,23 @@ void LyraTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
|
||||
Rect{batteryX, rect.y + 5, LyraMetrics::values.batteryWidth, LyraMetrics::values.batteryHeight},
|
||||
showBatteryPercentage);
|
||||
|
||||
// Draw clock on the left side (symmetric with battery on the right)
|
||||
if (SETTINGS.homeScreenClock != CrossPointSettings::CLOCK_OFF) {
|
||||
time_t now = time(nullptr);
|
||||
struct tm* t = localtime(&now);
|
||||
if (t != nullptr && t->tm_year > 100) {
|
||||
char timeBuf[16];
|
||||
if (SETTINGS.homeScreenClock == CrossPointSettings::CLOCK_24H) {
|
||||
snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d", t->tm_hour, t->tm_min);
|
||||
} else {
|
||||
int hour12 = t->tm_hour % 12;
|
||||
if (hour12 == 0) hour12 = 12;
|
||||
snprintf(timeBuf, sizeof(timeBuf), "%d:%02d %s", hour12, t->tm_min, t->tm_hour >= 12 ? "PM" : "AM");
|
||||
}
|
||||
renderer.drawText(SMALL_FONT_ID, rect.x + 12, rect.y + 5, timeBuf, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (title) {
|
||||
auto truncatedTitle = renderer.truncatedText(
|
||||
UI_12_FONT_ID, title, rect.width - LyraMetrics::values.contentSidePadding * 2, EpdFontFamily::BOLD);
|
||||
|
||||
13
src/main.cpp
13
src/main.cpp
@@ -11,6 +11,7 @@
|
||||
#include <builtinFonts/all.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "CrossPointSettings.h"
|
||||
@@ -350,6 +351,18 @@ 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);
|
||||
|
||||
// Log RTC time to verify persistence across deep sleep
|
||||
{
|
||||
time_t now = time(nullptr);
|
||||
struct tm* t = localtime(&now);
|
||||
if (t != nullptr && t->tm_year > 100) {
|
||||
LOG_DBG("MAIN", "RTC time: %04d-%02d-%02d %02d:%02d:%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
|
||||
t->tm_hour, t->tm_min, t->tm_sec);
|
||||
} else {
|
||||
LOG_DBG("MAIN", "RTC time not set (epoch)");
|
||||
}
|
||||
}
|
||||
|
||||
setupDisplayAndFonts();
|
||||
|
||||
exitActivity();
|
||||
|
||||
50
src/util/TimeSync.cpp
Normal file
50
src/util/TimeSync.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "TimeSync.h"
|
||||
|
||||
#include <Logging.h>
|
||||
#include <esp_sntp.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
namespace TimeSync {
|
||||
|
||||
void startNtpSync() {
|
||||
if (esp_sntp_enabled()) {
|
||||
esp_sntp_stop();
|
||||
}
|
||||
|
||||
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
|
||||
esp_sntp_setservername(0, "pool.ntp.org");
|
||||
esp_sntp_init();
|
||||
|
||||
LOG_DBG("NTP", "SNTP service started");
|
||||
}
|
||||
|
||||
bool waitForNtpSync(int timeoutMs) {
|
||||
startNtpSync();
|
||||
|
||||
const int intervalMs = 100;
|
||||
const int maxRetries = timeoutMs / intervalMs;
|
||||
int retry = 0;
|
||||
|
||||
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED && retry < maxRetries) {
|
||||
vTaskDelay(intervalMs / portTICK_PERIOD_MS);
|
||||
retry++;
|
||||
}
|
||||
|
||||
if (retry < maxRetries) {
|
||||
LOG_DBG("NTP", "Time synced after %d ms", retry * intervalMs);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_DBG("NTP", "Sync timeout after %d ms", timeoutMs);
|
||||
return false;
|
||||
}
|
||||
|
||||
void stopNtpSync() {
|
||||
if (esp_sntp_enabled()) {
|
||||
esp_sntp_stop();
|
||||
LOG_DBG("NTP", "SNTP service stopped");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TimeSync
|
||||
17
src/util/TimeSync.h
Normal file
17
src/util/TimeSync.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
namespace TimeSync {
|
||||
|
||||
// Start NTP time synchronization (non-blocking).
|
||||
// Configures and starts the SNTP service; time will be updated
|
||||
// automatically when the NTP response arrives.
|
||||
void startNtpSync();
|
||||
|
||||
// Start NTP sync and block until complete or timeout.
|
||||
// Returns true if time was synced, false on timeout.
|
||||
bool waitForNtpSync(int timeoutMs = 5000);
|
||||
|
||||
// Stop the SNTP service. Call before disconnecting WiFi.
|
||||
void stopNtpSync();
|
||||
|
||||
} // namespace TimeSync
|
||||
Reference in New Issue
Block a user