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:
@@ -4,21 +4,27 @@ import Foundation
|
||||
|
||||
extension DuooomiBleSDK {
|
||||
|
||||
/// 硬绑:BLE 协议层把当前用户绑定到设备(命令 0x0F),完成后同步拉一次版本号。
|
||||
/// 完整绑定:内部串四步业务编排(顺序固定),对集成方表现为一次原子调用。
|
||||
///
|
||||
/// **作用域**:只做 BLE 通道的绑定 + `getDeviceVersion`。
|
||||
/// 软绑 / 升级对账上报 / 拉最新固件 → 全部移到 `fetchLatestFirmware(sn:currentVersion:userId:)` 内部串。
|
||||
/// 1. **BLE 硬绑**(命令 0x0F):写 sn / isActivated
|
||||
/// 2. **getVersion**:写 version / versionLog(type 兼容 Int/String;log 为任意 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`(success≠1) / `.timeout(.bindDevice)`
|
||||
/// - 成功:`BindingResponse(success: 1, sn, contents)`(软绑也已成功,metadata 上报与否不影响结果)
|
||||
/// - 失败:`.notConnected` / `.alreadyBoundByOtherUser`(硬绑 success≠1) / `.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-forget;BLE 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-forget;versionLog 缺失/空则跳过)
|
||||
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 HTTP)。SDK 内部完成全套。
|
||||
///
|
||||
/// 软解失败仅打 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
|
||||
|
||||
Reference in New Issue
Block a user