From: liangzhenying <867174124@qq.com> Date: Fri, 21 Jun 2024 07:42:39 +0000 (+0800) Subject: 表盘传输优化 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=15c7c7156e3f2fdf51b1569d04d35df38d18d5ea;p=mirrors%2FIOTBluetooth-iOS.git 表盘传输优化 --- diff --git a/Doc/20240607_092332_2_20240607_110430_ota.fwpkg b/Doc/20240607_092332_2_20240607_110430_ota.fwpkg new file mode 100644 index 0000000..b69ae17 Binary files /dev/null and b/Doc/20240607_092332_2_20240607_110430_ota.fwpkg differ diff --git a/Doc/IOTBluetooth.md b/Doc/IOTBluetooth.md new file mode 100644 index 0000000..990b90c --- /dev/null +++ b/Doc/IOTBluetooth.md @@ -0,0 +1,743 @@ +# 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) + } + } +``` diff --git "a/Doc/\343\200\220230\343\200\221\350\223\235\347\211\231\346\211\213\350\241\250\351\200\232\350\256\257\345\215\217\350\256\256-\346\227\245\345\277\227\344\270\212\344\274\240V1.0.pdf" "b/Doc/\343\200\220230\343\200\221\350\223\235\347\211\231\346\211\213\350\241\250\351\200\232\350\256\257\345\215\217\350\256\256-\346\227\245\345\277\227\344\270\212\344\274\240V1.0.pdf" new file mode 100644 index 0000000..b04a82b Binary files /dev/null and "b/Doc/\343\200\220230\343\200\221\350\223\235\347\211\231\346\211\213\350\241\250\351\200\232\350\256\257\345\215\217\350\256\256-\346\227\245\345\277\227\344\270\212\344\274\240V1.0.pdf" differ diff --git a/IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift b/IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift index 86a693c..0389481 100644 --- a/IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift +++ b/IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift @@ -39,8 +39,23 @@ extension AbilityViewController: UITableViewDelegate, UITableViewDataSource { 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() @@ -49,16 +64,96 @@ extension AbilityViewController: UITableViewDelegate, UITableViewDataSource { .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) + } + } } diff --git a/IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift b/IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift index 53ec0bb..7be021f 100644 --- a/IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift +++ b/IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift @@ -96,7 +96,7 @@ class ConnectViewController: BaseViewController { /// 数据包索引 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] diff --git a/IOTBluetooth/IOTBluetooth/Example/Datas.swift b/IOTBluetooth/IOTBluetooth/Example/Datas.swift index 429ff48..145944e 100644 --- a/IOTBluetooth/IOTBluetooth/Example/Datas.swift +++ b/IOTBluetooth/IOTBluetooth/Example/Datas.swift @@ -29,8 +29,8 @@ let datas: [(ability: IBAbility, label: String, parameters: [IBParameter])] = [ (.setClock, "发送闹钟设置", [.clock(IBClock())]), (.fetchCurrentDialId, "发送当前表盘ID", []), - (.switchDial, "切换表盘", [.dialID(12)]), - (.deleteDial, "删除表盘", [.dialID(12)]), + (.switchDial, "切换表盘", [.dialID(26112)]), + (.deleteDial, "删除表盘", [.dialID(7168)]), (.fetchDials, "获取所有表盘UID", []), (.checkDialID, "获取某表盘ID是否已安装", [.dialID(12)]), @@ -87,18 +87,16 @@ let datas: [(ability: IBAbility, label: String, parameters: [IBParameter])] = [ (.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())]), ] diff --git a/IOTBluetooth/IOTBluetooth/Example/Resource/20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg b/IOTBluetooth/IOTBluetooth/Example/Resource/20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg new file mode 100644 index 0000000..08e44ee Binary files /dev/null and b/IOTBluetooth/IOTBluetooth/Example/Resource/20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg differ diff --git a/IOTBluetooth/IOTBluetooth/Example/Resource/galaxy.lzma b/IOTBluetooth/IOTBluetooth/Example/Resource/galaxy.lzma new file mode 100644 index 0000000..fb481b9 Binary files /dev/null and b/IOTBluetooth/IOTBluetooth/Example/Resource/galaxy.lzma differ diff --git "a/IOTBluetooth/IOTBluetooth/Example/Resource/\345\244\247\347\272\246\345\234\250\345\206\254\345\255\243.mp3" "b/IOTBluetooth/IOTBluetooth/Example/Resource/\345\244\247\347\272\246\345\234\250\345\206\254\345\255\243.mp3" new file mode 100644 index 0000000..4209e87 Binary files /dev/null and "b/IOTBluetooth/IOTBluetooth/Example/Resource/\345\244\247\347\272\246\345\234\250\345\206\254\345\255\243.mp3" differ diff --git "a/IOTBluetooth/IOTBluetooth/Example/Resource/\351\273\204\346\262\231-\345\220\215\345\206\263.mp3" "b/IOTBluetooth/IOTBluetooth/Example/Resource/\351\273\204\346\262\231-\345\220\215\345\206\263.mp3" new file mode 100644 index 0000000..680ab53 Binary files /dev/null and "b/IOTBluetooth/IOTBluetooth/Example/Resource/\351\273\204\346\262\231-\345\220\215\345\206\263.mp3" differ diff --git a/IOTBluetooth/IOTBluetooth/Example/ViewController.swift b/IOTBluetooth/IOTBluetooth/Example/ViewController.swift index 1d6af06..8e63674 100644 --- a/IOTBluetooth/IOTBluetooth/Example/ViewController.swift +++ b/IOTBluetooth/IOTBluetooth/Example/ViewController.swift @@ -44,6 +44,8 @@ class ViewController: BaseViewController { } @IBAction func startScan(_ sender: UIButton) { +// IBFileSender().readFile() +// return title = "正在扫描蓝牙设备..." IBManager.scan { [unowned self] peripheral in if let _ = datas.filter({ $0.uuid == peripheral.uuid }).last { diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj index 76bc46c..3ca006b 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj @@ -10,15 +10,23 @@ 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 */; }; @@ -109,15 +117,23 @@ 180B01972BFC831300524729 /* IBHistoryHeartRate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryHeartRate.swift; sourceTree = ""; }; 180B019D2BFC9CF400524729 /* IBHistoryBaseTraining.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryBaseTraining.swift; sourceTree = ""; }; 180CE2642C0D728F00A7006E /* IBAddressBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAddressBook.swift; sourceTree = ""; }; + 18113B2E2C13195900E04997 /* galaxy.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = galaxy.lzma; sourceTree = ""; }; + 18113B302C1400D700E04997 /* 黄沙-名决.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "黄沙-名决.mp3"; sourceTree = ""; }; + 18113B322C1406CB00E04997 /* 大约在冬季.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "大约在冬季.mp3"; sourceTree = ""; }; 181441CE2C11571400CCDD46 /* IBMp3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBMp3.swift; sourceTree = ""; }; 181441D02C11575D00CCDD46 /* IBOta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBOta.swift; sourceTree = ""; }; 181441D22C115AC500CCDD46 /* IBFileSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBFileSender.swift; sourceTree = ""; }; 181441D42C116AE500CCDD46 /* IBDial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBDial.swift; sourceTree = ""; }; + 18204BFF2C199B8600F94E69 /* IBFileSender+Mp3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Mp3.swift"; sourceTree = ""; }; + 18204C012C199D0E00F94E69 /* IBFileSender+Ota.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Ota.swift"; sourceTree = ""; }; + 18204C032C199E7800F94E69 /* IBFileSender+Dial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Dial.swift"; sourceTree = ""; }; + 1839DBCF2C14442200E33B6E /* IBFetchFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBFetchFile.swift; sourceTree = ""; }; 184431312BF3720E00546E4E /* IBOtherTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBOtherTarget.swift; sourceTree = ""; }; 1849DC8E2BEC8007002C2D2D /* IBVibrateSound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBVibrateSound.swift; sourceTree = ""; }; 1849DC902BECD131002C2D2D /* IBCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBCode.swift; sourceTree = ""; }; 184B94CA2BF606CC0042BEB3 /* IBTrainTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTrainTarget.swift; sourceTree = ""; }; 1859DF9F2BFF5A2700112E8C /* IBSendTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBSendTime.swift; sourceTree = ""; }; + 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 = ""; }; 18884C432C11DBA300E01458 /* IBDeleteFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBDeleteFile.swift; sourceTree = ""; }; 18884C452C12A4EA00E01458 /* IBEpo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBEpo.swift; sourceTree = ""; }; 18993AA12C073FED006742D2 /* IBBloodOxygen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBBloodOxygen.swift; sourceTree = ""; }; @@ -201,6 +217,17 @@ /* 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 = ""; + }; 8A0A39B12B980BEF006A3B18 = { isa = PBXGroup; children = ( @@ -233,6 +260,9 @@ 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 */, @@ -266,6 +296,7 @@ 8A1DFF502B9EB4F800B735FF /* Example */ = { isa = PBXGroup; children = ( + 18113B292C1318FD00E04997 /* Resource */, 8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */, 8A1DFF552B9EB4F800B735FF /* ViewController.swift */, 8A34B9262BA2AB66000F66BD /* ConnectViewController.swift */, @@ -322,6 +353,7 @@ 181441D42C116AE500CCDD46 /* IBDial.swift */, 18884C452C12A4EA00E01458 /* IBEpo.swift */, 18884C432C11DBA300E01458 /* IBDeleteFile.swift */, + 1839DBCF2C14442200E33B6E /* IBFetchFile.swift */, 1849DC902BECD131002C2D2D /* IBCode.swift */, ); path = Models; @@ -429,8 +461,12 @@ 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; }; @@ -444,6 +480,7 @@ 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 */, @@ -462,8 +499,10 @@ 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 */, @@ -479,6 +518,7 @@ 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 */, diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate index d9462a7..0136190 100644 Binary files a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate and b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index c296514..dd854dd 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,4 +3,22 @@ uuid = "3E7E940C-0C97-40F4-A990-DDBAB66B37E1" type = "1" version = "2.0"> + + + + + + diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift index cb5c81d..52797a6 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift @@ -439,24 +439,24 @@ public extension IBAbility { // 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`` @@ -467,20 +467,23 @@ public extension IBAbility { 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 { diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift index b30715a..7858eda 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift @@ -103,14 +103,6 @@ struct IBOtaChecker: IBChecker { 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] { @@ -314,17 +306,44 @@ struct IBMp3Checker: IBChecker { 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 { @@ -334,15 +353,20 @@ struct IBMarketDialChecker: IBChecker { 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 @@ -350,7 +374,7 @@ struct IBDialInfoChecker: IBChecker { else{ data += [0.byte] } - data += value.fizeSize.byte4 + data += value.size.byte4 return data } } @@ -359,7 +383,7 @@ struct IBDeleteFileChecker: IBChecker { 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 } diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Dial.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Dial.swift new file mode 100644 index 0000000..59a7b1b --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Dial.swift @@ -0,0 +1,164 @@ +// +// 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() + } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Mp3.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Mp3.swift new file mode 100644 index 0000000..8e196db --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Mp3.swift @@ -0,0 +1,87 @@ +// +// 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() + } + } + } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Ota.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Ota.swift new file mode 100644 index 0000000..68d090a --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Ota.swift @@ -0,0 +1,86 @@ +// +// 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() + } + } + } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender.swift index bb776e8..f36d7bf 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender.swift @@ -7,31 +7,255 @@ 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销毁定时器") + + } } diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift index 3d8f214..5de8c13 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift @@ -31,7 +31,7 @@ public class IBManager { 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 } diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift index a7c5a57..a8da2b2 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift @@ -160,6 +160,9 @@ public extension IBNotify.Name { /// 获取手表推送的训练实时数据,通过object获取状态IBTrainingData对象 static let trainingData = IBNotify.Name("IOTBluetooth.TrainingData") + + /// 手表发送文件已接收完成 + internal static let fileReceiveFinish = IBNotify.Name("IOTBluetooth.TileReceiveFinish") } extension IBNotify { @@ -205,7 +208,12 @@ 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) diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift index b10eb73..ac342bc 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift @@ -33,10 +33,10 @@ enum IBKey: String { 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" } /// 参数 @@ -86,19 +86,19 @@ public enum IBParameter { /// 发送通讯录 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 { @@ -127,11 +127,10 @@ public enum IBParameter { 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 } } } @@ -145,7 +144,7 @@ public enum IBMotionMode: Int { case ride } -public enum IBFile: Int { +public enum IBFile: Int,Codable { /// OTA 文件 case ota = 1 /// MP3 文件 diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift index a4d90d5..4ae93e7 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift @@ -239,6 +239,14 @@ struct IBSyncGpsParser: IBParser { 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 { diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift index 39e96ff..e0597a8 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift @@ -9,6 +9,7 @@ import Foundation public typealias IBCompletionHandler = (_ result: IBResponse) -> Void + /// 蓝牙消息发送类 public class IBSender { public init() { diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift index 5709399..c0d0d2a 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift @@ -72,6 +72,15 @@ class IBBluetooth: NSObject { /// 扫描到的蓝牙设备集合 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 @@ -86,17 +95,18 @@ class IBBluetooth: NSObject { 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) + } } /// 停止扫描 @@ -125,6 +135,7 @@ class IBBluetooth: NSObject { /// - sender: 消息类 func send(_ sender: IBSender) { if let _ = lastSender { +// sender.completion(.response.code(.failure).message("有命令正在执行中...")) sender.remove() Logger.log("有命令正在执行中...") return @@ -133,6 +144,8 @@ class IBBluetooth: NSObject { 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 @@ -142,10 +155,16 @@ class IBBluetooth: NSObject { 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]) @@ -158,6 +177,12 @@ class IBBluetooth: NSObject { .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 { //通知连接成功 @@ -243,8 +268,12 @@ extension IBBluetooth: CBCentralManagerDelegate { 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 @@ -316,7 +345,7 @@ extension IBBluetooth: CBPeripheralDelegate { guard let data = characteristic.value else { return } if(data.count == 2 && data.int == 0){ - Logger.log("收到0000指令异常") +// Logger.log("收到0000指令异常") return } @@ -324,12 +353,11 @@ extension IBBluetooth: CBPeripheralDelegate { 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 } @@ -358,11 +386,14 @@ extension IBBluetooth: CBPeripheralDelegate { /// 写入完成(发送指令完成) 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") } } @@ -374,8 +405,13 @@ extension IBBluetooth { 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("创建定时器") } diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBDeleteFile.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBDeleteFile.swift index eeac194..a46fd18 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBDeleteFile.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBDeleteFile.swift @@ -10,7 +10,7 @@ import Foundation /// 删除文件模型 public class IBDeleteFile:Codable { /// 文件类型,2:MP3 3:表盘 - public var fileType: Int = 2 + public var fileType: IBFile = .mp3 /// 文件UID,文件唯一编号。一次最多5个 public var uids: [Int] = [] diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBDial.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBDial.swift index cb5dea1..6d8060d 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBDial.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBDial.swift @@ -7,61 +7,98 @@ 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))" } } diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBEpo.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBEpo.swift index 03e29c3..a5a2340 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBEpo.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBEpo.swift @@ -7,19 +7,21 @@ import Foundation /// IBEpo信息模型 -public class IBEpo:Codable { - /// 文件类型,7 - internal var type: Int = 7 - /// 固定字段为1 - internal var valid: Int = 1 - /// 文件名 +public class IBEpo { + /// 文件编号,1~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))" + } } diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBFetchFile.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBFetchFile.swift new file mode 100644 index 0000000..bd7fa42 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBFetchFile.swift @@ -0,0 +1,80 @@ +// +// 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() { } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift index b6e2e87..94e855a 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift @@ -17,7 +17,7 @@ import Foundation 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) } } @@ -30,7 +30,7 @@ public class IBHistoryCalorieItem { /// 时间戳 public var time: Int = 0 /// 单位kcal - public var value: Int = 0 + public var value: Float = 0 public init() { } } diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift index 0c876bd..ef8d97d 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift @@ -17,7 +17,7 @@ import Foundation 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) } } @@ -30,7 +30,7 @@ public class IBHistoryDistanceItem { /// 时间戳 public var time: Int = 0 /// 单位km - public var value: Int = 0 + public var value: Float = 0 public init() { } } diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift index ebf1904..b84a695 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift @@ -62,11 +62,11 @@ public class IBHistorySleepItem { /// 时间 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 } @@ -80,7 +80,7 @@ public struct IBSleepTime { /// 睡眠状态 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 } diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBMp3.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBMp3.swift index d30879c..3b10293 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBMp3.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBMp3.swift @@ -6,20 +6,33 @@ // 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 + } +} + diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBOta.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBOta.swift index 6e6bdf6..5f8aa00 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBOta.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBOta.swift @@ -6,12 +6,12 @@ // 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是中间版本号 @@ -23,3 +23,26 @@ public class IBOta:Codable { 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 + } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift index 64e712f..662e7e1 100644 --- a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift @@ -179,3 +179,18 @@ public extension Data { public extension UInt8 { var int: Int { Int(self) } } + + +extension Data{ + var float: Float { + var floatValue: Float = 0.0 + let size = MemoryLayout.size + guard self.count >= size else { return 0 } + + self.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in + _ = memcpy(&floatValue, buffer.baseAddress!, size) + } + + return floatValue + } +}