#include "OtaUpdateActivity.h" #include #include #include #include "activities/network/WifiSelectionActivity.h" #include "config.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, inputManager, [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) { 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.processedSize, updater.totalSize); updaterProgress = static_cast(updater.processedSize) / static_cast(updater.totalSize); // Only update every 2% at the most if (static_cast(updaterProgress * 50) == lastUpdaterPercentage / 2) { return; } lastUpdaterPercentage = static_cast(updaterProgress * 100); } const auto pageHeight = renderer.getScreenHeight(); const auto pageWidth = renderer.getScreenWidth(); renderer.clearScreen(); renderer.drawCenteredText(READER_FONT_ID, 10, "Update", true, BOLD); if (state == CHECKING_FOR_UPDATE) { renderer.drawCenteredText(UI_FONT_ID, 300, "Checking for update...", true, BOLD); renderer.displayBuffer(); return; } if (state == WAITING_CONFIRMATION) { renderer.drawCenteredText(UI_FONT_ID, 200, "New update available!", true, BOLD); renderer.drawText(UI_FONT_ID, 20, 250, "Current Version: " CROSSPOINT_VERSION); renderer.drawText(UI_FONT_ID, 20, 270, ("New Version: " + updater.getLatestVersion()).c_str()); renderer.drawRect(25, pageHeight - 40, 106, 40); renderer.drawText(UI_FONT_ID, 25 + (105 - renderer.getTextWidth(UI_FONT_ID, "Cancel")) / 2, pageHeight - 35, "Cancel"); renderer.drawRect(130, pageHeight - 40, 106, 40); renderer.drawText(UI_FONT_ID, 130 + (105 - renderer.getTextWidth(UI_FONT_ID, "Update")) / 2, pageHeight - 35, "Update"); renderer.displayBuffer(); return; } if (state == UPDATE_IN_PROGRESS) { renderer.drawCenteredText(UI_FONT_ID, 310, "Updating...", true, BOLD); renderer.drawRect(20, 350, pageWidth - 40, 50); renderer.fillRect(24, 354, static_cast(updaterProgress * static_cast(pageWidth - 44)), 42); renderer.drawCenteredText(UI_FONT_ID, 420, (std::to_string(static_cast(updaterProgress * 100)) + "%").c_str()); renderer.drawCenteredText( UI_FONT_ID, 440, (std::to_string(updater.processedSize) + " / " + std::to_string(updater.totalSize)).c_str()); renderer.displayBuffer(); return; } if (state == NO_UPDATE) { renderer.drawCenteredText(UI_FONT_ID, 300, "No update available", true, BOLD); renderer.displayBuffer(); return; } if (state == FAILED) { renderer.drawCenteredText(UI_FONT_ID, 300, "Update failed", true, BOLD); renderer.displayBuffer(); return; } if (state == FINISHED) { renderer.drawCenteredText(UI_FONT_ID, 300, "Update complete", true, BOLD); renderer.drawCenteredText(UI_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 (inputManager.wasPressed(InputManager::BTN_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([this](const size_t, const size_t) { updateRequired = true; }); 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 (inputManager.wasPressed(InputManager::BTN_BACK)) { goBack(); } return; } if (state == FAILED) { if (inputManager.wasPressed(InputManager::BTN_BACK)) { goBack(); } return; } if (state == NO_UPDATE) { if (inputManager.wasPressed(InputManager::BTN_BACK)) { goBack(); } return; } if (state == SHUTTING_DOWN) { ESP.restart(); } }