From 69435512d5b0cdf7c849d9e579ef1110ad11e801 Mon Sep 17 00:00:00 2001 From: km2023 Date: Tue, 19 May 2026 17:19:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(sdk):=20=E5=90=8C=E6=AD=A5=20iOS=20?= =?UTF-8?q?=E2=80=94=20upgradeFirmware=20=E4=B8=8B=E5=8F=91=E5=89=8D?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E7=94=B5=E9=87=8F=20=E2=89=A540%=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E8=B6=B3=E6=8A=9B=20BatteryTooLow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1:1 复刻 iOS:OTA 烧录前主动 getDeviceInfo 取实时电量,<40% 抛 DuooomiBleError.BatteryTooLow(current,required)、不发任何字节。 错误消息字符串与 iOS 逐字一致;新增 MIN_BATTERY_FOR_UPGRADE=40。 协程版线性实现(无 callback 拆分)。README 同步错误处理/升级段。 Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 3 +++ .../main/kotlin/com/duooomi/ble/DuooomiBleSDK.kt | 16 ++++++++++++++++ .../main/kotlin/com/duooomi/ble/core/BleTypes.kt | 1 + 3 files changed, 20 insertions(+) diff --git a/README.md b/README.md index 069a002..7eda03f 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,8 @@ sdk.upgradeFirmware( // OTA 包发完,设备开始烧录并重启 ``` +> ⚠️ `upgradeFirmware` 下发前会**主动拉一次新鲜 `getDeviceInfo`** 校验实时电量,低于 40% 直接抛 `DuooomiBleError.BatteryTooLow`,**不发任何 OTA 字节**(防烧录中途断电变砖)。 + ### 错误处理 ```kotlin @@ -238,6 +240,7 @@ try { catch (e: DuooomiBleError.AlreadyBoundByOtherUser) { /* 设备已绑给他人 */ } catch (e: DuooomiBleError.BrandMismatch) { /* 不支持该品牌设备(已自动断连) */ } catch (e: DuooomiBleError.FirmwareUpgradeUnavailable) { /* sn 为空等前置不满足 */ } + catch (e: DuooomiBleError.BatteryTooLow) { /* OTA 前电量 < 40%,未下发任何字节;e.current / e.required 可读 */ } ``` ## 运行 Demo diff --git a/sdk/src/main/kotlin/com/duooomi/ble/DuooomiBleSDK.kt b/sdk/src/main/kotlin/com/duooomi/ble/DuooomiBleSDK.kt index 55f320e..a2b77c8 100644 --- a/sdk/src/main/kotlin/com/duooomi/ble/DuooomiBleSDK.kt +++ b/sdk/src/main/kotlin/com/duooomi/ble/DuooomiBleSDK.kt @@ -452,6 +452,9 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) { // MARK: - High-Level APIs companion object { + /** OTA 烧录前置电量门槛(百分比)。低于此值拒绝下发,避免烧录中途断电变砖。 */ + internal const val MIN_BATTERY_FOR_UPGRADE = 40 + private val SUPPORTED_EXTENSIONS = setOf("jpeg", "jpg", "png", "gif", "mp4", "webm", "webp") private val SUPPORTED_MIME_TYPES = setOf( "image/jpeg", "image/png", "image/gif", "image/webp", @@ -550,6 +553,13 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) { /** * OTA 固件烧录。完成后自动落 [UpgradeRecord] 到本地(复合主键 `(sn, firmwareId)`), * 等待下次 [fetchLatestFirmware] 触发 [tryReportPendingUpgrade] 对账上报。 + * + * 下发前会**主动拉一次新鲜 [getDeviceInfo]** 校验实时电量,低于 + * [MIN_BATTERY_FOR_UPGRADE](40%)直接抛 [DuooomiBleError.BatteryTooLow],不发任何 OTA 字节。 + * + * @throws DuooomiBleError.NotConnected 未连接 + * @throws DuooomiBleError.BatteryTooLow 电量 < [MIN_BATTERY_FOR_UPGRADE](未下发任何字节) + * @throws DuooomiBleError.TransferFailed 烧录阶段失败;[getDeviceInfo] 的 Timeout / 解码错误同样透传 */ suspend fun upgradeFirmware( sn: String, @@ -559,6 +569,12 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) { targetVersion: String ) { ensureConnected() + // OTA 前置电量校验:取实时电量(不用缓存,可能过期),低于门槛拒绝下发。 + val info = getDeviceInfo() + if (info.powerlevel < MIN_BATTERY_FOR_UPGRADE) { + BleLog.w("OTA blocked: battery ${info.powerlevel}% < $MIN_BATTERY_FOR_UPGRADE%", "Firmware") + throw DuooomiBleError.BatteryTooLow(current = info.powerlevel, required = MIN_BATTERY_FOR_UPGRADE) + } BleLog.i("OTA upgrade start: sn=$sn $fromVersion→$targetVersion (fw=$firmwareId)", "Firmware") transferFile(fileUri = fileUrl, commandType = CommandType.OTA_PACKAGE) BleLog.i("OTA upgrade completed", "Firmware") diff --git a/sdk/src/main/kotlin/com/duooomi/ble/core/BleTypes.kt b/sdk/src/main/kotlin/com/duooomi/ble/core/BleTypes.kt index df187c1..67e10b5 100644 --- a/sdk/src/main/kotlin/com/duooomi/ble/core/BleTypes.kt +++ b/sdk/src/main/kotlin/com/duooomi/ble/core/BleTypes.kt @@ -44,5 +44,6 @@ sealed class DuooomiBleError(message: String) : Exception(message) { class UnsupportedFormat(val format: String) : DuooomiBleError("Unsupported file format: $format. Supported: jpeg, png, gif, mp4, webm, webp") object InvalidFrame : DuooomiBleError("Invalid protocol frame") object PermissionDenied : DuooomiBleError("Bluetooth permission denied") + class BatteryTooLow(val current: Int, val required: Int) : DuooomiBleError("设备电量不足,无法升级 (当前 ${current}%,需 ≥ ${required}%)") class BrandMismatch(val expected: String, val actual: String) : DuooomiBleError("不支持该品牌设备 (expected=$expected, actual=$actual)") }