mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 22:57:50 +03:00
243 lines
7.4 KiB
C++
243 lines
7.4 KiB
C++
#include "OtaUpdateActivity.h"
|
|
|
|
#include <GfxRenderer.h>
|
|
#include <InputManager.h>
|
|
#include <WiFi.h>
|
|
|
|
#include "activities/network/WifiSelectionActivity.h"
|
|
#include "config.h"
|
|
#include "network/OtaUpdater.h"
|
|
|
|
void OtaUpdateActivity::taskTrampoline(void* param) {
|
|
auto* self = static_cast<OtaUpdateActivity*>(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<float>(updater.processedSize) / static_cast<float>(updater.totalSize);
|
|
// Only update every 2% at the most
|
|
if (static_cast<int>(updaterProgress * 50) == lastUpdaterPercentage / 2) {
|
|
return;
|
|
}
|
|
lastUpdaterPercentage = static_cast<int>(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<int>(updaterProgress * static_cast<float>(pageWidth - 44)), 42);
|
|
renderer.drawCenteredText(UI_FONT_ID, 420, (std::to_string(static_cast<int>(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();
|
|
}
|
|
}
|