Files
duooomi-ios-sdk/README.md
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

307 lines
10 KiB
Markdown
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.
# DuooomiBleSDK (iOS)
纯原生 Swift BLE SDK零第三方依赖。
## 集成
### CocoaPods
```ruby
pod 'DuooomiBleSDK', :git => 'https://gitea.bowong.cc/comi-logic/duooomi-ios-sdk.git', :branch => 'main'
```
然后执行 `pod install`
### Swift Package Manager
```swift
// Package.swift
dependencies: [
.package(url: "https://gitea.bowong.cc/comi-logic/duooomi-ios-sdk.git", branch: "main")
]
```
## Demo 初始化(仓库内 demo 目录)
`apiKey` / `brand` / `scanNamePrefix` 不进 git由本地 `demo/Sources/DemoSecrets.swift` 提供:
```bash
cp demo/Sources/DemoSecrets.template.swift demo/Sources/DemoSecrets.swift
# 然后编辑 DemoSecrets.swift 填真实 apiKey
pod install
open demo.xcworkspace
```
`DemoSecrets.swift` 已加入 `.gitignore`,本地修改不会被 commit。
## 初始化
```swift
let sdk = DuooomiBleSDK(config: .init(
apiKey: "your-api-key", // Loomart x-api-key
brand: "your-brand", // RPC x-owner header
scanNamePrefix: "蓝牙广播名前缀" //
// apiHost: URL(...)!, // https://api.duooomi.com
// cdnHost: "https://cdn.bowong.cc/", //
// aniWidth: 360, aniHeight: 360, aniFps: "24", // ANI
))
sdk.delegate = self
```
## SDK 能力范围
- **内置**BLE 协议(扫描/连接/绑定/解绑/播放模式/文件传输/OTA 烧录)+ 固件版本检查
- **不内置**UI、retry 策略SDK 只提供原子方法和状态属性,由集成方组合
### 绑定与固件升级流程
1. **`bind(userId:)`** — 绑定设备,完成后 `sn` / `version` / `isActivated` 就绪。
2. **`fetchLatestFirmware(sn:currentVersion:userId:)`** — 拉服务端最新固件信息,写入 `firmwareUpgrade`
3. **`upgradeFirmware(...)`** — 烧录固件包,进度通过 `didUpdateProgress` 回调。
解绑:`unbind(userId:)` 一步完成。
### API 调用风格
所有公开方法**显式传入参**,不依赖 SDK 内部状态自动取参。调用方从 SDK 只读属性(`sdk.sn` / `sdk.version` / `sdk.firmwareUpgrade?.targetFirmware?.*` 等)组装参数后传入。
## 代理Delegate
通过 `DuooomiBleSDKDelegate` 接收状态变化,所有方法在主线程调用,均有默认空实现:
```swift
extension ViewController: DuooomiBleSDKDelegate {
func sdk(_ sdk: DuooomiBleSDK, didChangeState state: ConnectionState) { }
func sdk(_ sdk: DuooomiBleSDK, didUpdateDevices devices: [DiscoveredDevice]) { }
func sdk(_ sdk: DuooomiBleSDK, didConnectDevice device: DiscoveredDevice?) { }
func sdk(_ sdk: DuooomiBleSDK, didUpdateDeviceInfo info: DeviceInfo?) { }
func sdk(_ sdk: DuooomiBleSDK, didUpdateVersion version: String) { }
func sdk(_ sdk: DuooomiBleSDK, didChangeActivation isActivated: Bool) { }
func sdk(_ sdk: DuooomiBleSDK, didUpdateProgress progress: Int) { }
func sdk(_ sdk: DuooomiBleSDK, didEncounterError error: String?) { }
}
```
## API
所有异步方法使用 `completion` 回调,返回 `Result<T, Error>`
### 扫描
| 方法 | 说明 |
|------|------|
| `scan()` | 开始扫描,结果通过 delegate `didUpdateDevices` 回调 |
| `stopScan()` | 停止扫描 |
### 连接
```swift
sdk.connect(deviceId: deviceId) { result in
switch result {
case .success(let device): print("Connected: \(device.name ?? device.id)")
case .failure(let error): print("Failed: \(error)")
}
}
sdk.disconnect { result in
// ...
}
```
### 设备命令
```swift
sdk.getDeviceInfo { result in
if case .success(let info) = result {
print("Brand: \(info.brand), Power: \(info.powerlevel)%")
}
}
sdk.getVersion { result in
if case .success(let info) = result {
print("Version: \(info.version)")
}
}
// +
sdk.bind(userId: "user-id") { result in
if case .success(let resp) = result {
print("SN: \(resp.sn), Files: \(resp.contents.count)")
// sdk.sn / sdk.version / sdk.isActivated
}
}
//
sdk.unbind(userId: "user-id") { result in /* ... */ }
sdk.deleteFile(key: "file-key") { result in /* ... */ }
```
#### 播放模式Play Mode
> ⚠️ **前置条件 — 必须先绑定。** `setPlayMode` 复用 `BIND_DEVICE` (0x0F) 命令通道,固件设计上要求设备先和该 `userId` 完成 `bind`(即 SDK `isActivated == true`才能切播放模式。SDK 在客户端做了守卫:未绑定时直接 fail不会发命令。调用方在 `isActivated == true` 之前不应调用此方法。
```swift
// 0 = / 1 =
sdk.setPlayMode(.loop, userId: "user-id") { result in
switch result {
case .success:
// SDK deviceInfo
// didUpdateDeviceInfo info.loop
break
case .failure(let error):
print("setPlayMode failed: \(error)")
}
}
```
**注意事项:**
- `bind` / `unbind` / `setPlayMode` 共享 `0x0F` / `0x12` 响应通道,**不要并发调用**
- 旧固件可能不支持 `loop` 字段 —— 此时 `DeviceInfo.loop``nil`,调用 `setPlayMode` 设备可能回 `success=0`
### 文件传输
```swift
// ANI prepare transfer
sdk.transferMedia(fileUrl: "https://example.com/video.mp4") { result in
switch result {
case .success: print("Transfer complete")
case .failure(let error): print("Transfer failed: \(error)")
}
}
// delegate didUpdateProgress
```
**支持的媒体格式:** JPEG、PNG、GIF、MP4、WebM、WebP
格式校验规则:
- 优先从 URL path 提取扩展名(自动忽略查询参数)
- 无扩展名时通过 HEAD 请求检查 `Content-Type`
- 不支持的格式返回 `DuooomiBleError.unsupportedFormat`
### 固件升级
完整流程:**扫描 → 连接 → bind → fetchLatestFirmware → upgradeFirmware**。设备重启重连后再次走一遍即可。
```swift
// 1. +
sdk.scan()
// didUpdateDevices connect
sdk.connect(deviceId: device.id) { result in
guard case .success = result else { return }
// 2.
sdk.bind(userId: "user-123") { result in
guard case .success(let resp) = result else { return }
// sdk.sn = resp.sn / sdk.version / sdk.isActivated = true
// 3.
sdk.fetchLatestFirmware(
sn: sdk.sn,
currentVersion: sdk.version,
userId: "user-123"
) { result in
switch result {
case .failure(let err):
print("拉取失败:\(err.localizedDescription)")
return
case .success(let info):
// info sdk.firmwareUpgrade
guard let target = info.targetFirmware,
let fileUrl = target.fileUrl else {
print("无可升级固件")
return
}
// 4.
sdk.upgradeFirmware(
sn: sdk.sn,
fileUrl: fileUrl,
firmwareId: target.id,
fromVersion: sdk.version,
targetVersion: target.version
) { result in
switch result {
case .success:
// OTA
print("烧录命令完成,等待设备重启")
case .failure(let err):
print("烧录失败:\(err.localizedDescription)")
}
}
}
}
}
}
```
### 可读取状态
| 属性 | 类型 | 说明 |
|------|------|------|
| `btState` | `ConnectionState` | idle / scanning / connecting / connected / disconnecting / disconnected |
| `discoveredDevices` | `[DiscoveredDevice]` | 扫描到的设备列表 |
| `connectedDevice` | `DiscoveredDevice?` | 当前连接设备 |
| `deviceInfo` | `DeviceInfo?` | 设备信息 |
| `version` | `String` | 固件版本 |
| `isActivated` | `Bool` | 是否已绑定 |
| `transferProgress` | `Int` | 传输进度 0-100 |
| `error` | `String?` | 最近错误 |
### 数据类型
```swift
// FirmwareUpgradeCheckResult fetchLatestFirmware sdk.firmwareUpgrade
public struct FirmwareUpgradeCheckResult {
let upgradeAvailable: Bool
let reason: FirmwareUpgradeCheckReason // UPGRADE_AVAILABLE / ALREADY_LATEST / DEVICE_NOT_FOUND ...
let currentSystemVersion: String?
let targetFirmware: TargetFirmware?
}
public struct TargetFirmware {
let id: String // firmwareId使
let version: String //
let fileUrl: String? //
let fileSize: String? //
let fileMd5: String?
}
// PlayMode
public enum PlayMode: Int {
case single = 0 //
case loop = 1 //
}
// DeviceInfo getDeviceInfo
public struct DeviceInfo {
let name: String //
let brand: String //
let size: String //
let powerlevel: Int //
let allspace: UInt64 //
let freespace: UInt64 //
let loop: PlayMode? // nil
}
// BindingResponse bind
public struct BindingResponse {
let success: Int // 1=
let sn: String //
let contents: [String] // key
}
```
## Info.plist
```xml
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs Bluetooth to communicate with your device</string>
```
## 技术规格
- iOS 12.2+ / Swift 5.0+ / 零依赖
- Delegate 回调模式,所有状态变化在主线程通知
- BLE 操作专用串行队列