表盘传输优化
authorliangzhenying <867174124@qq.com>
Fri, 21 Jun 2024 07:42:39 +0000 (15:42 +0800)
committerliangzhenying <867174124@qq.com>
Fri, 21 Jun 2024 07:42:39 +0000 (15:42 +0800)
36 files changed:
Doc/20240607_092332_2_20240607_110430_ota.fwpkg [new file with mode: 0644]
Doc/IOTBluetooth.md [new file with mode: 0644]
Doc/【230】蓝牙手表通讯协议-日志上传V1.0.pdf [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift
IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift
IOTBluetooth/IOTBluetooth/Example/Datas.swift
IOTBluetooth/IOTBluetooth/Example/Resource/20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/Resource/galaxy.lzma [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/Resource/大约在冬季.mp3 [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/Resource/黄沙-名决.mp3 [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/ViewController.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Dial.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Mp3.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender+Ota.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBFileSender.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBDeleteFile.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBDial.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBEpo.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBFetchFile.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBMp3.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBOta.swift
IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift

diff --git a/Doc/20240607_092332_2_20240607_110430_ota.fwpkg b/Doc/20240607_092332_2_20240607_110430_ota.fwpkg
new file mode 100644 (file)
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 (file)
index 0000000..990b90c
--- /dev/null
@@ -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/【230】蓝牙手表通讯协议-日志上传V1.0.pdf b/Doc/【230】蓝牙手表通讯协议-日志上传V1.0.pdf
new file mode 100644 (file)
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
index 86a693c..0389481 100644 (file)
@@ -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)
+        }
+    }
 }
index 53ec0bb..7be021f 100644 (file)
@@ -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]
         
index 429ff48..145944e 100644 (file)
@@ -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 (file)
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 (file)
index 0000000..fb481b9
Binary files /dev/null and b/IOTBluetooth/IOTBluetooth/Example/Resource/galaxy.lzma differ
diff --git a/IOTBluetooth/IOTBluetooth/Example/Resource/大约在冬季.mp3 b/IOTBluetooth/IOTBluetooth/Example/Resource/大约在冬季.mp3
new file mode 100644 (file)
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/黄沙-名决.mp3 b/IOTBluetooth/IOTBluetooth/Example/Resource/黄沙-名决.mp3
new file mode 100644 (file)
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
index 1d6af06..8e63674 100644 (file)
@@ -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 {
index 76bc46c..3ca006b 100644 (file)
                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 */,
index d9462a7..0136190 100644 (file)
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
index c296514..dd854dd 100644 (file)
@@ -3,4 +3,22 @@
    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>
index cb5c81d..52797a6 100644 (file)
@@ -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 {
index b30715a..7858eda 100644 (file)
@@ -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 (file)
index 0000000..59a7b1b
--- /dev/null
@@ -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 (file)
index 0000000..8e196db
--- /dev/null
@@ -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 (file)
index 0000000..68d090a
--- /dev/null
@@ -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()
+                }
+            }
+    }
+}
index bb776e8..f36d7bf 100644 (file)
 
 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销毁定时器")
+        
+    }
 }
index 3d8f214..5de8c13 100644 (file)
@@ -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 }
index a7c5a57..a8da2b2 100644 (file)
@@ -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)
index b10eb73..ac342bc 100644 (file)
@@ -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 文件
index a4d90d5..4ae93e7 100644 (file)
@@ -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 {
index 39e96ff..e0597a8 100644 (file)
@@ -9,6 +9,7 @@ import Foundation
 
 public typealias IBCompletionHandler = (_ result: IBResponse) -> Void
 
+
 /// 蓝牙消息发送类
 public class IBSender {
     public init() {
index 5709399..c0d0d2a 100644 (file)
@@ -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("创建定时器")
     }
index eeac194..a46fd18 100644 (file)
@@ -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] = []
 
index cb5dea1..6d8060d 100644 (file)
@@ -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))"
     }
 }
index 03e29c3..a5a2340 100644 (file)
@@ -7,19 +7,21 @@
 
 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))"
+    }
 }
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBFetchFile.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBFetchFile.swift
new file mode 100644 (file)
index 0000000..bd7fa42
--- /dev/null
@@ -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() { }
+}
index b6e2e87..94e855a 100644 (file)
@@ -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() { }
 }
index 0c876bd..ef8d97d 100644 (file)
@@ -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() { }
 }
index ebf1904..b84a695 100644 (file)
@@ -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
 }
index d30879c..3b10293 100644 (file)
@@ -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
+    }
+}
+
index 6e6bdf6..5f8aa00 100644 (file)
@@ -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
+    }
+}
index 64e712f..662e7e1 100644 (file)
@@ -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<Float>.size
+        guard self.count >= size else { return 0 }
+        
+        self.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
+            _ = memcpy(&floatValue, buffer.baseAddress!, size)
+        }
+        
+        return floatValue
+    }
+}