feat(sdk): 绑定时上报设备 log 到 update-metadata 并内聚双层绑定

- bind(userId:) 内聚为硬绑 + getVersion + 软绑 + updateMetadata 一次原子调用,
  软绑失败自动硬解绑回滚 + 清状态;集成方无需感知双层绑定
- fetchLatestFirmware 简化为对账上报 + checkUpgrade 两步,不再重复软绑
- VersionInfo 增加 log: [String: Any]?,走 JSONSerialization 解析(放弃 Codable);
  SDK 暴露只读 versionLog,断连/解绑/软绑回滚时清空
- ShipmentSnDeviceService 新增 updateMetadata(sn:data:),抽出 performPOST
  共享 HTTP 编排,新增 postAnyJSON 支持 [String: Any] body
- 老固件不返回 log → versionLog == nil → 自动跳过 updateMetadata,前向兼容

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
km2023
2026-05-18 16:25:55 +08:00
parent cdcf36a75b
commit 4bc8fdb64d
6 changed files with 163 additions and 67 deletions

View File

@@ -4,21 +4,27 @@ import Foundation
extension DuooomiBleSDK {
/// BLE 0x0F
///
///
/// **** BLE + `getDeviceVersion`
/// / / `fetchLatestFirmware(sn:currentVersion:userId:)`
/// 1. **BLE ** 0x0F sn / isActivated
/// 2. **getVersion** version / versionLogtype Int/Stringlog JSON nil
/// 3. ****POST `/api/auth/loomart/shipment-sn/device/bind`
/// - BLE 0x12 + `isActivated`/`sn`/`versionLog` bind
/// 4. **updateMetadata**fire-and-forget versionLog `shipment-sn/device/update-metadata`
/// - warn versionLog /
///
/// SDK
/// - `sdk.sn`
/// - `sdk.version` getVersion bind
/// - `sdk.versionLog` log JSON nil
/// - `sdk.isActivated = true`delegate `didChangeActivation(true)`
///
/// - Parameters:
/// - userId: ID BLE BIND payload `bindUserId` token
/// - userId: ID BLE BIND payload + `bindUserId`
/// - completion: `Result<BindingResponse, Error>`
/// - `BindingResponse(success: 1, sn, contents)`
/// - `.notConnected` / `.alreadyBoundByOtherUser`(success1) / `.timeout(.bindDevice)`
/// - `BindingResponse(success: 1, sn, contents)`metadata
/// - `.notConnected` / `.alreadyBoundByOtherUser`( success1) / `.timeout(.bindDevice)`
/// / `.brandMismatch` / `.softBindFailed`
/// - Note: `bind` / `unbind` / `setPlayMode` `0x0F` / `0x12` opId ****
public func bind(userId: String, completion: @escaping (Result<BindingResponse, Error>) -> Void) {
guard ensureConnected(completion: completion) else { return }
@@ -82,9 +88,9 @@ extension DuooomiBleSDK {
}
self.sn = resp.sn
BleLog.i("Hard bind success: sn=\(resp.sn)", "Command")
// fetchLatestFirmware currentVersion
// self.version / self.versionLog updateMetadata
self.getVersion { _ in
completion(.success(resp))
self.performSoftBindAndMetadata(resp: resp, userId: userId, completion: completion)
}
} catch {
completion(.failure(error))
@@ -95,6 +101,50 @@ extension DuooomiBleSDK {
})
}
/// + metadata + + bind
/// metadata fire-and-forget warn
private func performSoftBindAndMetadata(
resp: BindingResponse,
userId: String,
completion: @escaping (Result<BindingResponse, Error>) -> Void
) {
let sn = resp.sn
BleLog.d("Soft bind start: sn=\(sn)", "Command")
shipmentService.bind(sn: sn, bindUserId: userId) { [weak self] result in
guard let self = self else { return }
if case .failure(let err) = result {
BleLog.e("Soft bind failed: \(err.localizedDescription); rolling back hard unbind", "Command")
// fire-and-forgetBLE ack bind
self.deviceInfoService.unbindDevice(userId: userId) { rollbackResult in
if case .failure(let rbErr) = rollbackResult {
BleLog.w("Hard unbind rollback failed (ignored): \(rbErr.localizedDescription)", "Command")
} else {
BleLog.i("Hard unbind rollback sent", "Command")
}
}
self.isActivated = false
self.sn = ""
self.versionLog = nil
DispatchQueue.main.async { completion(.failure(err)) }
return
}
BleLog.i("Soft bind success: sn=\(sn)", "Command")
// metadata fire-and-forgetversionLog /
if let log = self.versionLog, !log.isEmpty {
self.shipmentService.updateMetadata(sn: sn, data: log) { mdResult in
if case .failure(let err) = mdResult {
BleLog.w("updateMetadata failed (ignored): \(err.localizedDescription)", "Command")
} else {
BleLog.i("updateMetadata success: sn=\(sn)", "Command")
}
}
}
DispatchQueue.main.async { completion(.success(resp)) }
}
}
/// BLE 0x12 shipment-sn HTTPSDK
///
/// warn bind
@@ -125,6 +175,7 @@ extension DuooomiBleSDK {
if resp.success == 1 {
self.isActivated = false
self.sn = ""
self.versionLog = nil
BleLog.i("Hard unbind success", "Command")
if !cachedSn.isEmpty {
self.shipmentService.unbind(sn: cachedSn, bindUserId: userId) { softResult in