feat(sdk): OTA 升级流程对齐 expo + 拆 extension 文件分层 + 秘钥本地化

主要改动:
- 新增 fetchLatestFirmware/upgradeFirmware/tryReportPendingUpgrade
  三段 OTA 接口,对齐 expo bindDeviceWithOrchestration 行为
- bind 仅做硬绑+getVersion,软绑/对账上报/拉最新固件搬到
  fetchLatestFirmware 内部串;软绑失败自动硬解回滚
- UpgradeRecord 复合主键 (sn, firmwareId) 持久化 + sn 级 in-flight 互斥
- DuooomiBleSDKDelegate 新增 didUpdateUpgradeRecords,集成方
  可观察对账上报结果(reported/outcome 状态)
- DuooomiBleSDK 主类按职责拆 5 个 extension 文件,公开 API 不变
- TargetFirmware 简化为只取必要字段 + 新增 forceUpgrade
- scanNamePrefix 改为必填 String
- demo 秘钥移至本地 DemoSecrets.swift (gitignored)
- README 重写:完整 OTA 流程示例 + 上报状态判定表

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
km2023
2026-05-08 14:46:41 +08:00
parent f4a32bb7e8
commit d5729a1a41
24 changed files with 2349 additions and 880 deletions

View File

@@ -0,0 +1,90 @@
import Foundation
// MARK: - Connection
extension DuooomiBleSDK {
///
///
/// `sdk.btState = .connected``sdk.connectedDevice`
/// delegate `didConnectDevice` `getVersion` / `getDeviceInfo` / `bind`
///
/// - Parameters:
/// - deviceId: UUID `discoveredDevices.id` `CBPeripheral.identifier.uuidString`
/// - completion: `Result<DiscoveredDevice, Error>`
/// - `DiscoveredDevice`
/// - `.connectionFailed(...)` / `.serviceNotFound` / `.characteristicNotFound`
/// / `.bluetoothNotPoweredOn(...)` / `.permissionDenied`
public func connect(deviceId: String, completion: @escaping (Result<DiscoveredDevice, Error>) -> Void) {
stopScan()
btState = .connecting
BleLog.i("Connecting to \(deviceId)...", "Connect")
bleClient.connect(deviceId: deviceId) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let peripheral):
self.protocolService.startListening()
let device = DiscoveredDevice(
id: peripheral.identifier.uuidString,
name: peripheral.name,
rssi: 0
)
self.connectedDevice = device
self.btState = .connected
BleLog.i("Connected: \(device.id)", "Connect")
completion(.success(device))
case .failure(let error):
self.btState = .idle
self.error = error.localizedDescription
BleLog.e("Connect failed: \(error.localizedDescription)", "Connect")
completion(.failure(error))
}
}
}
/// BLE
///
/// `sdk.connectedDevice` / `deviceInfo` / `version` / `isActivated`
/// pending RPC `.notConnected`
///
/// - Parameter completion: `Result<Void, Error>`
/// -
/// - CoreBluetooth
public func disconnect(completion: @escaping (Result<Void, Error>) -> Void) {
btState = .disconnecting
BleLog.i("Disconnect requested", "Connect")
protocolService.stopListening()
cancelAllPending(error: DuooomiBleError.notConnected)
bleClient.disconnect { [weak self] result in
guard let self = self else { return }
if case .failure(let error) = result {
self.error = error.localizedDescription
BleLog.e("Disconnect error: \(error.localizedDescription)", "Connect")
}
self.connectedDevice = nil
self.deviceInfo = nil
self.version = ""
self.isActivated = false
self.btState = .disconnected
BleLog.i("Disconnected", "Connect")
completion(result)
}
}
/// iOS SDK UUID
///
/// App
/// `scan()` `DiscoveredDevice.id` `connect(deviceId:)`
///
/// - Returns: `DiscoveredDevice`
public func getConnectedDevices() -> [DiscoveredDevice] {
let list = bleClient.retrieveConnectedDevices()
BleLog.d("System-connected devices: \(list.count)", "Connect")
return list
}
}