#include "OtaUpdateActivity.h" #include #include #include #include "MappedInputManager.h" #include "activities/network/WifiSelectionActivity.h" #include "components/UITheme.h" #include "fontIds.h" #include "network/OtaUpdater.h" void OtaUpdateActivity::onWifiSelectionComplete(const bool success) { exitActivity(); if (!success) { LOG_ERR("OTA", "WiFi connection failed, exiting"); goBack(); return; } LOG_DBG("OTA", "WiFi connected, checking for update"); { RenderLock lock(*this); state = CHECKING_FOR_UPDATE; } requestUpdateAndWait(); const auto res = updater.checkForUpdate(); if (res != OtaUpdater::OK) { LOG_DBG("OTA", "Update check failed: %d", res); { RenderLock lock(*this); state = FAILED; } requestUpdate(); return; } if (!updater.isUpdateNewer()) { LOG_DBG("OTA", "No new update available"); { RenderLock lock(*this); state = NO_UPDATE; } requestUpdate(); return; } { RenderLock lock(*this); state = WAITING_CONFIRMATION; } requestUpdate(); } void OtaUpdateActivity::onEnter() { ActivityWithSubactivity::onEnter(); // Turn on WiFi immediately LOG_DBG("OTA", "Turning on WiFi..."); WiFi.mode(WIFI_STA); // Launch WiFi selection subactivity LOG_DBG("OTA", "Launching WifiSelectionActivity..."); enterNewActivity(new WifiSelectionActivity(renderer, mappedInput, [this](const bool connected) { onWifiSelectionComplete(connected); })); } void OtaUpdateActivity::onExit() { ActivityWithSubactivity::onExit(); // Turn off wifi WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame delay(100); // Allow disconnect frame to be sent WiFi.mode(WIFI_OFF); delay(100); // Allow WiFi hardware to fully power down } void OtaUpdateActivity::render(Activity::RenderLock&&) { if (subActivity) { // Subactivity handles its own rendering return; } 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_UPDATE)); const auto height = renderer.getLineHeight(UI_10_FONT_ID); const auto top = (pageHeight - height) / 2; float updaterProgress = 0; if (state == UPDATE_IN_PROGRESS) { LOG_DBG("OTA", "Update progress: %d / %d", updater.getProcessedSize(), updater.getTotalSize()); updaterProgress = static_cast(updater.getProcessedSize()) / static_cast(updater.getTotalSize()); // Only update every 2% at the most if (static_cast(updaterProgress * 50) == lastUpdaterPercentage / 2) { return; } lastUpdaterPercentage = static_cast(updaterProgress * 100); } if (state == CHECKING_FOR_UPDATE) { renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_CHECKING_UPDATE)); } else if (state == WAITING_CONFIRMATION) { renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_NEW_UPDATE), true, EpdFontFamily::BOLD); renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, top + height + metrics.verticalSpacing, (std::string(tr(STR_CURRENT_VERSION)) + CROSSPOINT_VERSION).c_str()); renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, top + height * 2 + metrics.verticalSpacing * 2, (std::string(tr(STR_NEW_VERSION)) + updater.getLatestVersion()).c_str()); const auto labels = mappedInput.mapLabels(tr(STR_CANCEL), tr(STR_UPDATE), "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } else if (state == UPDATE_IN_PROGRESS) { renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_UPDATING)); int y = top + height + metrics.verticalSpacing; GUI.drawProgressBar( renderer, Rect{metrics.contentSidePadding, y, pageWidth - metrics.contentSidePadding * 2, metrics.progressBarHeight}, static_cast(updaterProgress * 100), 100); y += metrics.progressBarHeight + metrics.verticalSpacing; renderer.drawCenteredText(UI_10_FONT_ID, y, (std::to_string(static_cast(updaterProgress * 100)) + "%").c_str()); y += height + metrics.verticalSpacing; renderer.drawCenteredText( UI_10_FONT_ID, y, (std::to_string(updater.getProcessedSize()) + " / " + std::to_string(updater.getTotalSize())).c_str()); } else if (state == NO_UPDATE) { renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_NO_UPDATE), true, EpdFontFamily::BOLD); } else if (state == FAILED) { renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_UPDATE_FAILED), true, EpdFontFamily::BOLD); } else if (state == FINISHED) { renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_UPDATE_COMPLETE), true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, top + height + metrics.verticalSpacing, tr(STR_POWER_ON_HINT)); } renderer.displayBuffer(); } void OtaUpdateActivity::loop() { // TODO @ngxson : refactor this logic later if (updater.getRender()) { requestUpdate(); } if (subActivity) { subActivity->loop(); return; } if (state == WAITING_CONFIRMATION) { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { LOG_DBG("OTA", "New update available, starting download..."); { RenderLock lock(*this); state = UPDATE_IN_PROGRESS; } requestUpdate(); requestUpdateAndWait(); const auto res = updater.installUpdate(); if (res != OtaUpdater::OK) { LOG_DBG("OTA", "Update failed: %d", res); { RenderLock lock(*this); state = FAILED; } requestUpdate(); return; } { RenderLock lock(*this); state = FINISHED; } requestUpdate(); } if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { goBack(); } return; } if (state == FAILED) { if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { goBack(); } return; } if (state == NO_UPDATE) { if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { goBack(); } return; } if (state == SHUTTING_DOWN) { ESP.restart(); } }