#include "OtaUpdateActivity.h" #include #include #include "MappedInputManager.h" #include "activities/network/WifiSelectionActivity.h" #include "components/UITheme.h" #include "fontIds.h" #include "network/OtaUpdater.h" void OtaUpdateActivity::taskTrampoline(void* param) { auto* self = static_cast(param); self->displayTaskLoop(); } void OtaUpdateActivity::onWifiSelectionComplete(const bool success) { exitActivity(); if (!success) { Serial.printf("[%lu] [OTA] WiFi connection failed, exiting\n", millis()); goBack(); return; } Serial.printf("[%lu] [OTA] WiFi connected, checking for update\n", millis()); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = CHECKING_FOR_UPDATE; xSemaphoreGive(renderingMutex); updateRequired = true; vTaskDelay(10 / portTICK_PERIOD_MS); const auto res = updater.checkForUpdate(); if (res != OtaUpdater::OK) { Serial.printf("[%lu] [OTA] Update check failed: %d\n", millis(), res); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = FAILED; xSemaphoreGive(renderingMutex); updateRequired = true; return; } if (!updater.isUpdateNewer()) { Serial.printf("[%lu] [OTA] No new update available\n", millis()); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = NO_UPDATE; xSemaphoreGive(renderingMutex); updateRequired = true; return; } xSemaphoreTake(renderingMutex, portMAX_DELAY); state = WAITING_CONFIRMATION; xSemaphoreGive(renderingMutex); updateRequired = true; } void OtaUpdateActivity::onEnter() { ActivityWithSubactivity::onEnter(); renderingMutex = xSemaphoreCreateMutex(); xTaskCreate(&OtaUpdateActivity::taskTrampoline, "OtaUpdateActivityTask", 2048, // Stack size this, // Parameters 1, // Priority &displayTaskHandle // Task handle ); // Turn on WiFi immediately Serial.printf("[%lu] [OTA] Turning on WiFi...\n", millis()); WiFi.mode(WIFI_STA); // Launch WiFi selection subactivity Serial.printf("[%lu] [OTA] Launching WifiSelectionActivity...\n", millis()); 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 // Wait until not rendering to delete task to avoid killing mid-instruction to EPD xSemaphoreTake(renderingMutex, portMAX_DELAY); if (displayTaskHandle) { vTaskDelete(displayTaskHandle); displayTaskHandle = nullptr; } vSemaphoreDelete(renderingMutex); renderingMutex = nullptr; } void OtaUpdateActivity::displayTaskLoop() { while (true) { if (updateRequired || updater.getRender()) { updateRequired = false; xSemaphoreTake(renderingMutex, portMAX_DELAY); render(); xSemaphoreGive(renderingMutex); } vTaskDelay(10 / portTICK_PERIOD_MS); } } void OtaUpdateActivity::render() { if (subActivity) { // Subactivity handles its own rendering return; } float updaterProgress = 0; if (state == UPDATE_IN_PROGRESS) { Serial.printf("[%lu] [OTA] Update progress: %d / %d\n", millis(), 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); } const auto pageWidth = renderer.getScreenWidth(); renderer.clearScreen(); renderer.drawCenteredText(UI_12_FONT_ID, 15, "Update", true, EpdFontFamily::BOLD); if (state == CHECKING_FOR_UPDATE) { renderer.drawCenteredText(UI_10_FONT_ID, 300, "Checking for update...", true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } if (state == WAITING_CONFIRMATION) { renderer.drawCenteredText(UI_10_FONT_ID, 200, "New update available!", true, EpdFontFamily::BOLD); renderer.drawText(UI_10_FONT_ID, 20, 250, "Current Version: " CROSSPOINT_VERSION); renderer.drawText(UI_10_FONT_ID, 20, 270, ("New Version: " + updater.getLatestVersion()).c_str()); const auto labels = mappedInput.mapLabels("Cancel", "Update", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); return; } if (state == UPDATE_IN_PROGRESS) { renderer.drawCenteredText(UI_10_FONT_ID, 310, "Updating...", true, EpdFontFamily::BOLD); renderer.drawRect(20, 350, pageWidth - 40, 50); renderer.fillRect(24, 354, static_cast(updaterProgress * static_cast(pageWidth - 44)), 42); renderer.drawCenteredText(UI_10_FONT_ID, 420, (std::to_string(static_cast(updaterProgress * 100)) + "%").c_str()); renderer.drawCenteredText( UI_10_FONT_ID, 440, (std::to_string(updater.getProcessedSize()) + " / " + std::to_string(updater.getTotalSize())).c_str()); renderer.displayBuffer(); return; } if (state == NO_UPDATE) { renderer.drawCenteredText(UI_10_FONT_ID, 300, "No update available", true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } if (state == FAILED) { renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update failed", true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; } if (state == FINISHED) { renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update complete", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, 350, "Press and hold power button to turn back on"); renderer.displayBuffer(); state = SHUTTING_DOWN; return; } } void OtaUpdateActivity::loop() { if (subActivity) { subActivity->loop(); return; } if (state == WAITING_CONFIRMATION) { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { Serial.printf("[%lu] [OTA] New update available, starting download...\n", millis()); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = UPDATE_IN_PROGRESS; xSemaphoreGive(renderingMutex); updateRequired = true; vTaskDelay(10 / portTICK_PERIOD_MS); const auto res = updater.installUpdate(); if (res != OtaUpdater::OK) { Serial.printf("[%lu] [OTA] Update failed: %d\n", millis(), res); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = FAILED; xSemaphoreGive(renderingMutex); updateRequired = true; return; } xSemaphoreTake(renderingMutex, portMAX_DELAY); state = FINISHED; xSemaphoreGive(renderingMutex); updateRequired = true; } 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(); } }