Files
duooomi-ios-sdk/Sources/DuooomiBleSDK/DuooomiBleSDK+Binding.swift
km2023 97e5b8359a feat(sdk): 重命名 owner→brand 并在硬绑前校验设备品牌
- DuooomiBleConfig.owner → brand(语义:设备品牌名),HTTP header x-owner 不变
- bind() 前置 verifyBrand:缺 deviceInfo 先 getDeviceInfo,与 config.brand 严格匹配,不一致主动断连并抛 .brandMismatch
- README 抹除软绑/对账/UpgradeRecord 等内部实现细节,仅保留厂商面向的公开 API(功能代码完全保留)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:51:17 +08:00

152 lines
7.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
// MARK: - Binding (BLE / + OTA // +Firmware)
extension DuooomiBleSDK {
/// BLE 0x0F
///
/// **** BLE + `getDeviceVersion`
/// / / `fetchLatestFirmware(sn:currentVersion:userId:)`
///
/// SDK
/// - `sdk.sn`
/// - `sdk.version` getVersion bind
/// - `sdk.isActivated = true`delegate `didChangeActivation(true)`
///
/// - Parameters:
/// - userId: ID BLE BIND payload `bindUserId` token
/// - completion: `Result<BindingResponse, Error>`
/// - `BindingResponse(success: 1, sn, contents)`
/// - `.notConnected` / `.alreadyBoundByOtherUser`(success1) / `.timeout(.bindDevice)`
/// - Note: `bind` / `unbind` / `setPlayMode` `0x0F` / `0x12` opId ****
public func bind(userId: String, completion: @escaping (Result<BindingResponse, Error>) -> Void) {
guard ensureConnected(completion: completion) else { return }
verifyBrand { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let err):
completion(.failure(err))
case .success:
self.performHardBind(userId: userId, completion: completion)
}
}
}
/// brand SDK brand deviceInfo
/// `.brandMismatch`
private func verifyBrand(completion: @escaping (Result<Void, Error>) -> Void) {
if let cached = deviceInfo {
checkBrand(actual: cached.brand, completion: completion)
return
}
getDeviceInfo { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let err):
completion(.failure(err))
case .success(let info):
self.checkBrand(actual: info.brand, completion: completion)
}
}
}
private func checkBrand(actual: String, completion: @escaping (Result<Void, Error>) -> Void) {
let expected = config.brand
if actual == expected {
completion(.success(()))
return
}
BleLog.w("Brand mismatch: expected=\(expected), actual=\(actual); disconnecting", "Command")
disconnect { _ in
completion(.failure(DuooomiBleError.brandMismatch(expected: expected, actual: actual)))
}
}
private func performHardBind(userId: String, completion: @escaping (Result<BindingResponse, Error>) -> Void) {
BleLog.d("Sending bind (userId=\(userId))", "Command")
sendAsyncAndWait(commandType: .bindDevice, completion: { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let error):
completion(.failure(error))
case .success(let data):
do {
let resp = try self.decodeResponse(BindingResponse.self, from: data)
self.isActivated = resp.success == 1
if resp.success != 1 {
BleLog.w("Hard bind failed: success=0", "Command")
completion(.failure(DuooomiBleError.alreadyBoundByOtherUser))
return
}
self.sn = resp.sn
BleLog.i("Hard bind success: sn=\(resp.sn)", "Command")
// fetchLatestFirmware currentVersion
self.getVersion { _ in
completion(.success(resp))
}
} catch {
completion(.failure(error))
}
}
}, send: { done in
self.deviceInfoService.bindDevice(userId: userId, completion: done)
})
}
/// BLE 0x12 shipment-sn HTTPSDK
///
/// warn bind
///
/// SDK
/// - `sdk.sn`
/// - `sdk.isActivated = false`delegate `didChangeActivation(false)`
///
/// - Parameters:
/// - userId: ID BLE UNBIND payload + `bindUserId`
/// - completion: `Result<UnbindResponse, Error>`
/// - `UnbindResponse(success: 1)`
/// - `.notConnected` / `.unbindFailed`(BLE success1) / `.timeout(.unbindDevice)`
/// - Note: `bind` / `unbind` / `setPlayMode` `0x0F` / `0x12` opId ****
public func unbind(userId: String, completion: @escaping (Result<UnbindResponse, Error>) -> Void) {
guard ensureConnected(completion: completion) else { return }
let cachedSn = self.sn
BleLog.d("Sending unbind (userId=\(userId))", "Command")
sendAsyncAndWait(commandType: .unbindDevice, completion: { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let error):
completion(.failure(error))
case .success(let data):
do {
let resp = try self.decodeResponse(UnbindResponse.self, from: data)
if resp.success == 1 {
self.isActivated = false
self.sn = ""
BleLog.i("Hard unbind success", "Command")
if !cachedSn.isEmpty {
self.shipmentService.unbind(sn: cachedSn, bindUserId: userId) { softResult in
if case .failure(let err) = softResult {
BleLog.w("Soft unbind failed (ignored): \(err.localizedDescription)", "Command")
}
completion(.success(resp))
}
} else {
completion(.success(resp))
}
} else {
BleLog.w("Unbind failed", "Command")
completion(.failure(DuooomiBleError.unbindFailed))
}
} catch {
completion(.failure(error))
}
}
}, send: { done in
self.deviceInfoService.unbindDevice(userId: userId, completion: done)
})
}
}