Implements Lyra theme for some more Crosspoint screens:       - A bit of refactoring for list scrolling logic --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --------- Co-authored-by: Dave Allie <dave@daveallie.com>
211 lines
6.3 KiB
C++
211 lines
6.3 KiB
C++
#include "OtaUpdateActivity.h"
|
|
|
|
#include <GfxRenderer.h>
|
|
#include <I18n.h>
|
|
#include <WiFi.h>
|
|
|
|
#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<float>(updater.getProcessedSize()) / static_cast<float>(updater.getTotalSize());
|
|
// Only update every 2% at the most
|
|
if (static_cast<int>(updaterProgress * 50) == lastUpdaterPercentage / 2) {
|
|
return;
|
|
}
|
|
lastUpdaterPercentage = static_cast<int>(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<int>(updaterProgress * 100), 100);
|
|
|
|
y += metrics.progressBarHeight + metrics.verticalSpacing;
|
|
renderer.drawCenteredText(UI_10_FONT_ID, y,
|
|
(std::to_string(static_cast<int>(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();
|
|
}
|
|
}
|