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:
@@ -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))"
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user