#include "NtpSyncActivity.h" #include #include #include #include #include "activities/ActivityResult.h" #include "CrossPointSettings.h" #include "MappedInputManager.h" #include "activities/network/WifiSelectionActivity.h" #include "components/UITheme.h" #include "fontIds.h" #include "util/BootNtpSync.h" #include "util/TimeSync.h" static constexpr unsigned long AUTO_DISMISS_MS = 5000; void NtpSyncActivity::onWifiSelectionComplete(const bool success) { if (!success) { LOG_ERR("NTP", "WiFi connection failed, exiting"); finish(); return; } LOG_DBG("NTP", "WiFi connected, starting NTP sync"); { RenderLock lock(*this); state = SYNCING; } requestUpdateAndWait(); const bool synced = TimeSync::waitForNtpSync(8000); { RenderLock lock(*this); state = synced ? SUCCESS : FAILED; if (synced) { successTimestamp = millis(); } } requestUpdate(); if (synced) { LOG_DBG("NTP", "Time synced successfully"); } else { LOG_ERR("NTP", "NTP sync timed out"); } } void NtpSyncActivity::onEnter() { Activity::onEnter(); BootNtpSync::cancel(); LOG_DBG("NTP", "Turning on WiFi..."); WiFi.mode(WIFI_STA); LOG_DBG("NTP", "Launching WifiSelectionActivity..."); startActivityForResult( std::make_unique(renderer, mappedInput), [this](const ActivityResult& result) { const bool success = !result.isCancelled && std::holds_alternative(result.data); onWifiSelectionComplete(success); }); } void NtpSyncActivity::onExit() { Activity::onExit(); TimeSync::stopNtpSync(); WiFi.disconnect(false); delay(100); WiFi.mode(WIFI_OFF); delay(100); } void NtpSyncActivity::render(RenderLock&&) { auto metrics = UITheme::getInstance().getMetrics(); const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight(); renderer.clearScreen(); GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_SYNC_CLOCK)); const auto lineHeight = renderer.getLineHeight(UI_10_FONT_ID); const auto centerY = (pageHeight - lineHeight) / 2; if (state == SYNCING) { renderer.drawCenteredText(UI_10_FONT_ID, centerY, tr(STR_SYNCING_TIME)); } else if (state == SUCCESS) { renderer.drawCenteredText(UI_10_FONT_ID, centerY, tr(STR_TIME_SYNCED), true, EpdFontFamily::BOLD); time_t now = time(nullptr); struct tm* t = localtime(&now); if (t != nullptr && t->tm_year > 100) { char timeBuf[32]; if (SETTINGS.clockFormat == 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.drawCenteredText(UI_10_FONT_ID, centerY + lineHeight + metrics.verticalSpacing, timeBuf); } const unsigned long elapsed = millis() - successTimestamp; const int remaining = static_cast((AUTO_DISMISS_MS - elapsed + 999) / 1000); char backLabel[32]; snprintf(backLabel, sizeof(backLabel), "%s (%d)", tr(STR_BACK), remaining > 0 ? remaining : 1); const auto labels = mappedInput.mapLabels(backLabel, "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } else if (state == FAILED) { renderer.drawCenteredText(UI_10_FONT_ID, centerY, tr(STR_SYNC_FAILED_MSG), true, EpdFontFamily::BOLD); const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } renderer.displayBuffer(); } void NtpSyncActivity::loop() { if (state == SUCCESS) { const unsigned long elapsed = millis() - successTimestamp; if (mappedInput.wasPressed(MappedInputManager::Button::Back) || elapsed >= AUTO_DISMISS_MS) { finish(); return; } const int currentSecond = static_cast(elapsed / 1000); if (currentSecond != lastCountdownSecond) { lastCountdownSecond = currentSecond; requestUpdate(); } return; } if (state == FAILED) { if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { finish(); } return; } }