Skip to content

Commit 0e98962

Browse files
committed
Add error messages for OTA failure
1 parent ad4bd6c commit 0e98962

File tree

4 files changed

+134
-28
lines changed

4 files changed

+134
-28
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.11
1+
1.0.12

lib/Backend/firmware_update.dart

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,22 @@ enum OtaState {
139139
rebooting,
140140
}
141141

142+
enum OtaError {
143+
md5Mismatch,
144+
downloadFailed,
145+
gearVersionMismatch,
146+
gearReturnedError,
147+
uploadFailed,
148+
gearReconnectTimeout,
149+
gearDisconnectTimeout,
150+
gearOtaFinalTimeout,
151+
}
152+
142153
class OtaUpdater {
143154
Function(double)? onProgress;
144155
Function(OtaState)? onStateChanged;
156+
Function(OtaError)? onError;
157+
145158
BaseStatefulDevice baseStatefulDevice;
146159
OtaState _otaState = OtaState.standby;
147160

@@ -172,8 +185,10 @@ class OtaUpdater {
172185
List<int>? firmwareFile;
173186
String? downloadedMD5;
174187
bool _wakelockEnabledBeforehand = false;
175-
int current = 0;
176-
Timer? _timer;
188+
int currentFirmwareUploadPosition = 0;
189+
Timer? _disconnectTimer;
190+
Timer? _reconnectTimer;
191+
Timer? _finalTimer;
177192
final Logger _otaLogger = Logger('otaLogger');
178193

179194
void setManualOtaFile(List<int>? bytes) {
@@ -187,6 +202,14 @@ class OtaUpdater {
187202
downloadProgress = 1;
188203
}
189204

205+
void _onError(OtaError error) {
206+
otaState = OtaState.error;
207+
if (onError != null) {
208+
onError!(error);
209+
}
210+
_cancelTimers();
211+
}
212+
190213
double _previousProgress = 0;
191214

192215
void _updateProgress() {
@@ -214,14 +237,14 @@ class OtaUpdater {
214237
}
215238
WakelockPlus.enable();
216239
if (firmwareFile == null) {
217-
await downloadFirmware();
240+
await _downloadFirmware();
218241
}
219242
if (otaState != OtaState.error) {
220-
await uploadFirmware();
243+
await _uploadFirmware();
221244
}
222245
}
223246

224-
Future<void> downloadFirmware() async {
247+
Future<void> _downloadFirmware() async {
225248
if (firmwareInfo == null) {
226249
return;
227250
}
@@ -244,33 +267,55 @@ class OtaUpdater {
244267
if (digest.toString() == firmwareInfo!.md5sum) {
245268
firmwareFile = rs.data;
246269
} else {
247-
otaState = OtaState.error;
270+
_onError(OtaError.md5Mismatch);
248271
}
249272
}
250273
} catch (e) {
251-
otaState = OtaState.error;
274+
_onError(OtaError.downloadFailed);
252275
}
253276
}
254277

255-
Future<void> verListener() async {
278+
Future<void> _verListener() async {
256279
Version version = baseStatefulDevice.fwVersion.value;
257280
FWInfo? fwInfo = firmwareInfo;
258281
if (fwInfo != null && version.compareTo(const Version()) > 0 && otaState == OtaState.rebooting) {
259282
bool updated = version.compareTo(getVersionSemVer(fwInfo.version)) >= 0;
260-
otaState = updated ? OtaState.completed : OtaState.error;
283+
if (!updated) {
284+
_onError(OtaError.gearVersionMismatch);
285+
} else {
286+
otaState = OtaState.completed;
287+
}
261288
}
262289
}
263290

264-
void fwInfoListener() {
291+
void _fwInfoListener() {
265292
firmwareInfo = baseStatefulDevice.fwInfo.value;
266293
}
267294

268-
Future<void> uploadFirmware() async {
295+
void _connectivityStateListener() {
296+
ConnectivityState connectivityState = baseStatefulDevice.deviceConnectionState.value;
297+
if (OtaState.rebooting == otaState) {
298+
if (connectivityState == ConnectivityState.disconnected) {
299+
_disconnectTimer?.cancel();
300+
_reconnectTimer = Timer(
301+
const Duration(seconds: 30),
302+
() {
303+
_otaLogger.warning("Gear did not reconnect");
304+
_onError(OtaError.gearReconnectTimeout);
305+
},
306+
);
307+
} else if (connectivityState == ConnectivityState.connected) {
308+
_reconnectTimer?.cancel();
309+
}
310+
}
311+
}
312+
313+
Future<void> _uploadFirmware() async {
269314
otaState = OtaState.upload;
270315
uploadProgress = 0;
271316
if (firmwareFile != null) {
272317
int mtu = baseStatefulDevice.mtu.value - 10;
273-
current = 0;
318+
currentFirmwareUploadPosition = 0;
274319
baseStatefulDevice.gearReturnedError.value = false;
275320

276321
_otaLogger.info("Holding the command queue");
@@ -281,42 +326,53 @@ class OtaUpdater {
281326
while (uploadProgress < 1 && otaState != OtaState.error) {
282327
baseStatefulDevice.deviceState.value = DeviceState.busy; // hold the command queue
283328
if (baseStatefulDevice.gearReturnedError.value) {
284-
otaState = OtaState.error;
329+
_onError(OtaError.gearReturnedError);
285330
break;
286331
}
287332

288-
List<int> chunk = firmwareFile!.skip(current).take(mtu).toList();
333+
List<int> chunk = firmwareFile!.skip(currentFirmwareUploadPosition).take(mtu).toList();
289334
if (chunk.isNotEmpty) {
290335
try {
291336
await sendMessage(baseStatefulDevice, chunk, withoutResponse: true);
292337
} catch (e, s) {
293338
_otaLogger.severe("Exception during ota upload:$e", e, s);
294-
if ((current + chunk.length) / firmwareFile!.length < 0.99) {
295-
otaState = OtaState.error;
339+
if ((currentFirmwareUploadPosition + chunk.length) / firmwareFile!.length < 0.99) {
340+
_onError(OtaError.uploadFailed);
341+
296342
return;
297343
}
298344
}
299-
current = current + chunk.length;
345+
currentFirmwareUploadPosition = currentFirmwareUploadPosition + chunk.length;
300346
} else {
301-
current = firmwareFile!.length;
347+
currentFirmwareUploadPosition = firmwareFile!.length;
302348
}
303349

304-
uploadProgress = current / firmwareFile!.length;
350+
uploadProgress = currentFirmwareUploadPosition / firmwareFile!.length;
305351
_updateProgress();
306352
}
307353
if (uploadProgress == 1) {
308354
_otaLogger.info("File Uploaded");
309355
otaState = OtaState.rebooting;
356+
//TODO: check if gear disconnects
310357
beginScan(
311358
scanReason: ScanReason.manual,
312359
timeout: const Duration(seconds: 60),
313-
); // start scanning for the gear to reconnect
314-
_timer = Timer(
360+
);
361+
362+
_disconnectTimer = Timer(
363+
const Duration(seconds: 30),
364+
() {
365+
_otaLogger.warning("Gear did not disconnect");
366+
_onError(OtaError.gearDisconnectTimeout);
367+
},
368+
);
369+
// start scanning for the gear to reconnect
370+
_finalTimer = Timer(
315371
const Duration(seconds: 60),
316372
() {
317373
if (otaState != OtaState.completed) {
318374
_otaLogger.warning("Gear did not return correct version after reboot");
319-
otaState = OtaState.error;
375+
_onError(OtaError.gearOtaFinalTimeout);
320376
}
321377
},
322378
);
@@ -326,22 +382,29 @@ class OtaUpdater {
326382
}
327383
}
328384

329-
OtaUpdater({this.onProgress, this.onStateChanged, required this.baseStatefulDevice}) {
385+
OtaUpdater({this.onProgress, this.onStateChanged, required this.baseStatefulDevice, this.onError}) {
330386
firmwareInfo ??= baseStatefulDevice.fwInfo.value;
331387
WakelockPlus.enabled.then((value) => _wakelockEnabledBeforehand = value);
332-
baseStatefulDevice.fwVersion.addListener(verListener);
333-
baseStatefulDevice.fwInfo.addListener(fwInfoListener);
388+
baseStatefulDevice.fwVersion.addListener(_verListener);
389+
baseStatefulDevice.fwInfo.addListener(_fwInfoListener);
390+
baseStatefulDevice.deviceConnectionState.addListener(_connectivityStateListener);
334391
}
335392

336393
void dispose() {
337-
_timer?.cancel();
394+
_cancelTimers();
338395
if (!_wakelockEnabledBeforehand) {
339396
unawaited(WakelockPlus.disable());
340397
}
341398
if (!HiveProxy.getOrDefault(settings, alwaysScanning, defaultValue: alwaysScanningDefault)) {
342399
unawaited(stopScan());
343400
}
344401
}
402+
403+
void _cancelTimers() {
404+
_disconnectTimer?.cancel();
405+
_reconnectTimer?.cancel();
406+
_finalTimer?.cancel();
407+
}
345408
}
346409

347410
@pragma('vm:entry-point')

lib/Frontend/pages/ota_update.dart

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class OtaUpdate extends ConsumerStatefulWidget {
2727
class _OtaUpdateState extends ConsumerState<OtaUpdate> {
2828
late OtaUpdater otaUpdater;
2929
BaseStatefulDevice? baseStatefulDevice;
30+
OtaError? otaError;
3031

3132
@override
3233
void initState() {
@@ -36,6 +37,7 @@ class _OtaUpdateState extends ConsumerState<OtaUpdate> {
3637
baseStatefulDevice: baseStatefulDevice!,
3738
onProgress: (p0) => setState(() {}),
3839
onStateChanged: (p0) => setState(() {}),
40+
onError: (p0) => setState(() => otaError = p0),
3941
);
4042
unawaited(ref.read(hasOtaUpdateProvider(baseStatefulDevice!).future));
4143
}
@@ -46,6 +48,27 @@ class _OtaUpdateState extends ConsumerState<OtaUpdate> {
4648
otaUpdater.dispose();
4749
}
4850

51+
String getErrorMessage(OtaError otaError) {
52+
switch (otaError) {
53+
case OtaError.md5Mismatch:
54+
return otaFailedReasonMD5Mismatch();
55+
case OtaError.downloadFailed:
56+
return otaFailedReasonDownloadFailed();
57+
case OtaError.gearVersionMismatch:
58+
return otaFailedReasonGearVersionMismatch();
59+
case OtaError.gearReturnedError:
60+
return otaFailedReasonGearReturnedError();
61+
case OtaError.uploadFailed:
62+
return otaFailedReasonUploadFailed();
63+
case OtaError.gearReconnectTimeout:
64+
return otaFailedReasonGearReconnectTimeout();
65+
case OtaError.gearDisconnectTimeout:
66+
return otaFailedReasonGearDisconnectTimeout();
67+
case OtaError.gearOtaFinalTimeout:
68+
return otaFailedReasonGearOtaFinalTimeout();
69+
}
70+
}
71+
4972
@override
5073
Widget build(BuildContext context) {
5174
return Scaffold(
@@ -179,6 +202,10 @@ class _OtaUpdateState extends ConsumerState<OtaUpdate> {
179202
style: Theme.of(context).textTheme.titleLarge,
180203
textAlign: TextAlign.center,
181204
),
205+
subtitle: Text(
206+
otaError != null ? getErrorMessage(otaError!) : "",
207+
textAlign: TextAlign.center,
208+
),
182209
),
183210
),
184211
),
@@ -258,7 +285,7 @@ class _OtaUpdateState extends ConsumerState<OtaUpdate> {
258285
subtitle: Column(
259286
crossAxisAlignment: CrossAxisAlignment.start,
260287
children: [
261-
Text('Upload Progress: ${otaUpdater.current} / ${otaUpdater.firmwareFile?.length} = ${otaUpdater.uploadProgress.toStringAsPrecision(3)}'),
288+
Text('Upload Progress: ${otaUpdater.currentFirmwareUploadPosition} / ${otaUpdater.firmwareFile?.length} = ${otaUpdater.uploadProgress.toStringAsPrecision(3)}'),
262289
Text('MTU: ${baseStatefulDevice!.mtu.value}'),
263290
Text('OtaState: ${otaUpdater.otaState.name}'),
264291
Text('DeviceState: ${baseStatefulDevice!.deviceState.value}'),

lib/Frontend/translation_string_definitions.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,22 @@ String otaCompletedTitle() => Intl.message("Update Completed", name: 'otaComplet
276276

277277
String otaFailedTitle() => Intl.message("Update Failed. Please restart your gear and try again", name: 'otaFailedTitle', desc: 'Title for the text that appears when an OTA update has failed');
278278

279+
String otaFailedReasonMD5Mismatch() => Intl.message("The downloaded file failed verification", name: 'otaFailedReasonMD5Mismatch', desc: 'md5Mismatch error message for uploading firmware');
280+
281+
String otaFailedReasonDownloadFailed() => Intl.message("Failed to download the firmware file", name: 'otaFailedReasonDownloadFailed', desc: 'downloadFailed error message for uploading firmware');
282+
283+
String otaFailedReasonGearVersionMismatch() => Intl.message("The firmware version on your gear does not match the uploaded firmware version", name: 'otaFailedReasonGearVersionMismatch', desc: 'gearVersionMismatch error message for uploading firmware');
284+
285+
String otaFailedReasonGearReturnedError() => Intl.message("The gear returned an unknown error", name: 'otaFailedReasonGearReturnedError', desc: 'gearReturnedError error message for uploading firmware');
286+
287+
String otaFailedReasonUploadFailed() => Intl.message("Failed to upload firmware to gear", name: 'otaFailedReasonUploadFailed', desc: 'uploadFailed error message for uploading firmware');
288+
289+
String otaFailedReasonGearReconnectTimeout() => Intl.message("The gear did not reconnect in time", name: 'otaFailedReasonGearReconnectTimeout', desc: 'gearReconnectTimeout error message for uploading firmware');
290+
291+
String otaFailedReasonGearDisconnectTimeout() => Intl.message("The gear did not disconnect in time and likely didn't reboot", name: 'otaFailedReasonGearDisconnectTimeout', desc: 'gearDisconnectTimeout error message for uploading firmware');
292+
293+
String otaFailedReasonGearOtaFinalTimeout() => Intl.message("Timed out waiting for gear to return its new firmware version", name: 'otaFailedReasonGearOtaFinalTimeout', desc: 'gearOtaFinalTimeout error message for uploading firmware');
294+
279295
String otaLowBattery() => Intl.message("Low Battery. Please charge your gear to at least 50%", name: 'otaLowBattery', desc: 'Title for the text that appears when an OTA update was blocked due to low battery');
280296

281297
String triggerInfoDescription() => Intl.message(

0 commit comments

Comments
 (0)