Additional OTA robustness improvements

This commit is contained in:
Jake-B 2026-01-05 20:39:37 -05:00
parent cd7e16f7d4
commit fc45e41e38
3 changed files with 24 additions and 9 deletions

View file

@ -108,6 +108,7 @@ struct ESP32BLEOTASheet: View {
} label: {
Label("Retry", systemImage: "arrow.clockwise")
.frame(maxWidth: .infinity)
.foregroundStyle(.white)
}
.buttonStyle(.borderedProminent)
.tint(.red)

View file

@ -62,14 +62,7 @@ struct ESP32WifiOTASheet: View {
beginWifiProcessButton()
case .error:
Text("Error: \(ota.errorMessage, default: "Unknown")")
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
.font(.headline)
Button("Retry") {
ota.retry()
}
retryButton()
default:
Text("\(ota.statusMessage, default: "")")
@ -115,6 +108,7 @@ struct ESP32WifiOTASheet: View {
} label: {
Label("Retry", systemImage: "arrow.clockwise")
.frame(maxWidth: .infinity)
.foregroundStyle(.white)
}
.buttonStyle(.borderedProminent)
.tint(.red)

View file

@ -63,6 +63,8 @@ class ESP32WifiOTAViewModel: ObservableObject {
try await connectAndUpload(endpoint: targetEndpoint, data: firmwareData)
// 3. Success
// Explicitly force progress to 1.0 to ensure Checkmark appears
progress = 1.0
statusMessage = "Success!"
otaState = .completed
Logger.services.info("[ESP OTA] Update Complete")
@ -101,7 +103,7 @@ class ESP32WifiOTAViewModel: ObservableObject {
let response: String
do {
// Timeout logic relies on the Reader unblocking immediately on cancel
response = try await withTimeout(seconds: 60.0) {
response = try await withTimeout(seconds: 30.0) {
try await reader.readLine()
}
} catch {
@ -149,12 +151,18 @@ class ESP32WifiOTAViewModel: ObservableObject {
offset += chunk.count
// Update UI periodically to avoid flooding main thread
if offset % (self.chunkSize * 10) == 0 {
let percent = Double(offset) / Double(totalSize)
await self.updateUI { self.progress = percent }
}
}
// MARK: - FIX: Force 100% on upload completion
// Because of the modulo operator above, the loop often ends at 99.xxx%.
// We force it to 1.0 here so the user sees "100%" while waiting for verification.
await self.updateUI { self.progress = 1.0 }
isUploading.withLock { $0 = false }
Logger.services.info("[ESP OTA] Writer Task: All data sent.")
}
@ -350,6 +358,18 @@ actor AsyncLineReader {
return try await withCheckedThrowingContinuation { continuation in
lock.withLock { $0 = continuation }
// MARK: - FIX for Deadlock
// If `onCancel` ran before we set the lock (race condition), `Task.isCancelled`
// will be true here. We must abort immediately.
if Task.isCancelled {
lock.withLock { state in
guard let cont = state else { return }
state = nil
cont.resume(throwing: CancellationError())
}
return
}
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, _, _, error in
lock.withLock { state in
guard let cont = state else { return } // Already cancelled/resumed