diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 01cda73a29..0fd523ea33 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -123,6 +123,9 @@ void HomeActivity::onEnter() { firstRenderDone = false; recentsLoaded = false; recentsLoading = false; + const auto snapshot = WeatherActivity::getSilentRefreshSnapshot(); + weatherSyncSeenVersion = snapshot.completionVersion; + weatherRefreshing = snapshot.active; loadRecentBooks(4); requestUpdate(); } @@ -135,6 +138,8 @@ void HomeActivity::onExit() { // ── Input / Render dispatchers ──────────────────────────────────────────────── void HomeActivity::loop() { + pollWeatherSyncStatus(); + if (SETTINGS.uiTheme <= CrossPointSettings::LYRA_3_COVERS) loopOriginal(); else if (SETTINGS.uiTheme == CrossPointSettings::CROSSPET_CLASSIC) @@ -251,40 +256,29 @@ void HomeActivity::renderHeaderClock() { void HomeActivity::performSyncAfterWifi() { static char syncBuf[24]; - weatherRefreshing = true; - requestUpdateAndWait(); - - if (WiFi.status() != WL_CONNECTED) { - const auto& ssid = WIFI_STORE.getLastConnectedSsid(); - const auto* cred = ssid.empty() ? nullptr : WIFI_STORE.findCredential(ssid); - if (cred) { - WiFi.mode(WIFI_STA); - WiFi.begin(cred->ssid.c_str(), cred->password.c_str()); - const unsigned long connectStart = millis(); - while (WiFi.status() != WL_CONNECTED && millis() - connectStart < 8000) { - delay(100); - } - if (WiFi.status() != WL_CONNECTED) { - WiFi.disconnect(false); - WiFi.mode(WIFI_OFF); - weatherRefreshing = false; - snprintf(syncBuf, sizeof(syncBuf), "%s", tr(STR_WIFI_CONN_FAILED)); - syncResultMsg = syncBuf; - syncResultExpiry = millis() + 3000; - requestUpdate(); - return; - } - } + + const auto startResult = WeatherActivity::startSilentRefreshAsync(); + const auto snapshot = WeatherActivity::getSilentRefreshSnapshot(); + weatherRefreshing = snapshot.active; + + if (startResult == SilentRefreshStartResult::STARTED) { + syncResultMsg = nullptr; + requestUpdate(); + return; + } + + if (startResult == SilentRefreshStartResult::ALREADY_RUNNING) { + requestUpdate(); + return; } - int rc = WeatherActivity::silentRefresh(); - weatherRefreshing = false; - if (rc == 0) snprintf(syncBuf, sizeof(syncBuf), "%s", tr(STR_SYNC_OK)); - else if (rc == 2) snprintf(syncBuf, sizeof(syncBuf), "%s", tr(STR_WIFI_TIMEOUT)); - else snprintf(syncBuf, sizeof(syncBuf), tr(STR_API_ERROR), rc); + if (startResult == SilentRefreshStartResult::BUSY_FOREGROUND) { + snprintf(syncBuf, sizeof(syncBuf), "%s", tr(STR_FETCHING_WEATHER)); + } else { + snprintf(syncBuf, sizeof(syncBuf), tr(STR_API_ERROR), 6); + } syncResultMsg = syncBuf; syncResultExpiry = millis() + 3000; - coverRendered = false; requestUpdate(); } @@ -300,6 +294,30 @@ void HomeActivity::doSync() { performSyncAfterWifi(); } +void HomeActivity::pollWeatherSyncStatus() { + static char syncBuf[24]; + const auto snapshot = WeatherActivity::getSilentRefreshSnapshot(); + weatherRefreshing = snapshot.active; + + if (snapshot.completionVersion != weatherSyncSeenVersion) { + weatherSyncSeenVersion = snapshot.completionVersion; + weatherRefreshing = false; + if (snapshot.result == 0) { + snprintf(syncBuf, sizeof(syncBuf), "%s", tr(STR_SYNC_OK)); + } else if (snapshot.result == 2) { + snprintf(syncBuf, sizeof(syncBuf), "%s", tr(STR_WIFI_TIMEOUT)); + } else if (snapshot.result == 1) { + snprintf(syncBuf, sizeof(syncBuf), "%s", tr(STR_WIFI_CONN_FAILED)); + } else { + snprintf(syncBuf, sizeof(syncBuf), tr(STR_API_ERROR), snapshot.result); + } + syncResultMsg = syncBuf; + syncResultExpiry = millis() + 3000; + coverRendered = false; + requestUpdate(); + } +} + // ── Actions ─────────────────────────────────────────────────────────────────── void HomeActivity::onSelectBook(const std::string& path) { freeCoverBuffer(); activityManager.goToReader(path); } diff --git a/src/activities/home/HomeActivity.h b/src/activities/home/HomeActivity.h index dd1eedf4bb..6facbfa8c8 100644 --- a/src/activities/home/HomeActivity.h +++ b/src/activities/home/HomeActivity.h @@ -18,6 +18,7 @@ class HomeActivity final : public Activity { const char* syncResultMsg = nullptr; // "OK" or "Failed" after sync unsigned long syncResultExpiry = 0; // millis() when to clear message bool syncTriggered = false; // Guard against re-triggering sync while held + uint32_t weatherSyncSeenVersion = 0; 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 @@ -65,6 +66,7 @@ class HomeActivity final : public Activity { void renderHeaderClock(); void doSync(); void performSyncAfterWifi(); + void pollWeatherSyncStatus(); // Theme-specific render/loop dispatchers void renderCrossPet(); diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 1f75d64beb..4a9f05e42b 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include "CrossPointSettings.h" @@ -34,6 +35,9 @@ namespace { // pagesPerRefresh now comes from SETTINGS.getRefreshFrequency() constexpr unsigned long skipChapterMs = 700; constexpr unsigned long goHomeMs = 1000; +constexpr unsigned long bookmarkToastDurationMs = 900; +constexpr unsigned long progressSaveDebounceMs = 2000; +constexpr uint8_t maxPageLoadRetries = 3; // Max pages per minute for auto-turn (setting range 1-20) constexpr uint8_t AUTO_TURN_MAX_PPM = 20; @@ -113,6 +117,10 @@ void EpubReaderActivity::onEnter() { void EpubReaderActivity::onExit() { Activity::onExit(); + if (epub && section && section->pageCount > 0) { + flushProgressSave(currentSpineIndex, section->currentPage, section->pageCount); + } + // Reset text darkness to normal for UI screens renderer.setTextDarkness(0); @@ -189,6 +197,12 @@ void EpubReaderActivity::loop() { requestUpdate(); } + // Dismiss bookmark toast after timeout (non-blocking) + if (showBookmarkToast && millis() - bookmarkToastTime > bookmarkToastDurationMs) { + showBookmarkToast = false; + requestUpdate(); + } + if (automaticPageTurnActive) { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) || mappedInput.wasReleased(MappedInputManager::Button::Back)) { @@ -258,9 +272,7 @@ void EpubReaderActivity::loop() { } } bookmarkStore.toggle(si, pg, snippet); - GUI.drawPopup(renderer, wasStarred ? tr(STR_PAGE_UNSTARRED) : tr(STR_PAGE_STARRED)); - renderer.displayBuffer(); - delay(600); + queueBookmarkToast(wasStarred ? tr(STR_PAGE_UNSTARRED) : tr(STR_PAGE_STARRED)); requestUpdate(); } return; @@ -356,9 +368,7 @@ void EpubReaderActivity::loop() { } } bookmarkStore.toggle(si, pg, snippet); - GUI.drawPopup(renderer, wasStarred ? tr(STR_PAGE_UNSTARRED) : tr(STR_PAGE_STARRED)); - renderer.displayBuffer(); - delay(600); + queueBookmarkToast(wasStarred ? tr(STR_PAGE_UNSTARRED) : tr(STR_PAGE_STARRED)); requestUpdate(); } return; @@ -650,6 +660,9 @@ void EpubReaderActivity::applyOrientation(const uint8_t orientation) { // Update renderer orientation to match the new logical coordinate system. ReaderUtils::applyOrientation(renderer, SETTINGS.orientation); + // Orientation changes invalidate glyph raster layout assumptions. + forceFontCacheClear = true; + // Reset section to force re-layout in the new orientation. section.reset(); } @@ -932,26 +945,75 @@ void EpubReaderActivity::render(RenderLock&& lock) { } { + const int attemptedPage = section->currentPage; auto p = section->loadPageFromSectionFile(); if (!p) { - LOG_ERR("ERS", "Failed to load page from SD - clearing section cache"); - section->clearCache(); + if (failedLoadSpineIndex == currentSpineIndex && failedLoadPage == attemptedPage) { + consecutiveLoadFailures++; + } else { + failedLoadSpineIndex = currentSpineIndex; + failedLoadPage = attemptedPage; + consecutiveLoadFailures = 1; + } + + LOG_ERR("ERS", "Failed to load page from SD (spine=%d, page=%d, retry=%d)", currentSpineIndex, + attemptedPage, consecutiveLoadFailures); + + const bool tooManyFailures = consecutiveLoadFailures >= maxPageLoadRetries; + if (!tooManyFailures) { + section->clearCache(); + } section.reset(); - requestUpdate(); // Try again after clearing cache - // TODO: prevent infinite loop if the page keeps failing to load for some reason + + if (tooManyFailures) { + automaticPageTurnActive = false; + consecutiveLoadFailures = 0; + failedLoadSpineIndex = -1; + failedLoadPage = -1; + + if (currentSpineIndex < static_cast(epub->getSpineItemsCount()) - 1) { + currentSpineIndex++; + nextPageNumber = 0; + queueBookmarkToast("Skipped bad chapter"); + requestUpdate(); + } else if (currentSpineIndex > 0) { + // Last chapter failed repeatedly: step back to previous chapter instead of looping forever. + currentSpineIndex--; + nextPageNumber = UINT16_MAX; + queueBookmarkToast("Skipped bad chapter"); + requestUpdate(); + } else { + // Single-chapter unrecoverable case: show once and stop auto-retrying this frame. + GUI.drawPopup(renderer, "Page load failed"); + renderer.displayBuffer(); + } + return; + } + + requestUpdate(); automaticPageTurnActive = false; return; } + consecutiveLoadFailures = 0; + failedLoadSpineIndex = -1; + failedLoadPage = -1; + // Collect footnotes from the loaded page currentPageFootnotes = std::move(p->footnotes); const auto start = millis(); renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft); LOG_DBG("ERS", "Rendered page in %dms", millis() - start); - renderer.clearFontCache(); + if (shouldClearFontCache()) { + renderer.clearFontCache(); + forceFontCacheClear = false; + lastRenderedFontFamily = SETTINGS.fontFamily; + lastRenderedFontSize = SETTINGS.fontSize; + lastRenderedOrientation = SETTINGS.orientation; + } } - saveProgress(currentSpineIndex, section->currentPage, section->pageCount); + maybeSaveProgress(currentSpineIndex, section->currentPage, section->pageCount); if (pendingScreenshot) { pendingScreenshot = false; @@ -979,6 +1041,10 @@ void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageC data[5] = (pageCount >> 8) & 0xFF; f.write(data, 6); f.close(); + lastSavedSpineIndex = spineIndex; + lastSavedPage = currentPage; + lastSavedPageCount = pageCount; + lastProgressSaveMs = millis(); LOG_DBG("ERS", "Progress saved: Chapter %d, Page %d", spineIndex, currentPage); } else { LOG_ERR("ERS", "Could not save progress!"); @@ -990,6 +1056,48 @@ void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageC const uint8_t percent = static_cast(clampPercent(static_cast(bookProg + 0.5f))); RECENT_BOOKS.updateBookProgress(epub->getPath(), percent); } + +void EpubReaderActivity::queueBookmarkToast(const char* text) { + if (!text) { + return; + } + std::strncpy(bookmarkToastText, text, sizeof(bookmarkToastText) - 1); + bookmarkToastText[sizeof(bookmarkToastText) - 1] = '\0'; + showBookmarkToast = true; + bookmarkToastTime = millis(); +} + +void EpubReaderActivity::maybeSaveProgress(int spineIndex, int currentPage, int pageCount) { + const bool changed = spineIndex != lastSavedSpineIndex || + currentPage != lastSavedPage || + pageCount != lastSavedPageCount; + if (!changed) { + return; + } + + if (millis() - lastProgressSaveMs < progressSaveDebounceMs) { + return; + } + + saveProgress(spineIndex, currentPage, pageCount); +} + +void EpubReaderActivity::flushProgressSave(int spineIndex, int currentPage, int pageCount) { + const bool changed = spineIndex != lastSavedSpineIndex || + currentPage != lastSavedPage || + pageCount != lastSavedPageCount; + if (changed) { + saveProgress(spineIndex, currentPage, pageCount); + } +} + +bool EpubReaderActivity::shouldClearFontCache() const { + return forceFontCacheClear || + lastRenderedFontFamily != SETTINGS.fontFamily || + lastRenderedFontSize != SETTINGS.fontSize || + lastRenderedOrientation != SETTINGS.orientation; +} + void EpubReaderActivity::renderContents(std::unique_ptr page, const int orientedMarginTop, const int orientedMarginRight, const int orientedMarginBottom, const int orientedMarginLeft) { @@ -1011,6 +1119,17 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or renderer.drawText(SMALL_FONT_ID, tx + 8, ty + 4, milestoneText, true, EpdFontFamily::BOLD); } + if (showBookmarkToast && bookmarkToastText[0]) { + const int screenW = renderer.getScreenWidth(); + const int toastW = renderer.getTextWidth(SMALL_FONT_ID, bookmarkToastText) + 16; + const int toastH = renderer.getLineHeight(SMALL_FONT_ID) + 8; + const int tx = (screenW - toastW) / 2; + const int ty = renderer.getScreenHeight() - orientedMarginBottom - toastH - 4; + renderer.fillRect(tx, ty, toastW, toastH, false); + renderer.drawRect(tx, ty, toastW, toastH); + renderer.drawText(SMALL_FONT_ID, tx + 8, ty + 4, bookmarkToastText, true, EpdFontFamily::BOLD); + } + if (imagePageWithAA) { // Double FAST_REFRESH with selective image blanking (pablohc's technique): // HALF_REFRESH sets particles too firmly for the grayscale LUT to adjust. diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index c491e92b09..c72cf3f394 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -51,6 +51,28 @@ class EpubReaderActivity final : public Activity { unsigned long milestoneToastTime = 0; char milestoneText[48] = {}; + // Bookmark toast (non-blocking) + bool showBookmarkToast = false; + unsigned long bookmarkToastTime = 0; + char bookmarkToastText[32] = {}; + + // Progress write debounce state + int lastSavedSpineIndex = -1; + int lastSavedPage = -1; + int lastSavedPageCount = -1; + unsigned long lastProgressSaveMs = 0; + + // Font cache invalidation state + bool forceFontCacheClear = true; + uint8_t lastRenderedFontFamily = 0xFF; + uint8_t lastRenderedFontSize = 0xFF; + uint8_t lastRenderedOrientation = 0xFF; + + // Page load failure guard to avoid infinite retry loops + int failedLoadSpineIndex = -1; + int failedLoadPage = -1; + uint8_t consecutiveLoadFailures = 0; + // Bookmarks (starred pages) BookmarkStore bookmarkStore; @@ -69,6 +91,10 @@ class EpubReaderActivity final : public Activity { void renderStatusBar() const; int getEstimatedMinutesLeft() const; void saveProgress(int spineIndex, int currentPage, int pageCount); + void queueBookmarkToast(const char* text); + void maybeSaveProgress(int spineIndex, int currentPage, int pageCount); + void flushProgressSave(int spineIndex, int currentPage, int pageCount); + bool shouldClearFontCache() const; // Jump to a percentage of the book (0-100), mapping it to spine and page. void jumpToPercent(int percent); void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action); diff --git a/src/activities/tools/WeatherActivity.cpp b/src/activities/tools/WeatherActivity.cpp index 4bb3b24a82..23603347e1 100644 --- a/src/activities/tools/WeatherActivity.cpp +++ b/src/activities/tools/WeatherActivity.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "CrossPointSettings.h" #include "WifiCredentialStore.h" @@ -103,8 +105,35 @@ static bool trySilentWifiConnect() { return false; } +namespace { +enum class AsyncSyncState : uint8_t { IDLE = 0, RUNNING = 1, DONE = 2 }; + +volatile AsyncSyncState g_asyncSyncState = AsyncSyncState::IDLE; +volatile int g_asyncSyncResult = -1; +volatile uint32_t g_asyncSyncCompletionVersion = 0; +volatile bool g_weatherActivityForegroundActive = false; +TaskHandle_t g_asyncSyncTaskHandle = nullptr; + +void weatherSilentRefreshTask(void*) { + const int rc = WeatherActivity::silentRefresh(); + + taskENTER_CRITICAL(nullptr); + g_asyncSyncResult = rc; + g_asyncSyncState = AsyncSyncState::DONE; + g_asyncSyncCompletionVersion++; + g_asyncSyncTaskHandle = nullptr; + taskEXIT_CRITICAL(nullptr); + + vTaskDelete(nullptr); +} +} // namespace + void WeatherActivity::onEnter() { Activity::onEnter(); + taskENTER_CRITICAL(nullptr); + g_weatherActivityForegroundActive = true; + taskEXIT_CRITICAL(nullptr); + selectedCity = SETTINGS.weatherCity; if (selectedCity > CITY_COUNT) selectedCity = 0; // 0=Auto, 1..3=manual lastUpdateTime[0] = '\0'; @@ -472,7 +501,55 @@ int WeatherActivity::silentRefresh() { return 0; } +SilentRefreshStartResult WeatherActivity::startSilentRefreshAsync() { + taskENTER_CRITICAL(nullptr); + if (g_weatherActivityForegroundActive) { + taskEXIT_CRITICAL(nullptr); + return SilentRefreshStartResult::BUSY_FOREGROUND; + } + if (g_asyncSyncState == AsyncSyncState::RUNNING || g_asyncSyncTaskHandle != nullptr) { + taskEXIT_CRITICAL(nullptr); + return SilentRefreshStartResult::ALREADY_RUNNING; + } + + g_asyncSyncState = AsyncSyncState::RUNNING; + g_asyncSyncResult = -1; + taskEXIT_CRITICAL(nullptr); + + BaseType_t created = xTaskCreate(&weatherSilentRefreshTask, + "WeatherSilentSync", + 8192, + nullptr, + 1, + &g_asyncSyncTaskHandle); + if (created != pdPASS) { + taskENTER_CRITICAL(nullptr); + g_asyncSyncState = AsyncSyncState::IDLE; + g_asyncSyncTaskHandle = nullptr; + taskEXIT_CRITICAL(nullptr); + return SilentRefreshStartResult::TASK_CREATE_FAILED; + } + + return SilentRefreshStartResult::STARTED; +} + +SilentRefreshSnapshot WeatherActivity::getSilentRefreshSnapshot() { + SilentRefreshSnapshot snapshot; + + taskENTER_CRITICAL(nullptr); + snapshot.active = (g_asyncSyncState == AsyncSyncState::RUNNING); + snapshot.result = g_asyncSyncResult; + snapshot.completionVersion = g_asyncSyncCompletionVersion; + taskEXIT_CRITICAL(nullptr); + + return snapshot; +} + void WeatherActivity::onExit() { + taskENTER_CRITICAL(nullptr); + g_weatherActivityForegroundActive = false; + taskEXIT_CRITICAL(nullptr); + SETTINGS.weatherCity = selectedCity; SETTINGS.saveToFile(); WiFi.disconnect(false); diff --git a/src/activities/tools/WeatherActivity.h b/src/activities/tools/WeatherActivity.h index 81ed295f09..320f919964 100644 --- a/src/activities/tools/WeatherActivity.h +++ b/src/activities/tools/WeatherActivity.h @@ -26,6 +26,19 @@ struct DailyForecast { char dayLabel[4] = ""; // "Mon", "Tue", etc. }; +struct SilentRefreshSnapshot { + bool active = false; + int result = -1; + uint32_t completionVersion = 0; +}; + +enum class SilentRefreshStartResult : uint8_t { + STARTED = 0, + ALREADY_RUNNING = 1, + BUSY_FOREGROUND = 2, + TASK_CREATE_FAILED = 3, +}; + class WeatherActivity final : public Activity { public: enum State { WIFI_CONNECTING, FETCHING, DISPLAYING, FETCH_ERROR, SELECTING_CITY }; @@ -61,6 +74,10 @@ class WeatherActivity final : public Activity { // Returns: 0=ok, 1=no saved wifi creds, 2=wifi connect timeout, 3=geo fail, 4=api fail, 5=parse fail static int silentRefresh(); + // Non-blocking wrapper: run silentRefresh() in background task. + static SilentRefreshStartResult startSilentRefreshAsync(); + static SilentRefreshSnapshot getSilentRefreshSnapshot(); + private: static constexpr int FORECAST_DAYS = 5; State state = WIFI_CONNECTING; diff --git a/src/main.cpp b/src/main.cpp index 31f178c922..0e8c2e2ee3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -490,6 +490,7 @@ void loop() { static unsigned long maxLoopDuration = 0; const unsigned long loopStartTime = millis(); static unsigned long lastMemPrint = 0; + static uint32_t weatherSyncSeenVersion = 0; gpio.update(); @@ -515,6 +516,14 @@ void loop() { lastMemPrint = millis(); } + const auto weatherSync = WeatherActivity::getSilentRefreshSnapshot(); + if (weatherSync.completionVersion != weatherSyncSeenVersion) { + weatherSyncSeenVersion = weatherSync.completionVersion; + LOG_INF("PWR", "Async sync result: %d (0=ok, 1=no creds, 2=timeout, 4=api, 5=parse)", weatherSync.result); + renderer.requestNextHalfRefresh(); + activityManager.requestUpdate(); + } + // Handle incoming serial commands, // nb: we use logSerial from logging to avoid deprecation warnings if (logSerial.available() > 0) { @@ -534,6 +543,7 @@ void loop() { // Check for any user activity (button press or release) or active background work static unsigned long lastActivityTime = millis(); bool hasActivity = gpio.wasAnyPressed() || gpio.wasAnyReleased() || activityManager.preventAutoSleep(); + hasActivity = hasActivity || weatherSync.active; #ifdef ENABLE_BLE // BLE HID reports count as user activity — prevent auto-sleep while remote is in use hasActivity = hasActivity || BluetoothHIDManager::getInstance().hasRecentActivity(); @@ -596,11 +606,14 @@ void loop() { break; case CrossPointSettings::SHORT_PWRBTN::SYNC_WEATHER_TIME: { LOG_INF("PWR", "Sync weather/time triggered"); - int syncResult = WeatherActivity::silentRefresh(); - LOG_INF("PWR", "Sync result: %d (0=ok, 1=no creds, 2=timeout, 4=api, 5=parse)", syncResult); - // Refresh screen after time/weather sync to show updated clock - renderer.requestNextHalfRefresh(); - activityManager.requestUpdate(); + const auto startResult = WeatherActivity::startSilentRefreshAsync(); + if (startResult == SilentRefreshStartResult::ALREADY_RUNNING) { + LOG_INF("PWR", "Sync already running"); + } else if (startResult == SilentRefreshStartResult::BUSY_FOREGROUND) { + LOG_INF("PWR", "Weather activity is active; skip background sync trigger"); + } else if (startResult == SilentRefreshStartResult::TASK_CREATE_FAILED) { + LOG_ERR("PWR", "Failed to start background sync task"); + } break; } default: