feat(sdk): upgradeFirmware 下发前校验电量 ≥40%,不足抛 .batteryTooLow

OTA 烧录前主动拉新鲜 getDeviceInfo 取实时电量,<40% 直接以
.batteryTooLow(current:required:) 失败、不发任何字节,防烧录中途断电变砖。
新增常量 minBatteryForUpgrade=40;烧录体抽出私有 performFirmwareTransfer。
README 补可用方法/属性汇总表。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
km2023
2026-05-19 17:19:23 +08:00
parent 4bc8fdb64d
commit a15403ae36
3 changed files with 92 additions and 1 deletions

View File

@@ -39,6 +39,7 @@ public enum DuooomiBleError: LocalizedError {
case permissionDenied
case unsupportedFormat(String)
case firmwareUpgradeUnavailable(String)
case batteryTooLow(current: Int, required: Int)
case brandMismatch(expected: String, actual: String)
public var errorDescription: String? {
@@ -77,6 +78,8 @@ public enum DuooomiBleError: LocalizedError {
return "Unsupported file format: \(ext). Supported: jpeg, png, gif, mp4, webm, webp"
case .firmwareUpgradeUnavailable(let reason):
return "Firmware upgrade unavailable: \(reason)"
case .batteryTooLow(let current, let required):
return "设备电量不足,无法升级 (当前 \(current)%,需 ≥ \(required)%)"
case .brandMismatch(let expected, let actual):
return "不支持该品牌设备 (expected=\(expected), actual=\(actual))"
}

View File

@@ -7,6 +7,9 @@ import Foundation
extension DuooomiBleSDK {
/// OTA
static let minBatteryForUpgrade = 40
/// ****
///
/// 1. **** pending `UpgradeRecord`
@@ -61,7 +64,10 @@ extension DuooomiBleSDK {
/// - targetVersion: `sdk.firmwareUpgrade?.targetFirmware?.version`
/// - completion: `Result<Void, Error>`
/// - OTA UpgradeRecord
/// - `.notConnected` / `.transferFailed(...)`
/// - `.notConnected` / `.batteryTooLow(current:required:)` < `minBatteryForUpgrade`
/// / `.transferFailed(...)` / `getDeviceInfo` `.timeout` /
/// - Important: ** `getDeviceInfo`**
/// `minBatteryForUpgrade`40% `.batteryTooLow` OTA
public func upgradeFirmware(
sn: String,
fileUrl: String,
@@ -74,6 +80,43 @@ extension DuooomiBleSDK {
completion(.failure(DuooomiBleError.notConnected))
return
}
// OTA
getDeviceInfo { [weak self] infoResult in
guard let self = self else { return }
switch infoResult {
case .failure(let error):
BleLog.w("OTA blocked: getDeviceInfo failed: \(error.localizedDescription)", "Firmware")
completion(.failure(error))
case .success(let info):
guard info.powerlevel >= Self.minBatteryForUpgrade else {
BleLog.w("OTA blocked: battery \(info.powerlevel)% < \(Self.minBatteryForUpgrade)%", "Firmware")
completion(.failure(DuooomiBleError.batteryTooLow(
current: info.powerlevel,
required: Self.minBatteryForUpgrade
)))
return
}
self.performFirmwareTransfer(
sn: sn,
fileUrl: fileUrl,
firmwareId: firmwareId,
fromVersion: fromVersion,
targetVersion: targetVersion,
completion: completion
)
}
}
}
/// `upgradeFirmware` + `UpgradeRecord`
private func performFirmwareTransfer(
sn: String,
fileUrl: String,
firmwareId: String,
fromVersion: String,
targetVersion: String,
completion: @escaping (Result<Void, Error>) -> Void
) {
BleLog.i("OTA upgrade start: sn=\(sn) \(fromVersion)\(targetVersion) (fw=\(firmwareId))", "Firmware")
transferFile(fileUri: fileUrl, commandType: .otaPackage) { [weak self] result in
if case .success = result {