forked from crosspoint-reader/crosspoint-reader
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathOtaUpdateActivity.cpp
More file actions
230 lines (201 loc) · 7.63 KB
/
Copy pathOtaUpdateActivity.cpp
File metadata and controls
230 lines (201 loc) · 7.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#include "OtaUpdateActivity.h"
#include <GfxRenderer.h>
#include <I18n.h>
#include <WiFi.h>
#include "MappedInputManager.h"
#include "SilentRestart.h"
#include "activities/network/WifiSelectionActivity.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "network/OtaUpdater.h"
void OtaUpdateActivity::onWifiSelectionComplete(const bool success) {
if (!success) {
LOG_ERR("OTA", "WiFi connection failed, exiting");
finish();
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;
}
return;
}
if (!updater.isUpdateNewer()) {
LOG_DBG("OTA", "No new update available");
{
RenderLock lock(*this);
state = NO_UPDATE;
}
return;
}
{
RenderLock lock(*this);
state = WAITING_CONFIRMATION;
}
const char* options[] = {tr(STR_CANCEL), tr(STR_UPDATE)};
// Default the selection to Update so the hardware Confirm button installs,
// matching the pre-popup layout (Back = cancel, Confirm = update).
confirmPopup.show(tr(STR_NEW_UPDATE), options, 2, 1, [this](const int idx) {
if (idx == 1) {
runUpdateInstall();
} else {
finish();
}
});
requestUpdate();
}
void OtaUpdateActivity::onEnter() {
Activity::onEnter();
// Turn on WiFi immediately
LOG_DBG("OTA", "Turning on WiFi...");
WiFi.mode(WIFI_STA);
// Launch WiFi selection subactivity
LOG_DBG("OTA", "Launching WifiSelectionActivity...");
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput),
[this](const ActivityResult& result) { onWifiSelectionComplete(!result.isCancelled); });
}
void OtaUpdateActivity::onExit() {
Activity::onExit();
// Success path reboots via the SHUTTING_DOWN state's plain ESP.restart()
// (loop() above) so the new firmware boots normally. Back-out paths land
// here with wifi still active; silent-restart to free the LWIP/mbedTLS
// fragmentation, same as the other wifi activities.
if (WiFi.getMode() != WIFI_MODE_NULL) {
WiFi.disconnect(false);
delay(30);
silentRestart();
}
}
void OtaUpdateActivity::render(RenderLock&&) {
const 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) {
// Version info sits in the upper part of the screen so the centered
// Cancel/Update popup doesn't cover it (same layout as ConfirmationActivity).
const int infoTop = pageHeight / 6;
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, infoTop,
(std::string(tr(STR_CURRENT_VERSION)) + CROSSPOINT_VERSION).c_str());
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, infoTop + height + metrics.verticalSpacing,
(std::string(tr(STR_NEW_VERSION)) + updater.getLatestVersion()).c_str());
if (confirmPopup.processRender(renderer, mappedInput)) return;
} 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;
// Percent label is drawn by BaseTheme::drawProgressBar; this slot is left intentionally empty
// so the bytes line below stays at the same Y it was at when the activity drew its own percent.
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);
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
} else if (state == FAILED) {
renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_UPDATE_FAILED), true, EpdFontFamily::BOLD);
if (failedDetail != nullptr) {
renderer.drawCenteredText(UI_10_FONT_ID, top + height + metrics.verticalSpacing, failedDetail);
}
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
} 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::runUpdateInstall() {
LOG_DBG("OTA", "New update available, starting download...");
{
RenderLock lock(*this);
state = UPDATE_IN_PROGRESS;
}
requestUpdateAndWait();
const auto res = updater.installUpdate(
[](void* ctx) {
// immediate=true notifies the render task directly. The default deferred path only
// sets a flag consumed at the end of ActivityManager::loop(), which never runs while
// installUpdate() blocks this task.
static_cast<OtaUpdateActivity*>(ctx)->requestUpdate(true);
},
this);
if (res != OtaUpdater::OK) {
LOG_DBG("OTA", "Update failed: %d", res);
{
RenderLock lock(*this);
failedDetail = res == OtaUpdater::WRONG_DEVICE_ERROR ? tr(STR_FIRMWARE_WRONG_DEVICE) : nullptr;
state = FAILED;
}
requestUpdate();
return;
}
{
RenderLock lock(*this);
state = FINISHED;
}
requestUpdateAndWait();
// Hold the completion screen briefly so the user sees it, then restart.
delay(3000);
{
RenderLock lock(*this);
state = SHUTTING_DOWN;
}
}
void OtaUpdateActivity::loop() {
if (state == WAITING_CONFIRMATION) {
if (confirmPopup.handleInput(mappedInput, [this] { requestUpdate(); })) return;
// Popup dismissed without a selection (Back button or tap outside): cancel.
finish();
return;
}
if (state == FAILED) {
int x = 0;
int y = 0;
if (mappedInput.wasPressed(MappedInputManager::Button::Back) || mappedInput.wasScreenTapped(x, y)) {
finish();
}
return;
}
if (state == NO_UPDATE) {
int x = 0;
int y = 0;
if (mappedInput.wasPressed(MappedInputManager::Button::Back) || mappedInput.wasScreenTapped(x, y)) {
finish();
}
return;
}
if (state == SHUTTING_DOWN) {
ESP.restart();
}
}