mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-06-01 22:19:18 +02:00
fix(firmware): surface error state when BLE OTA connection attempts are exhausted (#5700)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+26
-8
@@ -237,10 +237,20 @@ class FirmwareUpdateViewModel(
|
||||
updateState = { _state.value = it },
|
||||
)
|
||||
|
||||
if (_state.value is FirmwareUpdateState.Success) {
|
||||
verifyUpdateResult(originalDeviceAddress)
|
||||
} else if (_state.value is FirmwareUpdateState.Error) {
|
||||
tempFirmwareFile = cleanupTemporaryFiles(fileHandler, tempFirmwareFile)
|
||||
when (_state.value) {
|
||||
is FirmwareUpdateState.Success -> verifyUpdateResult(originalDeviceAddress)
|
||||
|
||||
is FirmwareUpdateState.Error -> {
|
||||
tempFirmwareFile = cleanupTemporaryFiles(fileHandler, tempFirmwareFile)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Defense-in-depth: handler returned without setting a terminal state
|
||||
Logger.w { "Firmware update returned without terminal state: ${_state.value}" }
|
||||
_state.value =
|
||||
FirmwareUpdateState.Error(UiText.Resource(Res.string.firmware_update_failed))
|
||||
tempFirmwareFile = cleanupTemporaryFiles(fileHandler, tempFirmwareFile)
|
||||
}
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
Logger.w(e) { "Firmware update cancelled — cause: ${e.cause} message: ${e.message}" }
|
||||
@@ -315,10 +325,18 @@ class FirmwareUpdateViewModel(
|
||||
)
|
||||
tempFirmwareFile = updateArtifact ?: extractedFile
|
||||
|
||||
if (_state.value is FirmwareUpdateState.Success) {
|
||||
verifyUpdateResult(originalDeviceAddress)
|
||||
} else if (_state.value is FirmwareUpdateState.Error) {
|
||||
tempFirmwareFile = cleanupTemporaryFiles(fileHandler, tempFirmwareFile)
|
||||
when (_state.value) {
|
||||
is FirmwareUpdateState.Success -> verifyUpdateResult(originalDeviceAddress)
|
||||
|
||||
is FirmwareUpdateState.Error -> {
|
||||
tempFirmwareFile = cleanupTemporaryFiles(fileHandler, tempFirmwareFile)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Logger.w { "Firmware update returned without terminal state: ${_state.value}" }
|
||||
_state.value = FirmwareUpdateState.Error(UiText.Resource(Res.string.firmware_update_failed))
|
||||
tempFirmwareFile = cleanupTemporaryFiles(fileHandler, tempFirmwareFile)
|
||||
}
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
|
||||
+3
-4
@@ -156,7 +156,7 @@ class Esp32OtaUpdateHandler(
|
||||
delay(GATT_RELEASE_DELAY_MS)
|
||||
|
||||
val transport = transportFactory()
|
||||
if (!connectToDevice(transport, connectionAttempts, updateState)) return@withContext null
|
||||
connectToDevice(transport, connectionAttempts, updateState)
|
||||
|
||||
try {
|
||||
executeOtaSequence(transport, firmwareBytes, sha256Hash, rebootMode, updateState)
|
||||
@@ -255,7 +255,7 @@ class Esp32OtaUpdateHandler(
|
||||
transport: UnifiedOtaProtocol,
|
||||
attempts: Int,
|
||||
updateState: (FirmwareUpdateState) -> Unit,
|
||||
): Boolean {
|
||||
) {
|
||||
// Show "waiting for reboot" state before first connection attempt
|
||||
updateState(
|
||||
FirmwareUpdateState.Processing(ProgressState(UiText.Resource(Res.string.firmware_update_waiting_reboot))),
|
||||
@@ -269,13 +269,12 @@ class Esp32OtaUpdateHandler(
|
||||
),
|
||||
)
|
||||
transport.connect().getOrThrow()
|
||||
return true
|
||||
return
|
||||
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
|
||||
if (i == attempts) throw e
|
||||
delay(RETRY_DELAY)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
|
||||
+10
-8
@@ -141,7 +141,7 @@ class SecureDfuHandler(
|
||||
var completed = false
|
||||
try {
|
||||
// ── 5. Connect to device in DFU mode ─────────────────────────────
|
||||
if (!connectWithRetry(transport, updateState)) return@withContext null
|
||||
connectWithRetry(transport, updateState)
|
||||
|
||||
// ── 6. Init packet ────────────────────────────────────────────
|
||||
updateState(
|
||||
@@ -252,13 +252,11 @@ class SecureDfuHandler(
|
||||
return if (legacyHit != null) DfuProtocolKind.LEGACY else DfuProtocolKind.SECURE
|
||||
}
|
||||
|
||||
private suspend fun connectWithRetry(
|
||||
transport: DfuUploadTransport,
|
||||
updateState: (FirmwareUpdateState) -> Unit,
|
||||
): Boolean {
|
||||
private suspend fun connectWithRetry(transport: DfuUploadTransport, updateState: (FirmwareUpdateState) -> Unit) {
|
||||
updateState(
|
||||
FirmwareUpdateState.Processing(ProgressState(UiText.Resource(Res.string.firmware_update_waiting_reboot))),
|
||||
)
|
||||
var lastError: Throwable? = null
|
||||
for (attempt in 1..CONNECT_ATTEMPTS) {
|
||||
updateState(
|
||||
FirmwareUpdateState.Processing(
|
||||
@@ -269,12 +267,16 @@ class SecureDfuHandler(
|
||||
)
|
||||
val result = transport.connectToDfuMode()
|
||||
if (result.isSuccess) {
|
||||
return true
|
||||
return
|
||||
}
|
||||
Logger.w { "DFU: Connect attempt $attempt/$CONNECT_ATTEMPTS failed: ${result.exceptionOrNull()?.message}" }
|
||||
lastError = result.exceptionOrNull()
|
||||
Logger.w { "DFU: Connect attempt $attempt/$CONNECT_ATTEMPTS failed: ${lastError?.message}" }
|
||||
if (attempt < CONNECT_ATTEMPTS) delay(RETRY_DELAY_MS)
|
||||
}
|
||||
return false
|
||||
throw DfuException.ConnectionFailed(
|
||||
"Failed to connect to DFU device after $CONNECT_ATTEMPTS attempts",
|
||||
lastError,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun obtainZipFile(
|
||||
|
||||
Reference in New Issue
Block a user