--- /dev/null
+# IOTBluetooth
+
+## 介绍
+
+- 当前版本:v1.0.11
+
+- 更新内容20240612:
+ 1. 表盘传输接口调整、OTA升级优化
+
+ 1. 修改IBHistoryDistanceItem、IBHistoryCalorieItem的数值类型
+
+
+
+
+## 环境要求
+
+- iOS系统版本: iOS 12.0以上
+- 真机环境
+
+## 软件架构
+
+SDK 分为两部分
+
+1. 蓝牙封装
+2. 蓝牙协议指令封装
+
+## 安装教程
+
+1. 把 IOTBluetooth.Framework` 拖入项目中
+2. 在使用的文件中`#import IOTBluetooth`
+
+## 使用流程
+
+### 添加权限
+
+```
+info.plist中添加以下权限:
+
+Privacy - Bluetooth Always Usage Description
+
+Privacy - Bluetooth Peripheral Usage Description
+```
+
+### SDK初始化
+
+```swift
+IBManager.initializationSDK()
+```
+
+### 扫描蓝牙设备
+
+```swift
+/// 扫描设备
+/// - Parameters:
+/// - option: 扫描参数
+/// - discoverHandler: 结果回调
+IBManager.scan(_ option: IBScanOption = .default, discoverHandler: @escaping IBDiscoverHandler)
+```
+
+### 连接
+
+```swift
+/// 连接设备
+/// - Parameters:
+/// - peripheral: 扫描发现的设备
+/// - completeHandler: 结果回调
+IBManager.connect(_ peripheral: IBPeripheral, completeHandler: @escaping IBCompleteHandler)
+```
+
+### 停止扫描
+
+```swift
+IBManager.stopScan()
+```
+
+### 断连
+
+```swift
+/// 断开与蓝牙设备的连接
+/// - Parameter peripheral: 蓝牙设备对像
+IBManager.cancel(_ peripheral: IBPeripheral)
+```
+
+### 通知
+
+```swift
+extension IBNotify.Name {
+
+ /// 连接状态发生改变。通过object 获取状态,0断开连接。1连上设备。
+ public static let connectStateDidChange: IOTBluetooth.IBNotify.Name
+
+ /// 远程拍照动作。通过object获取状态。2进行手势拍照,3退出
+ public static let takePhoto: IOTBluetooth.IBNotify.Name
+
+ /// 手表请求天气数据
+ public static let weather: IOTBluetooth.IBNotify.Name
+
+ /// 找到设备
+ public static let didFindDevice: IOTBluetooth.IBNotify.Name
+
+ /// 训练状态变化,通过object获取状态IBTarinState对象
+ public static let trainStateDidChanged: IOTBluetooth.IBNotify.Name
+
+ /// 获取手表推送的训练实时数据,通过object获取状态IBTrainingData对象
+ public static let trainingData: IOTBluetooth.IBNotify.Name
+}
+```
+
+
+
+## 蓝牙协议相关
+
+### 相关接口
+
+```swift
+extension IBAbility {
+
+ /// 设置用户数据
+ /// - Parameter userInfo: see ``IBUserInfo``
+ public static let userData: IOTBluetooth.IBAbility
+
+ /// 关于本机内容
+ public static let about: IOTBluetooth.IBAbility
+
+ /// 切换表盘
+ /// - Parameter dialID:Int
+ /// 表盘id
+ public static let switchDial: IOTBluetooth.IBAbility
+
+ /// APP删除表盘
+ /// - Parameter dialID:Int \
+ /// 表盘id
+ public static let deleteDial: IOTBluetooth.IBAbility
+
+ /// 发送当前表盘ID
+ /// - Returns: Int \
+ /// Read from ``IBResponse``.value
+ public static let fetchCurrentDialId: IOTBluetooth.IBAbility
+
+ /// 查找手表
+ /// - Parameter action:
+ /// 0 = 停止查找 \
+ /// 1 = 开始查找
+ public static let find: IOTBluetooth.IBAbility
+
+ /// 开关消息推送功能
+ /// - Parameter action:
+ /// 0 = 关闭消息 \
+ /// 1 = 开启消息
+ public static let message: IOTBluetooth.IBAbility
+
+ /// 开启指定运动模式
+ /// - Parameter motionMode: see ``IBMotionMode``
+ public static let startSport: IOTBluetooth.IBAbility
+
+ /// 结束运动模式
+ public static let endSport: IOTBluetooth.IBAbility
+
+ /// 设置训练实时数据采样频率
+ /// - Parameter value:Int \
+ /// 单位(秒)
+ public static let trainFrequency: IOTBluetooth.IBAbility
+
+ /// 训练暂停/恢复
+ /// - Parameter action:
+ /// 0 = 暂停 \
+ /// 1 = 恢复
+ public static let trainPauseResume: IOTBluetooth.IBAbility
+
+ /// 设置语言
+ /// - Parameter language: see ``IBLanguage``
+ public static let language: IOTBluetooth.IBAbility
+
+ /// 获取手表心率参数
+ /// - Returns: ``IBHeartRate`` \
+ /// Read from ``IBResponse``.value
+ public static let fetchHeartRate: IOTBluetooth.IBAbility
+
+ /// 设置手表心率参数
+ /// - Parameter heartRate: see ``IBHeartRate``
+ public static let heartRate: IOTBluetooth.IBAbility
+
+ /// 获取手表血氧设置
+ /// - Returns: ``IBBloodOxygen`` \
+ /// Read from ``IBResponse``.value
+ public static let fetchBloodOxygen: IOTBluetooth.IBAbility
+
+ /// 设置手表血氧参数
+ /// - Parameter heartRate: see ``IBBloodOxygen``
+ public static let bloodOxygen: IOTBluetooth.IBAbility
+
+ /// 获取实时心率(实时训练数据)
+ /// - Returns: ``IBTrainingData`` \
+ /// Read from ``IBResponse``.value
+ public static let currentHeartRate: IOTBluetooth.IBAbility
+
+ /// 开启/关闭自动获取实时心率
+ /// - Parameter action:
+ /// 0 = 关闭自动获取 \
+ /// 1 = 开启自动获取
+ public static let heartRateMode: IOTBluetooth.IBAbility
+
+ /// 设置最高心率提醒
+ /// - Parameter value:Int \
+ /// 最高不超过 250 ,如果需要关闭最高心率提醒,可将最高心率设为255,开启则设正常值即可。
+ public static let heartRateHigh: IOTBluetooth.IBAbility
+
+ /// 手势拍照
+ /// - Parameter action:
+ /// 0 = 退出手势拍照 \
+ /// 1 = 进入手势拍照
+ public static let takePhoto: IOTBluetooth.IBAbility
+
+ /// 恢复出厂设置
+ public static let restore: IOTBluetooth.IBAbility
+
+ /// 重新启动
+ public static let reboot: IOTBluetooth.IBAbility
+
+ /// 关机
+ public static let shutdown: IOTBluetooth.IBAbility
+
+ /// 获取手表电池电量。
+ /// - Returns: Int \
+ /// Read from ``IBResponse``.value
+ public static let fetchBattery: IOTBluetooth.IBAbility
+
+ /// 获取闹钟设置
+ /// - Returns: ``[IBClockInfo]`` \
+ /// Read from ``IBResponse``.items
+ public static let fetchClock: IOTBluetooth.IBAbility
+
+ /// 发送闹钟设置
+ /// - Parameter clock: see ``IBClock``
+ public static let setClock: IOTBluetooth.IBAbility
+
+ /// 获取所有表盘UID
+ /// - Returns: [Int] \
+ /// Read from ``IBResponse``.items
+ public static let fetchDials: IOTBluetooth.IBAbility
+
+ /// APP获取某表盘ID是否已安装
+ /// - Parameter value:Int
+ /// - Returns: Int
+ /// 1 = 已安装 \
+ /// 2 = 未安装 \
+ /// Read from ``IBResponse``.value
+ public static let checkDialID: IOTBluetooth.IBAbility
+
+ /// 获取当前手表状态
+ /// - Returns: Int \
+ /// 0 = 空闲状态 \
+ /// 9 = USB存储状态 \
+ /// 10 = 运动训练状态 \
+ /// Read from ``IBResponse``.value
+ public static let fetchWatchState: IOTBluetooth.IBAbility
+
+ /// 获取手表屏幕参数
+ /// - Returns: ``IBScreen`` \
+ /// Read from ``IBResponse``.value
+ public static let fetchScreen: IOTBluetooth.IBAbility
+
+ /// 设置手表屏幕参数
+ /// - Parameter screen: see ``screen``
+ public static let screen: IOTBluetooth.IBAbility
+
+ /// 获取手表声音与震动参数
+ /// - Returns: ``IBVibrateSound`` \
+ /// Read from ``IBResponse``.value
+ public static let fetchVibrateSound: IOTBluetooth.IBAbility
+
+ /// APP设置手表声音与震动参数
+ /// - Parameter vibrateSound: see ``IBVibrateSound``
+ public static let vibrateSound: IOTBluetooth.IBAbility
+
+ /// 设置手表辅助功能参数
+ /// - Parameter auxiliary: see ``IBAuxiliary``
+ public static let auxiliary: IOTBluetooth.IBAbility
+
+ /// 获取手表辅助功能参数
+ /// - Returns: IBAuxiliary \
+ /// Read from ``IBResponse``.value
+ public static let fetchAuxiliary: IOTBluetooth.IBAbility
+
+ /// 获取目标步数设置
+ /// - Returns: ``IBTargetStep`` \
+ /// Read from ``IBResponse``.value
+ public static let fetchTargetStep: IOTBluetooth.IBAbility
+
+ /// 设置目标步数
+ /// - Parameter targetStep: see ``IBTrainTarget``
+ public static let targetStep: IOTBluetooth.IBAbility
+
+ /// 获取其他目标设置
+ /// - Returns: ``IBOtherTarget`` \
+ /// Read from ``IBResponse``.value
+ public static let fetchOtherTarget: IOTBluetooth.IBAbility
+
+ /// 设置其他目标
+ /// - Parameter targetOther: see ``IBOtherTarget``
+ public static let targetOther: IOTBluetooth.IBAbility
+
+ /// 获取睡眠设置
+ /// - Returns: ``IBSleep`` \
+ /// Read from ``IBResponse``.value
+ public static let fetchSleep: IOTBluetooth.IBAbility
+
+ /// 设置睡眠
+ /// - Parameter sleep: see ``IBSleep``
+ public static let sleep: IOTBluetooth.IBAbility
+
+ /// 获取手表喝水提醒设置
+ /// - Returns: Int \
+ /// 0 = 关闭 \
+ /// 1 = 开启 \
+ /// Read from ``IBResponse``.value
+ public static let fetchWater: IOTBluetooth.IBAbility
+
+ /// 设置喝水提醒
+ /// - Parameter water: see ``IBWater``
+ public static let water: IOTBluetooth.IBAbility
+
+ /// 设置手表公英制单位
+ /// - Parameter value:Int \
+ /// 0 = 公制 \
+ /// 1 = 英制
+ public static let unit: IOTBluetooth.IBAbility
+
+ /// 设置训练目标
+ /// - Parameter trainTarget:see ``IBTrainTarget``
+ public static let trainTarget: IOTBluetooth.IBAbility
+
+ /// 同步心率历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryHeartRateItem] \
+ /// Read from ``IBResponse``.items
+ public static let syncHeartRate: IOTBluetooth.IBAbility
+
+ /// 同步血氧历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryBloodOxygenItem] \
+ /// Read from ``IBResponse``.items
+ public static let syncBloodOxygen: IOTBluetooth.IBAbility
+
+ /// 同步历史步数
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryStepItem] \
+ /// Read from ``IBResponse``.items
+ public static let syncStepCount: IOTBluetooth.IBAbility
+
+ /// 同步燃脂时间历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryBurningItem] \
+ /// Read from ``IBResponse``.items
+ public static let syncFatBurning: IOTBluetooth.IBAbility
+
+ /// 同步站立历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryStandItem] \
+ /// Read from ``IBResponse``.items
+ public static let syncStand: IOTBluetooth.IBAbility
+
+ /// 同步卡路里历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryCalorieItem] \
+ /// Read from ``IBResponse``.items
+ public static let syncCalorie: IOTBluetooth.IBAbility
+
+ /// 同步距离历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryDistanceItem] \
+ /// Read from ``IBResponse``.items
+ public static let syncDistance: IOTBluetooth.IBAbility
+
+ /// 同步基础训练历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryBaseTrainingItem] \
+ /// Read from ``IBResponse``.items
+ public static let syncTrain: IOTBluetooth.IBAbility
+
+ /// 同步睡眠历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistorySleepItem] \
+ /// Read from ``IBResponse``.items
+ public static let syncSleep: IOTBluetooth.IBAbility
+
+ /// 同步路径坐标集历史数据
+ /// - Parameter value:开始训练时间戳
+ /// - Returns: [IBHistoryGpsItem] \
+ /// Read from ``IBResponse``.items
+ public static let syncGPS: IOTBluetooth.IBAbility
+
+ /// 发送同步数据结束消息
+ public static let sendSyncEnd: IOTBluetooth.IBAbility
+
+ /// 获取SD卡剩余空间
+ /// - Returns: Int \
+ /// Read from ``IBResponse``.value
+ public static let disk: IOTBluetooth.IBAbility
+
+ /// 发送手机信息
+ /// - Returns: Int \
+ /// 0 = 指令校验失败 \
+ /// 1 = 手表未与任何手机绑定 \
+ /// 2 = 手表已与该手机绑定 \
+ /// 3 = 手表已与其他手机绑定 \
+ /// Read from ``IBResponse``.value
+ public static let sendPhone: IOTBluetooth.IBAbility
+
+ /// 发送当前时间
+ /// - Parameter sendTime: see ``IBSendTime``
+ public static let sendTime: IOTBluetooth.IBAbility
+
+ /// 发送解除配对消息
+ public static let unpair: IOTBluetooth.IBAbility
+
+ /// 发送取消解除配对消息
+ public static let sendUnpair: IOTBluetooth.IBAbility
+
+ /// 发送天气数据
+ /// - Parameter weather: see ``IBWeather``
+ public static let sendWeather: IOTBluetooth.IBAbility
+
+ /// 发送通讯录
+ /// - Parameter addressBook: see ``IBAddressBook``
+ /// 联系人个数0~20
+ public static let addressBook: IOTBluetooth.IBAbility
+
+ /// 发送MP3文件信息
+ /// - Parameter mp3: see ``IBMp3File``
+ public static let mp3: IOTBluetooth.IBAbility
+
+ /// 发送自定义表盘文件汇总信息
+ /// - Parameter customDial: see ``IBCustomDail``
+ public static let customDial: IOTBluetooth.IBAbility
+
+ /// 发送市场表盘文件汇总信息
+ /// - Parameter marketDial: see ``IBMarketDail``
+ public static let marketDial: IOTBluetooth.IBAbility
+
+ /// 删除指定文件
+ /// - Parameter deleteFile: see ``IBDeleteFile``
+ public static let deleteFile: IOTBluetooth.IBAbility
+
+ /// 删除指定类型所有文件(当前只支持 MP3 和 表盘)
+ /// - Parameter file: see ``IBFile``
+ public static let deleteFiles: IOTBluetooth.IBAbility
+
+ /// 发送OTA文件信息
+ /// - Parameter ota: see ``IBOtaFile``
+ public static let ota: IOTBluetooth.IBAbility
+
+ /// 撤销当前文件传输
+ /// - Parameter file: see ``IBFile``
+ public static let revokeFileSend: IOTBluetooth.IBAbility
+
+ /// 获取指定类型所有文件(当前只支持 MP3 和 表盘)
+ /// - Parameter file: see ``IBFile``
+ /// - Returns: IBFetchFile \
+ /// Read from ``IBResponse``.value
+ public static let fetchFiles: IOTBluetooth.IBAbility
+}
+```
+
+
+
+### 方法调用
+
+```swift
+/// 发送指令
+/// IBAbility 功能接口
+/// IBParameter 参数
+/// IBCompletionHandler 请求结果,返回的闭包IBResponse包含isSuccess、message、items(当结果为集合时,应该从此字段读取)、value(当结果为非集合,应该从此字段读取)
+IBSender().ability(_ value: IBAbility).parameters(_ value: [IBParameter]).response(_ handler: @escaping IBCompletionHandler)
+```
+
+### 示例
+
+```swift
+let datas: [(ability: IBAbility, label: String, parameters: [IBParameter])] = [
+ (.userData,"设置用户数据",[.userInfo(IBUserInfo())]),
+ (.language, "设置语言", [.language(.english)]),
+ (.about,"关于本机内容",[]),
+ (.find, "开始查找手表", [.action(1)]),
+ (.find, "停止查找手表", [.action(0)]),
+ (.message, "开启消息推送功能", [.action(1)]),
+ (.message, "关闭消息推送功能", [.action(0)]),
+
+ (.takePhoto, "开启手势拍照", [.action(1)]),
+ (.takePhoto, "关闭手势拍照", [.action(0)]),
+ (.restore, "恢复出厂设置", []),
+ (.reboot, "重新启动", []),
+ (.shutdown, "关机", []),
+ (.fetchScreen, "获取手表屏幕参数", []),
+ (.screen, "设置手表屏幕参数", [.screen(IBScreen())]),
+ (.fetchBattery, "获取电量", []),
+ (.fetchClock, "获取闹钟设置", []),
+ (.setClock, "发送闹钟设置", [.clock(IBClock())]),
+
+ (.fetchCurrentDialId, "发送当前表盘ID", []),
+ (.switchDial, "切换表盘", [.dialID(26112)]),
+ (.deleteDial, "删除表盘", [.dialID(7168)]),
+ (.fetchDials, "获取所有表盘UID", []),
+ (.checkDialID, "获取某表盘ID是否已安装", [.dialID(12)]),
+
+ (.fetchWatchState, "获取当前手表状态", []),
+ (.fetchVibrateSound, "获取手表声音与震动参数", []),
+ (.vibrateSound, "设置手表声音与震动参数", [.vibrateSound(IBVibrateSound())]),
+ (.fetchAuxiliary, "获取手表辅助功能参数", []),
+ (.auxiliary, "设置手表辅助功能参数", [.auxiliary(.bloodOxygen)]),
+ (.unit, "设置手表公英制单位", [.value(1)]),
+
+
+ (.disk, "获取SD卡剩余空间", []),
+ (.unpair, "发送解除配对消息", []),
+ (.sendUnpair, "发送取消解除配对消息", []),
+
+
+ (.startSport, "开启指定运动模式", [.motionMode(.running)]),
+ (.trainPauseResume, "训练暂停", [.action(0)]),
+ (.trainPauseResume, "训练恢复", [.action(1)]),
+ (.endSport, "结束运动模式", []),
+
+ (.heartRateMode, "开启自动获取实时心率", [.action(1)]),
+ (.heartRateMode, "关闭自动获取实时心率", [.action(0)]),
+ (.heartRateHigh, "设置最高心率提醒", [.value(240)]),
+ (.heartRateHigh, "关闭最高心率提醒", [.value(0)]),
+ (.fetchHeartRate, "获取手表心率参数", []),
+ (.heartRate, "设置手表心率参数", [.heartRate(IBHeartRate())]),
+ (.currentHeartRate, "获取实时心率", []),
+
+ (.fetchBloodOxygen, "获取手表血氧设置", []),
+ (.bloodOxygen, "设置手表血氧参数", [.bloodOxygen(IBBloodOxygen())]),
+ (.trainFrequency, "设置训练实时数据采样频率", [.value(30)]),
+
+ (.fetchTargetStep, "获取目标步数设置", []),
+ (.targetStep, "设置目标步数", [.targetStep(IBTargetStep())]),
+ (.fetchOtherTarget, "获取其他目标设置", []),
+ (.targetOther, "设置其他目标", [.otherTarget(IBOtherTarget())]),
+ (.trainTarget, "设置训练目标", [.trainTarget(IBTrainTarget())]),
+ (.fetchSleep, "获取睡眠设置", []),
+ (.sleep, "设置睡眠", [.sleep(IBSleep())]),
+ (.fetchWater, "获取手表喝水提醒设置", []),
+ (.water, "设置喝水提醒", [.water(IBWater())]),
+
+
+ (.syncHeartRate, "同步心率历史数据", [.value(30)]),
+ (.syncBloodOxygen, "同步血氧历史数据", [.value(30)]),
+ (.syncStepCount, "同步历史步数数据", [.value(30)]),
+ (.syncFatBurning, "同步燃脂时间历史数据", [.value(30)]),
+ (.syncStand, "同步站立历史数据", [.value(30)]),
+ (.syncCalorie, "同步卡路里历史数据", [.value(30)]),
+ (.syncDistance, "同步距离历史数据", [.value(30)]),
+ (.syncTrain, "同步基础训练历史数据", [.value(30)]),
+ (.syncGPS, "同步路径坐标集历史数据", [.value(1654012800)]),
+ (.sendSyncEnd, "发送同步数据结束消息", []),
+ (.addressBook, "发送通讯录", [.addressBook([IBAddressBook()])]),
+
+// (.epo, "发送EPO文件", []),
+ (.ota, "发送OTA文件", []),
+ (.mp3, "发送MP3文件", []),
+// (.dial, "发送自定义表盘文件", []),
+ (.dial, "发送市场表盘文件", []),
+ (.revokeFileSend, "撤销当前文件传输", []),
+
+ (.deleteFile, "删除指定文件", [.deleteFile(IBDeleteFile())]),
+ (.deleteFiles, "删除指定类型所有文件", [.file(.mp3)]),
+ (.fetchFiles, "获取指定类型所有文件", [.file(.mp3)]),
+
+ (.sendWeather, "发送天气数据", [.weather(IBWeather())]),
+]
+
+func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ let data = datas[indexPath.row]
+ if data.label.isEqual("发送OTA文件"){
+ self.sendOta()
+ }
+ else if data.label.isEqual("发送MP3文件"){
+ self.sendMp3()
+ }
+ else if data.label.isEqual("发送EPO文件"){
+ self.sendEpo()
+ }
+ else if data.label.isEqual("发送自定义表盘文件"){
+ self.sendCustomDial()
+ }
+ else if data.label.isEqual("发送市场表盘文件"){
+ self.sendMarketDial()
+ }
+ else if data.label.isEqual("撤销当前文件传输"){
+ self.revokeFileSend()
+ }
+ else{
+ activityIndicatorView.startAnimating()
+ IBSender()
+ .ability(data.ability)
+ .parameters(data.parameters)
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ //请求成功
+ }
+ else{
+ //请求失败
+ }
+ print(result)
+ activityIndicatorView.stopAnimating()
+ }
+ }
+}
+
+ func sendOta(){
+ guard let path = Bundle.main.path(forResource: "20240607_092332_2_20240607_110430_ota", ofType: "fwpkg") else {return}
+ let info = IBSendOta()
+ info.major = 2
+ info.mid = 1
+ info.minor = 4
+ info.isForced = 0
+ info.filePath = path
+
+ activityIndicatorView.startAnimating()
+ IBFileSender().ota(info).progress({ total, current in
+ print("\(total) : \(current)")
+ }).response {[weak self] result in
+ print(result)
+ self?.activityIndicatorView.stopAnimating()
+ }
+ }
+ func sendMp3(){
+ guard let path = Bundle.main.path(forResource: "大约在冬季", ofType: "mp3") else {return}
+ let info = IBSendMp3()
+ info.name = "大约在冬季.mp3"
+ info.filePath = path
+
+ activityIndicatorView.startAnimating()
+ IBFileSender().mp3(info).progress({ total, current in
+ print("\(total) : \(current)")
+ }).response {[weak self] result in
+ print(result)
+ self?.activityIndicatorView.stopAnimating()
+ }
+ }
+ func sendEpo(){
+
+ }
+ func sendCustomDial(){
+ //自定义表盘目前硬件端还不支持
+// guard let path = Bundle.main.path(forResource: "galaxy", ofType: "lzma") else {return}
+// let file = IBDialFile()
+// file.filePath = path
+// file.fileName = "galaxy.lzma"
+//
+// let info = IBDial()
+// info.UID = 0
+// info.files = [file]
+//
+// info.batteryStyle = 1
+// info.timeStyle = 1
+// info.timeColor = 1
+//
+// activityIndicatorView.startAnimating()
+// IBFileSender().dial(info).progress({ total, current in
+// print("\(total) : \(current)")
+// }).response {[weak self] result in
+// print(result)
+// self?.activityIndicatorView.stopAnimating()
+// }
+ }
+
+ //目前只支持表盘市场
+ func sendMarketDial(){
+ guard let path = Bundle.main.path(forResource: "galaxy", ofType: "lzma") else {return}
+ let file = IBDialFile()
+ file.filePath = path
+ file.fileName = "galaxy.lzma"
+
+ let info = IBDial()
+ info.UID = 1000
+ info.files = [file]
+
+ info.theme = "黑暗"
+
+ activityIndicatorView.startAnimating()
+ IBFileSender().dial(info).progress({ total, current in
+ print("\(total) : \(current)")
+ }).response {[weak self] result in
+ print(result)
+ self?.activityIndicatorView.stopAnimating()
+ }
+ }
+
+ func revokeFileSend(){
+ IBFileSender().revokeFileSend(){ result in
+ print(result)
+ }
+ }
+```
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let data = datas[indexPath.row]
- if data.label.isEqual("mp3"){
-// IBFileSender().sendMp3(URL(fileURLWithPath: ""))
+ if data.label.isEqual("发送OTA文件"){
+ self.sendOta()
+ }
+ else if data.label.isEqual("发送MP3文件"){
+ self.sendMp3()
+ }
+ else if data.label.isEqual("发送EPO文件"){
+ self.sendEpo()
+ }
+ else if data.label.isEqual("发送自定义表盘文件"){
+ self.sendCustomDial()
+ }
+ else if data.label.isEqual("发送市场表盘文件"){
+ self.sendMarketDial()
+ }
+ else if data.label.isEqual("撤销当前文件传输"){
+ self.revokeFileSend()
}
else{
activityIndicatorView.startAnimating()
.parameters(data.parameters)
.response {[weak self] result in
guard let self = self else {return}
- if result.isSuccess{
- //请求成功
- }
- else{
- //请求失败
- }
print(result)
activityIndicatorView.stopAnimating()
}
}
}
+}
+extension AbilityViewController{
+ func sendOta(){
+ guard let path = Bundle.main.path(forResource: "20240604_155433_2_V1.0.1_20240618_113111_ota", ofType: "fwpkg") else {return}
+ let info = IBSendOta()
+ info.major = 2
+ info.mid = 1
+ info.minor = 4
+ info.isForced = 0
+ info.filePath = path
+
+ activityIndicatorView.startAnimating()
+ IBFileSender().ota(info).progress({ total, current in
+ print("\(total) : \(current)")
+ }).response {[weak self] result in
+ print(result)
+ self?.activityIndicatorView.stopAnimating()
+ }
+ }
+ func sendMp3(){
+ guard let path = Bundle.main.path(forResource: "大约在冬季", ofType: "mp3") else {return}
+ let info = IBSendMp3()
+ info.name = "大约在冬季.mp3"
+ info.filePath = path
+
+ activityIndicatorView.startAnimating()
+ IBFileSender().mp3(info).progress({ total, current in
+ print("\(total) : \(current)")
+ }).response {[weak self] result in
+ print(result)
+ self?.activityIndicatorView.stopAnimating()
+ }
+ }
+ func sendEpo(){
+
+ }
+ func sendCustomDial(){
+ //自定义表盘目前硬件端还不支持
+// guard let path = Bundle.main.path(forResource: "galaxy", ofType: "lzma") else {return}
+// let file = IBDialFile()
+// file.filePath = path
+// file.fileName = "galaxy.lzma"
+//
+// let info = IBDial()
+// info.UID = 0
+// info.files = [file]
+//
+// info.batteryStyle = 1
+// info.timeStyle = 1
+// info.timeColor = 1
+//
+// activityIndicatorView.startAnimating()
+// IBFileSender().dial(info).progress({ total, current in
+// print("\(total) : \(current)")
+// }).response {[weak self] result in
+// print(result)
+// self?.activityIndicatorView.stopAnimating()
+// }
+ }
+ //目前只支持表盘市场
+ func sendMarketDial(){
+ guard let path = Bundle.main.path(forResource: "galaxy", ofType: "lzma") else {return}
+ let file = IBDialFile()
+ file.filePath = path
+ file.fileName = "galaxy.lzma"
+
+ let info = IBDial()
+ info.UID = 10001
+ info.files = [file]
+
+ info.theme = "黑暗"
+
+ activityIndicatorView.startAnimating()
+ IBFileSender().dial(info).progress({ total, current in
+ print("\(total) : \(current)")
+ }).response {[weak self] result in
+ print(result)
+ self?.activityIndicatorView.stopAnimating()
+ }
+ }
+
+ func revokeFileSend(){
+ IBFileSender().revokeFileSend(){ result in
+ print(result)
+ }
+ }
}
/// 数据包索引
let index = 1
- var bytes = [0x73, 0x01, 0x07] + packageCount.byte4 + dataTotalLength.byte4 + index.byte4 + dataLength.byte2
+ let bytes = [0x73, 0x01, 0x07] + packageCount.byte4 + dataTotalLength.byte4 + index.byte4 + dataLength.byte2
dataPacket = bytes + data + [0x00]
(.setClock, "发送闹钟设置", [.clock(IBClock())]),
(.fetchCurrentDialId, "发送当前表盘ID", []),
- (.switchDial, "切换表盘", [.dialID(12)]),
- (.deleteDial, "删除表盘", [.dialID(12)]),
+ (.switchDial, "切换表盘", [.dialID(26112)]),
+ (.deleteDial, "删除表盘", [.dialID(7168)]),
(.fetchDials, "获取所有表盘UID", []),
(.checkDialID, "获取某表盘ID是否已安装", [.dialID(12)]),
(.sendSyncEnd, "发送同步数据结束消息", []),
(.addressBook, "发送通讯录", [.addressBook([IBAddressBook()])]),
- (.epo, "发送EPO文件信息", [.epo(IBEpo())]),
- (.ota, "发送OTA文件信息", [.ota(IBOta())]),
- (.mp3, "发送MP3文件信息", [.MP3(IBMp3())]),
- (.customDial, "发送自定义表盘文件汇总信息", [.customDial(IBCustomDial())]),
- (.marketDial, "发送市场表盘文件汇总信息", [.marketDial(IBMarketDial())]),
- (.dialInfo, "发送单个表盘文件信息", [.dialInfo(IBDialInfo())]),
- (.fileCheckCode, "发送文件校验码", [.value(10)]),
+// (.epo, "发送EPO文件", []),
+ (.ota, "发送OTA文件", []),
+ (.mp3, "发送MP3文件", []),
+// (.dial, "发送自定义表盘文件", []),
+ (.dial, "发送市场表盘文件", []),
+ (.revokeFileSend, "撤销当前文件传输", []),
- (.revokeFileSend, "撤销当前文件传输", [.file(.mp3)]),
(.deleteFile, "删除指定文件", [.deleteFile(IBDeleteFile())]),
(.deleteFiles, "删除指定类型所有文件", [.file(.mp3)]),
-
+ (.fetchFiles, "获取指定类型所有文件", [.file(.mp3)]),
(.sendWeather, "发送天气数据", [.weather(IBWeather())]),
]
}
@IBAction func startScan(_ sender: UIButton) {
+// IBFileSender().readFile()
+// return
title = "正在扫描蓝牙设备..."
IBManager.scan { [unowned self] peripheral in
if let _ = datas.filter({ $0.uuid == peripheral.uuid }).last {
180B01982BFC831300524729 /* IBHistoryHeartRate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180B01972BFC831300524729 /* IBHistoryHeartRate.swift */; };
180B019E2BFC9CF400524729 /* IBHistoryBaseTraining.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180B019D2BFC9CF400524729 /* IBHistoryBaseTraining.swift */; };
180CE2652C0D728F00A7006E /* IBAddressBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180CE2642C0D728F00A7006E /* IBAddressBook.swift */; };
+ 18113B2F2C13195900E04997 /* galaxy.lzma in Resources */ = {isa = PBXBuildFile; fileRef = 18113B2E2C13195900E04997 /* galaxy.lzma */; };
+ 18113B312C1400D700E04997 /* 黄沙-名决.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 18113B302C1400D700E04997 /* 黄沙-名决.mp3 */; };
+ 18113B332C1406CB00E04997 /* 大约在冬季.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 18113B322C1406CB00E04997 /* 大约在冬季.mp3 */; };
181441CF2C11571400CCDD46 /* IBMp3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181441CE2C11571400CCDD46 /* IBMp3.swift */; };
181441D12C11575D00CCDD46 /* IBOta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181441D02C11575D00CCDD46 /* IBOta.swift */; };
181441D32C115AC500CCDD46 /* IBFileSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181441D22C115AC500CCDD46 /* IBFileSender.swift */; };
181441D52C116AE500CCDD46 /* IBDial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181441D42C116AE500CCDD46 /* IBDial.swift */; };
+ 18204C002C199B8600F94E69 /* IBFileSender+Mp3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18204BFF2C199B8600F94E69 /* IBFileSender+Mp3.swift */; };
+ 18204C022C199D0E00F94E69 /* IBFileSender+Ota.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18204C012C199D0E00F94E69 /* IBFileSender+Ota.swift */; };
+ 18204C042C199E7800F94E69 /* IBFileSender+Dial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18204C032C199E7800F94E69 /* IBFileSender+Dial.swift */; };
+ 1839DBD02C14442200E33B6E /* IBFetchFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1839DBCF2C14442200E33B6E /* IBFetchFile.swift */; };
184431322BF3720E00546E4E /* IBOtherTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184431312BF3720E00546E4E /* IBOtherTarget.swift */; };
1849DC8F2BEC8007002C2D2D /* IBVibrateSound.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1849DC8E2BEC8007002C2D2D /* IBVibrateSound.swift */; };
1849DC912BECD131002C2D2D /* IBCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1849DC902BECD131002C2D2D /* IBCode.swift */; };
184B94CB2BF606CC0042BEB3 /* IBTrainTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184B94CA2BF606CC0042BEB3 /* IBTrainTarget.swift */; };
1859DFA02BFF5A2700112E8C /* IBSendTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1859DF9F2BFF5A2700112E8C /* IBSendTime.swift */; };
+ 185E9C3B2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg in Resources */ = {isa = PBXBuildFile; fileRef = 185E9C3A2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg */; };
18884C442C11DBA300E01458 /* IBDeleteFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18884C432C11DBA300E01458 /* IBDeleteFile.swift */; };
18884C462C12A4EA00E01458 /* IBEpo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18884C452C12A4EA00E01458 /* IBEpo.swift */; };
18993AA22C073FED006742D2 /* IBBloodOxygen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18993AA12C073FED006742D2 /* IBBloodOxygen.swift */; };
180B01972BFC831300524729 /* IBHistoryHeartRate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryHeartRate.swift; sourceTree = "<group>"; };
180B019D2BFC9CF400524729 /* IBHistoryBaseTraining.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryBaseTraining.swift; sourceTree = "<group>"; };
180CE2642C0D728F00A7006E /* IBAddressBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAddressBook.swift; sourceTree = "<group>"; };
+ 18113B2E2C13195900E04997 /* galaxy.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = galaxy.lzma; sourceTree = "<group>"; };
+ 18113B302C1400D700E04997 /* 黄沙-名决.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "黄沙-名决.mp3"; sourceTree = "<group>"; };
+ 18113B322C1406CB00E04997 /* 大约在冬季.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "大约在冬季.mp3"; sourceTree = "<group>"; };
181441CE2C11571400CCDD46 /* IBMp3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBMp3.swift; sourceTree = "<group>"; };
181441D02C11575D00CCDD46 /* IBOta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBOta.swift; sourceTree = "<group>"; };
181441D22C115AC500CCDD46 /* IBFileSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBFileSender.swift; sourceTree = "<group>"; };
181441D42C116AE500CCDD46 /* IBDial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBDial.swift; sourceTree = "<group>"; };
+ 18204BFF2C199B8600F94E69 /* IBFileSender+Mp3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Mp3.swift"; sourceTree = "<group>"; };
+ 18204C012C199D0E00F94E69 /* IBFileSender+Ota.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Ota.swift"; sourceTree = "<group>"; };
+ 18204C032C199E7800F94E69 /* IBFileSender+Dial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Dial.swift"; sourceTree = "<group>"; };
+ 1839DBCF2C14442200E33B6E /* IBFetchFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBFetchFile.swift; sourceTree = "<group>"; };
184431312BF3720E00546E4E /* IBOtherTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBOtherTarget.swift; sourceTree = "<group>"; };
1849DC8E2BEC8007002C2D2D /* IBVibrateSound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBVibrateSound.swift; sourceTree = "<group>"; };
1849DC902BECD131002C2D2D /* IBCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBCode.swift; sourceTree = "<group>"; };
184B94CA2BF606CC0042BEB3 /* IBTrainTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTrainTarget.swift; sourceTree = "<group>"; };
1859DF9F2BFF5A2700112E8C /* IBSendTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBSendTime.swift; sourceTree = "<group>"; };
+ 185E9C3A2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg */ = {isa = PBXFileReference; lastKnownFileType = file; path = 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg; sourceTree = "<group>"; };
18884C432C11DBA300E01458 /* IBDeleteFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBDeleteFile.swift; sourceTree = "<group>"; };
18884C452C12A4EA00E01458 /* IBEpo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBEpo.swift; sourceTree = "<group>"; };
18993AA12C073FED006742D2 /* IBBloodOxygen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBBloodOxygen.swift; sourceTree = "<group>"; };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 18113B292C1318FD00E04997 /* Resource */ = {
+ isa = PBXGroup;
+ children = (
+ 18113B322C1406CB00E04997 /* 大约在冬季.mp3 */,
+ 18113B302C1400D700E04997 /* 黄沙-名决.mp3 */,
+ 185E9C3A2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg */,
+ 18113B2E2C13195900E04997 /* galaxy.lzma */,
+ );
+ path = Resource;
+ sourceTree = "<group>";
+ };
8A0A39B12B980BEF006A3B18 = {
isa = PBXGroup;
children = (
8A878FE12BA9444C008C7F26 /* IBParameter.swift */,
8A79D2242BA4278E0096A98C /* IBSender.swift */,
181441D22C115AC500CCDD46 /* IBFileSender.swift */,
+ 18204BFF2C199B8600F94E69 /* IBFileSender+Mp3.swift */,
+ 18204C012C199D0E00F94E69 /* IBFileSender+Ota.swift */,
+ 18204C032C199E7800F94E69 /* IBFileSender+Dial.swift */,
8A79D2262BA433330096A98C /* IBNotify.swift */,
8A9F6AC02BA04BE100DA520F /* IBManager.swift */,
8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */,
8A1DFF502B9EB4F800B735FF /* Example */ = {
isa = PBXGroup;
children = (
+ 18113B292C1318FD00E04997 /* Resource */,
8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */,
8A1DFF552B9EB4F800B735FF /* ViewController.swift */,
8A34B9262BA2AB66000F66BD /* ConnectViewController.swift */,
181441D42C116AE500CCDD46 /* IBDial.swift */,
18884C452C12A4EA00E01458 /* IBEpo.swift */,
18884C432C11DBA300E01458 /* IBDeleteFile.swift */,
+ 1839DBCF2C14442200E33B6E /* IBFetchFile.swift */,
1849DC902BECD131002C2D2D /* IBCode.swift */,
);
path = Models;
buildActionMask = 2147483647;
files = (
8A1DFF5E2B9EB4F900B735FF /* LaunchScreen.storyboard in Resources */,
+ 18113B312C1400D700E04997 /* 黄沙-名决.mp3 in Resources */,
8A1DFF5B2B9EB4F900B735FF /* Assets.xcassets in Resources */,
8A1DFF592B9EB4F900B735FF /* Main.storyboard in Resources */,
+ 185E9C3B2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg in Resources */,
+ 18113B332C1406CB00E04997 /* 大约在冬季.mp3 in Resources */,
+ 18113B2F2C13195900E04997 /* galaxy.lzma in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
8A79D2292BA43F5A0096A98C /* IBResponse.swift in Sources */,
189C15932C05FFA600676A80 /* IBHistoryStand.swift in Sources */,
1849DC8F2BEC8007002C2D2D /* IBVibrateSound.swift in Sources */,
+ 18204C042C199E7800F94E69 /* IBFileSender+Dial.swift in Sources */,
180CE2652C0D728F00A7006E /* IBAddressBook.swift in Sources */,
8A878FE22BA9444C008C7F26 /* IBParameter.swift in Sources */,
184431322BF3720E00546E4E /* IBOtherTarget.swift in Sources */,
8A79D2272BA433330096A98C /* IBNotify.swift in Sources */,
8A0A39D42B980E93006A3B18 /* Any.swift in Sources */,
1849DC912BECD131002C2D2D /* IBCode.swift in Sources */,
+ 1839DBD02C14442200E33B6E /* IBFetchFile.swift in Sources */,
8A34B92D2BA2FA02000F66BD /* IBManufacturer.swift in Sources */,
184B94CB2BF606CC0042BEB3 /* IBTrainTarget.swift in Sources */,
+ 18204C002C199B8600F94E69 /* IBFileSender+Mp3.swift in Sources */,
8A79D2252BA4278E0096A98C /* IBSender.swift in Sources */,
18C9589D2C08600C006A5DE8 /* IBHistoryGps.swift in Sources */,
189C15912C05FBA100676A80 /* IBHistoryBurning.swift in Sources */,
189C15952C05FFC700676A80 /* IBHistoryCalorie.swift in Sources */,
181441CF2C11571400CCDD46 /* IBMp3.swift in Sources */,
8A9F6AC12BA04BE100DA520F /* IBManager.swift in Sources */,
+ 18204C022C199D0E00F94E69 /* IBFileSender+Ota.swift in Sources */,
18EC6E282BFF4C6D00757CA8 /* IBPhoneInfo.swift in Sources */,
18C9589F2C086A9D006A5DE8 /* IBHistorySleep.swift in Sources */,
8A65F95F2BABEC4D005C1EA4 /* IBClock.swift in Sources */,
uuid = "3E7E940C-0C97-40F4-A990-DDBAB66B37E1"
type = "1"
version = "2.0">
+ <Breakpoints>
+ <BreakpointProxy
+ BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+ <BreakpointContent
+ uuid = "1A482552-C041-4A0B-82F9-1374859DDB57"
+ shouldBeEnabled = "Yes"
+ ignoreCount = "0"
+ continueAfterRunningActions = "No"
+ filePath = "IOTBluetooth/IBChecker.swift"
+ startingColumnNumber = "9223372036854775807"
+ endingColumnNumber = "9223372036854775807"
+ startingLineNumber = "335"
+ endingLineNumber = "335"
+ landmarkName = "unknown"
+ landmarkType = "0">
+ </BreakpointContent>
+ </BreakpointProxy>
+ </Breakpoints>
</Bucket>
// MARK: - 文件
/// 发送MP3文件信息
- /// - Parameter mp3: see ``IBMp3``
+ /// - Parameter mp3: see ``IBMp3File``
static let mp3 = IBAbility.file.id(0x21).parser(IBGeneralParser.self).checker(IBMp3Checker.self)
- /// 发送自定义表盘文件汇总信息
- /// - Parameter customDial: see ``IBCustomDail``
- static let customDial = IBAbility.file.id(0x22).parser(IBGeneralParser.self).checker(IBCustomDialChecker.self)
+// /// 发送自定义表盘文件汇总信息
+// /// - Parameter customDial: see ``IBCustomDial``
+// static let customDial = IBAbility.file.id(0x22).parser(IBGeneralParser.self).checker(IBCustomDialChecker.self)
- /// 发送市场表盘文件汇总信息
- /// - Parameter marketDial: see ``IBMarketDail``
- static let marketDial = IBAbility.file.id(0x22).parser(IBGeneralParser.self).checker(IBMarketDialChecker.self)
+ /// 发送表盘文件汇总信息
+ /// - Parameter dial: see ``IBDial``
+ static let dial = IBAbility.file.id(0x22).parser(IBGeneralParser.self).checker(IBDialChecker.self)
/// 发送单个表盘文件信息
- /// - Parameter dialInfo: see ``IBDailInfo``
- static let dialInfo = IBAbility.file.id(0x24).parser(IBGeneralParser.self).checker(IBDialInfoChecker.self)
+ /// - Parameter dialFile: see ``IBDailFile``
+ internal static let dialFile = IBAbility.file.id(0x24).parser(IBGeneralParser.self).checker(IBDialFileChecker.self)
/// 发送文件校验码
/// - Parameter value:Int
- static let fileCheckCode = IBAbility.file.id(0x23).parser(IBGeneralParser.self).checker(IBValueChecker.self)
+ internal static let fileCheckCode = IBAbility.file.id(0x23).parser(IBGeneralParser.self).checker(IBValueChecker.self)
/// 删除指定文件
/// - Parameter deleteFile: see ``IBDeleteFile``
static let deleteFiles = IBAbility.short.id(0x22).parser(IBGeneralParser.self).checker(IBFileChecker.self)
/// 发送OTA文件信息
- /// - Parameter ota: see ``IBOta``
+ /// - Parameter ota: see ``IBOtaFile``
static let ota = IBAbility.file.id(0x20).parser(IBGeneralParser.self).checker(IBOtaChecker.self)
/// 发送EPO文件信息
- /// - Parameter epo: see ``IBEpo``
- static let epo = IBAbility.file.id(0x28).parser(IBGeneralParser.self).checker(IBEpoChecker.self)
+ /// - Parameter epo: see ``[IBEpo]``
+ internal static let epo = IBAbility.file.id(0x28).parser(IBGeneralParser.self).checker(IBEpoChecker.self)
/// 撤销当前文件传输
/// - Parameter file: see ``IBFile``
static let revokeFileSend = IBAbility.short.id(0x20).parser(IBGeneralParser.self).checker(IBFileChecker.self)
- // /// 获取指定类型所有文件(当前只支持 MP3 和 表盘)
- // static let fetchFiles = IBAbility.short.id(0x21).parser(IBGeneralParser.self).checker(IBFileChecker.self)
+ /// 获取指定类型所有文件(当前只支持 MP3 和 表盘)
+ /// - Parameter file: see ``IBFile``
+ /// - Returns: IBFetchFile \
+ /// Read from ``IBResponse``.value
+ static let fetchFiles = IBAbility.short.id(0x21).parser(IBFetchFilesParser.self).checker(IBFileChecker.self)
}
extension IBAbility {
return data.bytes
}
}
-struct IBEpoChecker: IBChecker {
- static func check(_ parameters: [IBParameter]) -> [UInt8] {
- guard case let .epo(value) = parameters[.epo] else { fatalError("缺少参数: epo") }
- guard let data = value.encode else { fatalError("json解析异常: epo") }
- Logger.log(value.jsonString ?? "")
- return data.bytes
- }
-}
struct IBTargetStepChecker: IBChecker {
static func check(_ parameters: [IBParameter]) -> [UInt8] {
return data.bytes
}
}
-struct IBCustomDialChecker: IBChecker {
+struct IBEpoChecker: IBChecker {
static func check(_ parameters: [IBParameter]) -> [UInt8] {
- guard case let .customDial(value) = parameters[.customDial] else { fatalError("缺少参数: customDial") }
- Logger.log(value.description)
- let data = value.UID.byte2 + [value.batteryStyle.byte] + [value.timeStyle.byte] + [value.timeColor.byte] + [value.numberOfFiles.byte]
+ guard case let .epo(list) = parameters[.epo] else { fatalError("缺少参数: epo") }
+ var data = [list.count.byte]
+ var des = "[ "
+ for item in list {
+ des += item.description
+ data += [item.UID.byte]
+ if let name = item.fileName.data(using: .utf8),!name.isEmpty {
+ data += [name.count.byte]
+ data += name
+ }
+ data += [item.fileSize.byte]
+ }
+ des += " ]"
+ Logger.log(des)
return data
- }
-}
-struct IBMarketDialChecker: IBChecker {
- static func check(_ parameters: [IBParameter]) -> [UInt8] {
- guard case let .marketDial(value) = parameters[.marketDial] else { fatalError("缺少参数: marketDial") }
+
+ }
+}
+
+//struct IBCustomDialChecker: IBChecker {
+// static func check(_ parameters: [IBParameter]) -> [UInt8] {
+// guard case let .customDial(value) = parameters[.customDial] else { fatalError("缺少参数: customDial") }
+// Logger.log(value.description)
+// var data = value.UID.byte2 + [value.batteryStyle.byte] + [value.timeStyle.byte] + [value.timeColor.byte]
+// if let list = value.files{
+// data += [list.count.byte]
+// }
+// else{
+// data += [0.byte]
+// }
+// return data
+// }
+//}
+struct IBDialChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .dial(value) = parameters[.dial] else { fatalError("缺少参数: dial") }
Logger.log(value.description)
var data = value.UID.byte2
if let name = value.theme?.data(using: .utf8),!name.isEmpty {
else{
data += [0.byte]
}
- data += [value.numberOfFiles.byte]
+ if let list = value.files{
+ data += [list.count.byte]
+ }
+ else{
+ data += [0.byte]
+ }
return data
}
}
-struct IBDialInfoChecker: IBChecker {
+struct IBDialFileChecker: IBChecker {
static func check(_ parameters: [IBParameter]) -> [UInt8] {
- guard case let .dialInfo(value) = parameters[.dialInfo] else { fatalError("缺少参数: marketDial") }
+ guard case let .dialFile(value) = parameters[.dialFile] else { fatalError("缺少参数: dialFile") }
Logger.log(value.description)
- var data = value.UID.byte2 + [value.number.byte] + [value.fileType.byte]
+ var data = value.UID.byte2 + [value.index.byte] + [value.fileType.byte]
if let name = value.fileName.data(using: .utf8),!name.isEmpty {
data += [name.count.byte]
data += name
else{
data += [0.byte]
}
- data += value.fizeSize.byte4
+ data += value.size.byte4
return data
}
}
static func check(_ parameters: [IBParameter]) -> [UInt8] {
guard case let .deleteFile(value) = parameters[.deleteFile] else { fatalError("缺少参数: deleteFile") }
Logger.log(value.description)
- var data = [value.uids.count.byte] + [value.fileType.byte]
+ var data = [value.uids.count.byte] + [value.fileType.rawValue.byte]
for uid in value.uids {
data += uid.byte2
}
--- /dev/null
+//
+// IBFileSender+Dial.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/12.
+//
+
+import Foundation
+extension IBFileSender{
+// func sendDial(_ info:IBCustomDial){
+// IBSender()
+// .ability(.customDial)
+// .parameters([.customDial(info)])
+// .response {[weak self] result in
+// guard let self = self else {return}
+// if result.isSuccess{
+// loadFile(info)
+// }
+// else{
+// completion(result)
+// self.remove()
+// }
+// }
+// }
+
+// private func loadFile(_ info:IBCustomDial){
+// guard let files = info.files else {
+// completion(.response.code(.success))
+// self.remove()
+// return
+// }
+//
+// var dataList:[IBFileSenderContent] = []
+// for info in files{
+// let (list,size,lrc) = readFile(from: info.filePath)
+// if let list = list {
+// info.size = size
+// total += info.size
+// dataList.append(IBFileSenderContent(dataList: list,lrc: lrc,dialFile: info))
+// }
+// else{
+// completion(.response.code(.failure).message("读取文件失败"))
+// self.remove()
+// return
+// }
+// }
+// self.fileList = dataList
+// sendDialFileInfo()
+// }
+ private func sendDialFileInfo(){
+ guard let file = self.fileList.first,let info = file.dialFile else {
+ completion(.response.code(.success))
+ self.remove()
+ return;
+ }
+ IBSender()
+ .ability(.dialFile)
+ .parameters([.dialFile(info)])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ self.sendContentDial()
+ }
+ else{
+ completion(result)
+ self.remove()
+ }
+ }
+ }
+
+ private func sendContentDial(){
+ if cancelSend {
+ Logger.log("已取消发送")
+ self.remove()
+ return
+ }
+
+ if let file = fileList.first,let data = file.dataList.first{
+ file.dataList.removeFirst()
+ IBBluetooth.shared.fileSender = self
+ let model = IBFileSenderData()
+ model.data = data
+ model.fileWriteHander = { [weak self] result in
+ guard let self = self else { return }
+ self.lastData = nil
+ if result.isSuccess {
+ self.current += data.count
+ self.progress?(self.total,self.current)
+ self.sendContentDial()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件超时,发送失败!"))
+ self.remove()
+ }
+ }
+ self.lastData = model
+ IBBluetooth.shared.send(fileData: data)
+ }
+ else{
+ Logger.log( "文件内容发送结束...")
+ startTimer()
+ }
+ }
+
+ func sendDialLrc(){
+ IBSender()
+ .ability(.fileCheckCode)
+ .parameters([.value(fileList.first!.lrc)])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ self.fileList.removeFirst()
+ self.sendDialFileInfo()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件校验码失败"))
+ self.remove()
+ }
+ }
+ }
+}
+
+extension IBFileSender{
+ func sendDial(_ info:IBDial){
+ IBSender()
+ .ability(.dial)
+ .parameters([.dial(info)])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ loadFile(info)
+ }
+ else{
+ completion(result)
+ self.remove()
+ }
+ }
+ }
+
+ private func loadFile(_ info:IBDial){
+ guard let files = info.files else {
+ completion(.response.code(.success))
+ self.remove()
+ return
+ }
+
+ var dataList:[IBFileSenderContent] = []
+ for info in files{
+ let (list,size,lrc) = readFile(from: info.filePath)
+ if let list = list {
+ info.size = size
+ total += info.size
+ dataList.append(IBFileSenderContent(dataList: list,lrc: lrc,dialFile: info))
+ }
+ else{
+ completion(.response.code(.failure).message("读取文件失败"))
+ self.remove()
+ return
+ }
+ }
+ self.fileList = dataList
+ sendDialFileInfo()
+ }
+}
--- /dev/null
+//
+// IBFileSender+Mp3.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/12.
+//
+
+import Foundation
+
+extension IBFileSender{
+ func sendMp3(_ info:IBSendMp3){
+ let (list,size,lrc) = readFile(from: info.filePath)
+ if let list = list {
+ info.size = size
+ total = info.size
+ let content = IBFileSenderContent(dataList: list,lrc: lrc)
+ self.fileList = [content]
+
+ IBSender()
+ .ability(.mp3)
+ .parameters([.MP3(IBMp3File(data:info))])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ self.sendContentMp3()
+ }
+ else{
+ completion(result)
+ self.remove()
+ }
+ }
+ }
+ else{
+ completion(.response.code(.failure).message("读取文件失败"))
+ self.remove()
+ }
+ }
+ private func sendContentMp3(){
+ if cancelSend {
+ Logger.log("已取消发送")
+ self.remove()
+ return
+ }
+
+ if let file = fileList.first,let data = file.dataList.first{
+ file.dataList.removeFirst()
+ IBBluetooth.shared.fileSender = self
+ let model = IBFileSenderData()
+ model.data = data
+ model.fileWriteHander = { [weak self] result in
+ guard let self = self else { return }
+ self.lastData = nil
+ if result.isSuccess {
+ self.current += data.count
+ self.progress?(self.total,self.current)
+ self.sendContentMp3()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件超时,发送失败!"))
+ self.remove()
+ }
+ }
+ self.lastData = model
+ IBBluetooth.shared.send(fileData: data)
+ }
+ else{
+ Logger.log( "文件内容发送结束...")
+ startTimer()
+ }
+ }
+ func sendMp3Lrc(){
+ IBSender()
+ .ability(.fileCheckCode)
+ .parameters([.value(fileList.first!.lrc)])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ completion(result)
+ self.remove()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件校验码失败"))
+ self.remove()
+ }
+ }
+ }
+}
--- /dev/null
+//
+// IBSender+Ota.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/12.
+//
+
+import Foundation
+extension IBFileSender{
+ func sendOta(_ info:IBSendOta){
+ let (list,size,lrc) = readFile(from: info.filePath)
+ if let list = list {
+ info.size = size
+ total = info.size
+ let content = IBFileSenderContent(dataList: list,lrc: lrc)
+ self.fileList = [content]
+
+ IBSender()
+ .ability(.ota)
+ .parameters([.ota(IBOtaFile(data:info))])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ self.sendContentOta()
+ }
+ else{
+ completion(result)
+ self.remove()
+ }
+ }
+ }
+ else{
+ completion(.response.code(.failure).message("读取文件失败"))
+ self.remove()
+ }
+ }
+ private func sendContentOta(){
+ if cancelSend {
+ Logger.log("已取消发送")
+ self.remove()
+ return
+ }
+
+ if let file = fileList.first,let data = file.dataList.first{
+ file.dataList.removeFirst()
+ IBBluetooth.shared.fileSender = self
+ let model = IBFileSenderData()
+ model.data = data
+ model.fileWriteHander = { [weak self] result in
+ guard let self = self else { return }
+ self.lastData = nil
+ if result.isSuccess {
+ self.current += data.count
+ self.progress?(self.total,self.current)
+ self.sendContentOta()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件超时,发送失败!"))
+ self.remove()
+ }
+ }
+ self.lastData = model
+ IBBluetooth.shared.send(fileData: data)
+ }
+ else{
+ Logger.log( "文件内容发送结束...")
+ startTimer()
+ }
+ }
+ func sendOtaLrc(){
+ IBSender()
+ .ability(.fileCheckCode)
+ .parameters([.value(fileList.first!.lrc)])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ completion(result)
+ self.remove()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件校验码失败"))
+ self.remove()
+ }
+ }
+ }
+}
import Foundation
+public typealias IBProgressHandler = (_ total:Int, _ current:Int) -> Void
+internal typealias IBFileWriteHandler = (_ result:IBResponse) -> Void
+
+class IBFileSenderData {
+ /// 发送的数据
+ var data:Data = Data()
+ /// 文件写入成功回调
+ var fileWriteHander:IBFileWriteHandler?
+}
+
+ class IBFileSenderContent {
+ var dataList:[Data] = []
+ var lrc = 0
+ var dialFile:IBDialFile?
+
+ init(dataList: [Data], lrc: Int = 0, dialFile: IBDialFile? = nil) {
+ self.dataList = dataList
+ self.lrc = lrc
+ self.dialFile = dialFile
+ }
+}
+
+
public class IBFileSender {
+ var completion: IBCompletionHandler!
+ var progress: IBProgressHandler?
+ var otaInfo:IBSendOta?
+ var mp3Info:IBSendMp3?
+ var epoInfo:[IBEpo]?
+// var customInfo:IBCustomDial?
+ var dialInfo:IBDial?
+
+ var fileList:[IBFileSenderContent] = []
+ var total:Int = 0
+ var current:Int = 0
+
+
+ var cancelSend = false
+ var lastData:IBFileSenderData?
public init() {
+ Self.all.append(self)
}
+ deinit {
+ Logger.log("deinit IBFileSender")
+ }
+ func sendFileResult(_ success:Bool ){
+ deinitTimer()
+ if (success){
+ Logger.log("手表接收文件成功")
+ sendFileFinish()
+ }
+ else{
+ completion(.response.code(.failure).message("手表接收文件失败"))
+ self.remove()
+ }
+ }
-// public func sendMp3(_ filePath:URL){
-//// let data = Data(filePath)
-// let model = IBMp3()
-//
-// IBSender()
-// .ability(.mp3)
-// .parameters([.MP3(model)])
-// .response {[weak self] result in
-// guard let self = self else {return}
-// if result.isSuccess{
-// //请求成功
-// }
-// else{
-// //请求失败
-// }
-// print(result)
-//
+ func sendFileFinish(){
+ if let _ = self.mp3Info{
+ sendMp3Lrc()
+ }
+ else if let _ = self.otaInfo {
+ sendOtaLrc()
+ }
+ else if let _ = self.dialInfo{
+ sendDialLrc()
+ }
+// else if let _ = self.marketInfo{
+// sendDialLrc()
+// }
+ // else if let path = self.epoInfo.filePath {
+ // filePath = path
+ // }
+ }
+}
+
+
+
+extension IBFileSender{
+ func start(){
+ if let info = self.mp3Info{
+ self.sendMp3(info)
+ }
+ else if let info = self.otaInfo {
+ self.sendOta(info)
+ }
+ else if let info = self.dialInfo{
+ self.sendDial(info)
+ }
+// else if let info = self.marketInfo{
+// self.sendDial(info)
+// }
+ // else if let path = self.epoInfo.filePath {
+ // filePath = path
+ // }
+ }
+ func readFile(from path:String) ->([Data]?,Int,Int){
+ let fileURL = URL(fileURLWithPath: path)
+ do {
+ let data = try Data(contentsOf:fileURL)
+ let seg = 495
+ var list = [Data]()
+ for i in stride(from: 0, to: data.count, by: seg) {
+ if i+seg < data.count {
+ list.append(data[i ... i+seg - 1])
+ }
+ else{
+ list.append(data[i ... data.count - 1])
+ }
+ }
+ let lrc = LRC.calculateLRC(data.bytes).int
+ return (list,data.count,lrc)
+ } catch {
+ return (nil,0,0)
+ }
+ }
+}
+
+public extension IBFileSender{
+ @discardableResult
+ /// 发送OTA
+ /// - Parameters:
+ /// - filePath: 文件路径
+ /// - info: ota信息
+ /// - Returns: self
+ func ota(_ info:IBSendOta) ->Self{
+ self.otaInfo = info
+ return self
+ }
+ @discardableResult
+
+
+ /// 发送mp3
+ /// - Parameter info: mp3信息
+ /// - Returns: Self
+ func mp3(_ info:IBSendMp3) ->Self{
+ self.mp3Info = info
+ return self
+ }
+
+// /// 发送自定义表盘
+// /// - Parameter info: 表盘信息
+// /// - Returns: Self
+// func customDial(_ info:IBCustomDial) ->Self{
+// if let list = info.files,list.count > 0{
+// for i in 0...list.count - 1 {
+// list[i].index = i
+// list[i].UID = info.UID
// }
+// }
+// self.customInfo = info
+// return self
// }
+ /// 发送表盘
+ /// - Parameter info: 表盘信息
+ /// - Returns: Self
+ func dial(_ info:IBDial) ->Self{
+// IBMarketDial
+
+ if let list = info.files,list.count > 0{
+ for i in 0...list.count - 1{
+ list[i].index = i
+ list[i].UID = info.UID
+ }
+ }
+
+ self.dialInfo = info
+ return self
+ }
+
+ /// 发送EPO文件
+ /// - Parameters:
+ /// - filePath: 文件路径
+ /// - info: epo信息
+ /// - Returns: self
+ // func epo(filePath:URL,info:[IBEpo]) ->Self{
+ // self.filePath = filePath
+ // self.epoInfo = info
+ // return self
+ // }
+
+ /// 进度
+ @discardableResult
+ func progress(_ value: @escaping IBProgressHandler) -> Self {
+ progress = value
+ return self
+ }
+
+ /// 执行与响应处理
+ @discardableResult
+ func response(_ handler: @escaping IBCompletionHandler) -> Self {
+ if Self.all.count > 1{
+ handler(.response.code(.failure).message("当前有文件正在传输中..."))
+ self.remove()
+ return self
+ }
+
+ completion = handler
+ self.start()
+ return self
+ }
+
+
+ /// 撤销当前的文件传输
+ func revokeFileSend(_ handler: @escaping IBCompletionHandler){
+ for item in Self.all{
+ item.cancelSend = true
+ }
+
+ IBSender()
+ .ability(.revokeFileSend)
+ .parameters([.file(.mp3)])
+ .response { result in
+ handler(result)
+ self.remove()
+ }
+ }
+}
+
+extension IBFileSender {
+ static var all: [IBFileSender] = []
+ func remove() {
+ Self.all.removeAll { $0 === self }
+ }
+}
+
+extension IBFileSender {
+ static var timer: Timer?
+ func startTimer() {
+ deinitTimer()
+
+ Self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false, block: { [unowned self] _ in
+ deinitTimer()
+ self.completion(.response.code(.failure).message("发送文件失败"))
+ self.remove()
+ })
+ Logger.log("IBFileSender创建定时器")
+ }
+
+ func deinitTimer() {
+ guard let _ = Self.timer else { return }
+ Self.timer?.invalidate()
+ Self.timer = nil
+
+ Logger.log("IBFileSender销毁定时器")
+
+ }
}
public extension IBManager {
/// SDK 版本号
- static let version: String = "1.0.8"
+ static let version: String = "1.0.11"
/// 默认为 false
static var isDebug: Bool = false {
didSet { Logger.isDebug = isDebug }
/// 获取手表推送的训练实时数据,通过object获取状态IBTrainingData对象
static let trainingData = IBNotify.Name("IOTBluetooth.TrainingData")
+
+ /// 手表发送文件已接收完成
+ internal static let fileReceiveFinish = IBNotify.Name("IOTBluetooth.TileReceiveFinish")
}
extension IBNotify {
post(.weather)
return true
}
-
+ //文件已接收完成
+ if data.bytes[1] == 0x05 && data.bytes[2] == 0x20 {
+ let result = IBGeneralParser.dataPacket(data)
+ IBBluetooth.shared.fileSender?.sendFileResult(result[0].int == 1)
+ return true
+ }
//配对
if data.bytes[1] == 0x05 && data.bytes[2] == 0x11 {
let result = IBGeneralParser.dataPacket(data)
case epo = "epo"
case ota = "ota"
case mp3 = "mp3"
- case customDial = "customDial"
- case marketDial = "marketDial"
- case dialInfo = "dialInfo"
+ case dial = "dial"
+ case dialFile = "dialFile"
case deleteFile = "deleteFile"
+ case fetchFils = "fetchFils"
}
/// 参数
/// 发送通讯录
case addressBook([IBAddressBook])
/// 发送EPO文件信息
- case epo(IBEpo)
+ case epo([IBEpo])
/// 发送OTA文件信息
- case ota(IBOta)
+ case ota(IBOtaFile)
/// 发送mp3文件信息
- case MP3(IBMp3)
- /// 发送自定义表盘信息汇总
- case customDial(IBCustomDial)
- /// 发送表盘市场信息汇总
- case marketDial(IBMarketDial)
+ case MP3(IBMp3File)
+ /// 发送表盘信息汇总
+ case dial(IBDial)
/// 发送单个表盘文件信息
- case dialInfo(IBDialInfo)
+ case dialFile(IBDialFile)
/// 删除指定文件
case deleteFile(IBDeleteFile)
+ /// 获取指定类型文件目录
+ case fetchFils(IBFile)
var key: IBKey {
switch self {
case .epo(_): return .epo
case .ota(_): return .ota
case .MP3(_): return .mp3
- case .customDial(_): return .customDial
- case .marketDial(_): return .marketDial
- case .dialInfo(_): return .dialInfo
+ case .dial(_): return .dial
+ case .dialFile(_): return .dialFile
case .deleteFile(_): return .deleteFile
-
+ case .fetchFils(_): return .fetchFils
}
}
}
case ride
}
-public enum IBFile: Int {
+public enum IBFile: Int,Codable {
/// OTA 文件
case ota = 1
/// MP3 文件
return .response.code(.success).items(model.list)
}
}
+struct IBFetchFilesParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ guard let model = IBFetchFile(dataPacket.data) else { return .response.code(.parseError) }
+ Logger.log(model.jsonString ?? "")
+ return .response.code(.success).value(model)
+ }
+}
struct IBAblutParser: IBParser {
public typealias IBCompletionHandler = (_ result: IBResponse) -> Void
+
/// 蓝牙消息发送类
public class IBSender {
public init() {
/// 扫描到的蓝牙设备集合
var devices: [IBPeripheral] = []
+
+ /// 当前连接设备
+ var currentConnected: IBPeripheral!
+ /// 最后消息发送实例
+ private weak var lastSender: IBSender?
+
+ /// 文件发送实例
+ internal weak var fileSender: IBFileSender?
+
private var option: IBScanOption = .default
func scan(_ option: IBScanOption = .default) {
self.option = option
centralManager.connect(connectedPeripheral)
}
- /// 当前连接设备
- var currentConnected: IBPeripheral!
- /// 最后消息发送实例
- private weak var lastSender: IBSender?
-
/// 开始扫描
private func startScan() {
if centralManager.state == .poweredOn {
Logger.log("开始扫描蓝牙设备...")
}
centralManager.scanForPeripherals(withServices: nil, options: option.values)
+
+ if let connectedPeripheral = centralManager.retrieveConnectedPeripherals(withServices: [CBUUID(string: serviceUUID)]).first{
+ currentConnected = IBPeripheral()
+ currentConnected.peripheral = connectedPeripheral
+ IBManager.discoverHandler?(currentConnected)
+ }
}
/// 停止扫描
/// - sender: 消息类
func send(_ sender: IBSender) {
if let _ = lastSender {
+// sender.completion(.response.code(.failure).message("有命令正在执行中..."))
sender.remove()
Logger.log("有命令正在执行中...")
return
startTimer()
for dataPacket in sender.dataPacket {
+ Thread.sleep(forTimeInterval:0.02)
+
Logger.log("\(sender.ability.characteristicUUID!) Write:", dataPacket.data.joined(" "), "Bytes(\(dataPacket.count))")
var writeCharacteristic: CBCharacteristic = writeCharacteristic
if sender.ability.characteristicUUID == writeFileCharacteristicUUID {
writeCharacteristic = writeFileCharacteristic
}
- Thread.sleep(forTimeInterval:0.02)
currentConnected.peripheral.writeValue(dataPacket.data, for: writeCharacteristic, type: .withResponse)
}
}
+ func send(fileData data: Data) {
+// Thread.sleep(forTimeInterval:0.02)
+ startTimer()
+// Logger.log("\(writeFileCharacteristicUUID) Write:", data.joined(" "), "Bytes(\(data.count))")
+ Logger.log("\(writeFileCharacteristicUUID) Write:", data)
+ currentConnected.peripheral.writeValue(data, for: writeFileCharacteristic, type: .withResponse)
+ }
func sendDeviceInfo(){
// IBNotify.post(.authStateDidChange, userInfo: ["state": 0])
.parameters([.sendPhone(model)])
.response { result in
Logger.log(result)
+ if result.code != .success{
+ //通知连接失败
+ IBManager.completeHandler?(false)
+ IBManager.completeHandler = nil
+ return
+ }
guard let value = result.value as? Int else {return}
if value == 2 {
//通知连接成功
Logger.log("断开连接")
deinitTimer()
- lastSender?.completion?(.response.code(.failure))
- lastSender?.remove()
+ if let sender = lastSender{
+ lastSender = nil
+ sender.completion?(.response.code(.failure))
+ sender.remove()
+ }
+
IBManager.completeHandler?(false)
IBManager.completeHandler = nil
guard let data = characteristic.value else { return }
if(data.count == 2 && data.int == 0){
- Logger.log("收到0000指令异常")
+// Logger.log("收到0000指令异常")
return
}
let uuid = characteristic.uuid.uuidString
Logger.log(mark, uuid, "->", data.joined(" "), "Bytes(\(data.count))")
- if lastSender?.isAnswer(data) == true {
+ if let sender = lastSender,sender.isAnswer(data) == true {
deinitTimer()
-
- let result = lastSender!.parse(data)
- lastSender?.completion?(result)
- lastSender?.remove()
+ lastSender = nil
+ sender.completion?(sender.parse(data))
+ sender.remove()
return
}
/// 写入完成(发送指令完成)
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
- Logger.log(error)
- return
+ Logger.log(characteristic.uuid.uuidString,error.localizedDescription)
+ }
+ else{
+ Logger.log(characteristic.uuid.uuidString, "write success")
+ }
+ if let hander = self.fileSender?.lastData?.fileWriteHander{
+ hander(error == nil ? .response.code(.success):.response.code(.failure))
}
-
-// Logger.log(characteristic.uuid.uuidString, "write success")
}
}
Self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false, block: { [unowned self] _ in
deinitTimer()
- lastSender?.completion?(.response.code(.timeout))
- lastSender?.remove()
+ if let sender = lastSender{
+ lastSender = nil
+ sender.completion?(.response.code(.timeout))
+ sender.remove()
+ }
+
+ self.fileSender?.lastData?.fileWriteHander?(.response.code(.timeout))
})
// Logger.log("创建定时器")
}
/// 删除文件模型
public class IBDeleteFile:Codable {
/// 文件类型,2:MP3 3:表盘
- public var fileType: Int = 2
+ public var fileType: IBFile = .mp3
/// 文件UID,文件唯一编号。一次最多5个
public var uids: [Int] = []
import Foundation
/// 表盘文件汇总信息模型(自定义)
-public class IBCustomDial {
- /// UID,0~4:自定义表盘UID(不可变更)
- public var UID: Int = 0
- /// 电池图标样式, 0:无图标 1:黑色图标 2:白色图标
- public var batteryStyle: Int = 0
- /// 时间样式 0:时间在下方 1:时间在上方
- public var timeStyle: Int = 0
- /// 时间字体颜色 0:亮白 1:黑 2:暗白 3:绿 4:橙 5:蓝
- public var timeColor: Int = 0
- /// 文件数目,0~255,为0则后面无需发送各个文件。(仅有一种情况为0,即自定义表盘背景图跟之前一样,仅改变了电池和时间样式)
- public var numberOfFiles: Int = 0
-
- public init() { }
-
- public var description: String {
- return "IBCustomDail(UID: \(UID), batteryStyle:\(batteryStyle), timeStyle:\(timeStyle), timeColor:\(timeColor), numberOfFiles:\(numberOfFiles))"
- }
-}
+//internal class IBCustomDial {
+// /// UID,0~4:自定义表盘UID(不可变更)
+// var UID: Int = 0
+// /// 电池图标样式, 0:无图标 1:黑色图标 2:白色图标
+// var batteryStyle: Int = 0
+// /// 时间样式 0:时间在下方 1:时间在上方
+// var timeStyle: Int = 0
+// /// 时间字体颜色 0:亮白 1:黑 2:暗白 3:绿 4:橙 5:蓝
+// var timeColor: Int = 0
+// /// 表盘文件
+// var files: [IBDialFile]?
+//
+// init() { }
+//
+// var description: String {
+// return "IBCustomDail(UID: \(UID), batteryStyle:\(batteryStyle), timeStyle:\(timeStyle), timeColor:\(timeColor))"
+// }
+//}
+//
+///// 表盘文件汇总信息模型(表盘市场)
+//internal class IBMarketDial {
+// /// UID,0x05~0xffff:表盘市场UID
+// var UID: Int = 5
+// /// 主题名称,名称长度,0~255
+// var theme: String?
+// /// 主题名称长度
+// internal var themeLength: Int = 0
+// /// 表盘文件
+// var files: [IBDialFile]?
+//
+//
+// init() { }
+//
+// var description: String {
+// return "IBMarketDail(UID: \(UID), themeLength:\(themeLength), theme:\(theme ?? ""))"
+// }
+//}
-/// 表盘文件汇总信息模型(表盘市场)
-public class IBMarketDial {
- /// UID,0x05~0xffff:表盘市场UID
- public var UID: Int = 0
- /// 主题名称长度。0~255,为0则后面名称不用传,且SD卡中该表盘文件夹名字为“unknown_xxxx”(xxxx为UID)。(仅表盘市场有该项)
- public var themeLength: Int = 0
- /// 主题名称,名字字节数为前面名称长度指定,UTF-8编码,仅表盘市场有该项,自定义表盘名称统一为"custom"
+/// 表盘文件汇总信息模型(目前只支持表盘市场)
+public class IBDial {
+ /// 自定义表盘:0~4
+ /// 表盘市场 : 0x05~0xffff
+ public var UID: Int = 5
+
+ /// 主题名称,名称长度,0~255 (仅表盘市场有该项)
public var theme: String?
- /// 文件数目,0~255,为0则后面无需发送各个文件。(仅有一种情况为0,即自定义表盘背景图跟之前一样,仅改变了电池和时间样式)
- public var numberOfFiles: Int = 0
+ /// 主题名称长度
+ internal var themeLength: Int = 0
+
+ /// 电池图标样式, 0:无图标 1:黑色图标 2:白色图标( 仅 定义表盘有该项)
+ public var batteryStyle: Int?
+ /// 时间样式 0:时间在下方 1:时间在上方 ( 仅自定义表盘有该项)
+ public var timeStyle: Int?
+ /// 时间字体颜色 0:亮白 1:黑 2:暗白 3:绿 4:橙 5:蓝 ( 仅自定义表盘有该项)
+ public var timeColor: Int?
+
+ /// 表盘文件
+ public var files: [IBDialFile]?
+
public init() { }
public var description: String {
- return "IBMarketDail(UID: \(UID), themeLength:\(themeLength), theme:\(theme ?? ""), numberOfFiles:\(numberOfFiles))"
+ return "IBMarketDail(UID: \(UID), themeLength:\(themeLength), theme:\(theme ?? ""))"
}
}
/// 单个表盘文件信息模型
-public class IBDialInfo {
+public class IBDialFile {
+ /// 文件路径
+ public var filePath: String = ""
+ /// 文件名称
+ public var fileName: String = ""
+
/// UID,0x05~0xffff:表盘市场UID
- public var UID: Int = 0
+ internal var UID: Int = 0
/// 文件编号,1~255,即该主题表盘中的第几个文件,最大为表盘文件汇总信息中的文件数目
- public var number: Int = 0
+ internal var index: Int = 0
/// 文件类型,0:表盘jSON文件(压缩后统一命名为local_page.lzma)1:图片文件(除缩略图外的其他图片)(背景图压缩后统一命名为main_bg.lzma)2:缩略图:(压缩后统一命名为thumbnail.lzma)
- public var fileType: Int = 0
+ internal var fileType: Int = 1
/// 文件名称长度,6~255,不能为0,文件后缀统一为.lzma
- public var nameLength: Int = 0
- /// 文件名称,名称字节数为前面名称长度指定,UTF-8编码
- public var fileName: String = ""
+ internal var nameLength: Int{
+ return fileName.count
+ }
/// 文件大小,0x01~0xffffffff
- public var fizeSize: Int = 0
+ internal var size: Int = 1
+
public init() { }
+
public var description: String {
- return "IBDailInfo(UID: \(UID), number:\(number),fileType: \(fileType), nameLength:\(nameLength), fileName:\(fileName), fizeSize:\(fizeSize))"
+ return "IBDialFile(UID: \(UID), index:\(index),fileType: \(fileType), nameLength:\(nameLength), filePath:\(filePath), fileName:\(fileName), size:\(size))"
}
}
import Foundation
/// IBEpo信息模型
-public class IBEpo:Codable {
- /// æ\96\87ä»¶ç±»å\9e\8bï¼\8c7
- internal var type: Int = 7
- /// 固定字段为1
- internal var valid: Int = 1
- /// 文件名
+public class IBEpo {
+ /// æ\96\87ä»¶ç¼\96å\8f·ï¼\8c1~255
+ public var UID: Int = 5
+ /// 文件名称长度,6~255,不能为0,文件后缀统一为.lzma
+ public var nameLength: Int = 6
+ /// 文件名称,名称字节数为前面名称长度指定,UTF-8编码
public var fileName: String = ""
- /// 纬度值
- public var lat: String = ""
- /// 经度值
- public var lon: String = ""
+ /// 文件路径
+ public var filePath: String = ""
/// 文件大小,单位byte
- public var size: Int = 1000
+ var fileSize: Int = 0
public init() { }
+
+ public var description: String {
+ return "IBEpo(UID: \(UID), nameLength:\(nameLength),fileName: \(fileName),filePath: \(filePath),fileSize: \(fileSize))"
+ }
}
--- /dev/null
+//
+// IBFetchFile.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/8.
+//
+
+import Foundation
+/// 获取指定类型文件目录模型
+public class IBFetchFile:Codable {
+ /// 类型 2:MP3, 3:表盘
+ var type:IBFile = .mp3
+ /// 表盘
+ var dialList:[IBFetchDialItem]?
+ /// mp3
+ var mp3List:[IBFetchMp3Item]?
+
+ init?(_ data: Data) {
+ guard let dict = data.jsonObject() as? [String: Any] else { return nil }
+ guard let type = dict["type"] as? Int,let fileType = IBFile(rawValue: type) else { return nil }
+ self.type = fileType
+
+ if let listDict = dict["list"] as? [[String: Any]] {
+ if self.type == .mp3 {
+ var list = [IBFetchMp3Item]()
+ for item in listDict {
+ let model = IBFetchMp3Item()
+ model.uid = item["uid"] as? Int ?? 0
+ model.name = item["name"] as? String ?? ""
+ model.size = item["size"] as? Int ?? 0
+ model.artist = item["artist"] as? String ?? ""
+ model.duration = item["duration"] as? Int ?? 0
+ list.append(model)
+ }
+ mp3List = list
+ }
+ else if self.type == .dial{
+ var list = [IBFetchDialItem]()
+ for item in listDict {
+ let model = IBFetchDialItem()
+ model.uid = item["uid"] as? Int ?? 0
+ model.name = item["name"] as? String ?? ""
+ model.size = item["size"] as? Int ?? 0
+ list.append(model)
+ }
+ dialList = list
+ }
+ }
+ }
+
+ public init() { }
+}
+
+/// 获取指定类型文件表盘模型
+public class IBFetchDialItem:Codable {
+ /// 表盘在手表中唯一编号
+ var uid:Int = 0
+ /// 表盘名字
+ var name:String = ""
+ /// 文件大小,单位byte
+ var size:Int = 0
+
+ public init() { }
+}
+
+/// 获取指定类型文件MP3模型
+public class IBFetchMp3Item:Codable {
+ /// 歌曲在手表中唯一编号
+ var uid:Int = 0
+ /// 歌曲名字
+ var name:String = ""
+ /// 文件大小,单位byte
+ var size:Int = 0
+ /// 作者
+ var artist:String = ""
+ /// 歌曲时长,单位毫秒
+ var duration:Int = 0
+
+ public init() { }
+}
if data.count > i + 7 {
let model = IBHistoryCalorieItem()
model.time = data[0...3].int
- model.value = data[i+4...i+7].int
+ model.value = data[i+4...i+7].float
historyList.append(model)
}
}
/// 时间戳
public var time: Int = 0
/// 单位kcal
- public var value: Int = 0
+ public var value: Float = 0
public init() { }
}
if data.count > i + 7 {
let model = IBHistoryDistanceItem()
model.time = data[i...i+3].int
- model.value = data[i+4...i+7].int
+ model.value = data[i+4...i+7].float
historyList.append(model)
}
}
/// 时间戳
public var time: Int = 0
/// 单位km
- public var value: Int = 0
+ public var value: Float = 0
public init() { }
}
/// 时间
public struct IBSleepTime {
/// 时
- public var hour: UInt8
+ public var hour: Int
/// 分 0~59
- public var minute: UInt8
+ public var minute: Int
- public init(hour: UInt8, minute: UInt8) {
+ public init(hour: Int, minute: Int) {
self.hour = hour
self.minute = minute
}
/// 睡眠状态
public class IBSleepState {
/// Unix时间戳
- public var time: Int32 = 0
+ public var time: Int = 0
/// 状态,0:进入睡眠 1:浅层睡眠 2:深层睡眠 3:清醒 4:结束睡眠 7:未出睡最后一笔睡眠数据(资料上报结束标志) 9:出睡最后一笔睡眠数据(资料上报结束标志) 12:快速眼动 13:取消入睡通知(睡眠不足 60 分钟出睡上报状态) 14:结束小睡
- public var state: Int8 = 0
+ public var state: Int = 0
}
//
import Foundation
-/// mp3文件信息模型
-public class IBMp3:Codable {
+
+/// 发送mp3文件模型
+public class IBSendMp3:Codable {
+ /// 文件路径
+ public var filePath: String = ""
/// 歌曲名字
public var name: String = ""
- /// 文件类型,2:MP3
- internal var type: Int = 2
/// 文件大小,单位byte
- public var size: Int = 0
-
-// enum CodingKeys: String, CodingKey {
-// case name = "pn"
-// case type = "tp"
-// case mac = "mac"
-// }
+ internal var size: Int = 0
+
public init() { }
}
+
+
+/// mp3文件模型
+public class IBMp3File:Codable {
+ /// 歌曲名字
+ var name: String = ""
+ /// 文件类型,2:MP3
+ var type: Int = 2
+ /// 文件大小,单位byte
+ var size: Int = 0
+
+ internal init(data:IBSendMp3) {
+ self.name = data.name
+ self.size = data.size
+ }
+}
+
//
import Foundation
-/// OTA文件信息模型
-public class IBOta:Codable {
- /// 文件类型,1:OTA
- internal var type: Int = 1
+/// 发送OTA文件模型
+public class IBSendOta:Codable {
+ /// 文件路径
+ public var filePath: String = ""
/// 文件大小,单位byte
- public var size: Int = 0
+ internal var size: Int = 0
/// major是主版本号,mid是中间版本号,minor是次版本号,用于与手表当前版本比较,新于当前版本才可升级
public var major: Int = 0
/// mid是中间版本号
public init() { }
}
+/// OTA文件模型
+public class IBOtaFile:Codable {
+ /// 文件类型,1:OTA
+ internal var type: Int = 1
+ /// 文件大小,单位byte
+ internal var size: Int = 0
+ /// major是主版本号,mid是中间版本号,minor是次版本号,用于与手表当前版本比较,新于当前版本才可升级
+ internal var major: Int = 0
+ /// mid是中间版本号
+ internal var mid: Int = 0
+ /// minor是次版本号
+ internal var minor: Int = 0
+ /// 是否强制升级,0为非强制,1为强制
+ internal var isForced: Int = 0
+
+ internal init(data:IBSendOta) {
+ self.size = data.size
+ self.major = data.major
+ self.mid = data.mid
+ self.minor = data.minor
+ self.isForced = data.isForced
+ }
+}
public extension UInt8 {
var int: Int { Int(self) }
}
+
+
+extension Data{
+ var float: Float {
+ var floatValue: Float = 0.0
+ let size = MemoryLayout<Float>.size
+ guard self.count >= size else { return 0 }
+
+ self.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
+ _ = memcpy(&floatValue, buffer.baseAddress!, size)
+ }
+
+ return floatValue
+ }
+}