From: liangzhenying <867174124@qq.com> Date: Tue, 5 Nov 2024 03:37:15 +0000 (+0800) Subject: 重构文件传输 X-Git-Tag: 给聚海提供sdk~5 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=69655b421fa3fffe4541353c31f1dc2e51fc7e03;p=mirrors%2FHsFileAgentSdk_iOS.git 重构文件传输 --- diff --git a/code/IOTBluetooth/Example/AbilityViewController.swift b/code/IOTBluetooth/Example/AbilityViewController.swift new file mode 100644 index 0000000..2261447 --- /dev/null +++ b/code/IOTBluetooth/Example/AbilityViewController.swift @@ -0,0 +1,170 @@ +// +// AbilityViewController.swift +// Example +// +// Created by yaozuopan on 2024/3/11. +// + +import UIKit +import IOTBluetooth + +class AbilityViewController: BaseViewController { + + + + @IBOutlet weak var listView: UITableView! + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Log", style: .plain, target: self, action: #selector(showLog)) + } + + @objc func showLog(){ + let vc = storyboard!.instantiateViewController(identifier: "LogViewController") + navigationController?.pushViewController(vc, animated: true) + } +} + +extension AbilityViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + datas.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let data = datas[indexPath.row] + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + let label = cell.viewWithTag(10) as! UILabel + label.text = "\(indexPath.row) " + data.ability.description + " \(data.label)" + return cell + } + + 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} + 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(){ +//系统表盘 +// ID: 101 | Name: sportdial +// ID: 102 | Name: kaleidoscope +// ID: 1052 | Name: classic +// ID: 103 | Name: jellyfish +// ID: 1028 | Name: KUMI + + 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 = 10002 + 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/code/IOTBluetooth/Example/AppDelegate.swift b/code/IOTBluetooth/Example/AppDelegate.swift new file mode 100644 index 0000000..b65ac44 --- /dev/null +++ b/code/IOTBluetooth/Example/AppDelegate.swift @@ -0,0 +1,24 @@ +// +// AppDelegate.swift +// Example +// +// Created by yaozuopan on 2024/3/11. +// + +import UIKit +import IOTBluetooth + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + HsFileAgentSdk.shared.isDebug = true + HsFileAgentSdk.shared.initSDK() + + return true + } +} + diff --git a/code/IOTBluetooth/Example/Assets.xcassets/AccentColor.colorset/Contents.json b/code/IOTBluetooth/Example/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/code/IOTBluetooth/Example/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/code/IOTBluetooth/Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/code/IOTBluetooth/Example/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/code/IOTBluetooth/Example/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/code/IOTBluetooth/Example/Assets.xcassets/Contents.json b/code/IOTBluetooth/Example/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/code/IOTBluetooth/Example/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/code/IOTBluetooth/Example/Base.lproj/LaunchScreen.storyboard b/code/IOTBluetooth/Example/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/code/IOTBluetooth/Example/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/IOTBluetooth/Example/Base.lproj/Main.storyboard b/code/IOTBluetooth/Example/Base.lproj/Main.storyboard new file mode 100644 index 0000000..5a59854 --- /dev/null +++ b/code/IOTBluetooth/Example/Base.lproj/Main.storyboard @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/IOTBluetooth/Example/BaseViewController.swift b/code/IOTBluetooth/Example/BaseViewController.swift new file mode 100644 index 0000000..c7ab315 --- /dev/null +++ b/code/IOTBluetooth/Example/BaseViewController.swift @@ -0,0 +1,36 @@ +// +// BaseViewController.swift +// Example +// +// Created by yaozuopan on 2024/3/14. +// + +import UIKit + +class BaseViewController: UIViewController { + + func showAlert(_ title: String? = nil, msg: String, confirmLabel: String? = nil, onConfirm: (() -> Void)? = nil) { + let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "返回", style: .cancel)) + + if let confirmLabel = confirmLabel { + alert.addAction(UIAlertAction(title: confirmLabel, style: .default, handler: { _ in + onConfirm?() + })) + } + present(alert, animated: true) + } + + let activityIndicatorView = UIActivityIndicatorView(style: .large) + override func viewDidLoad() { + super.viewDidLoad() + + activityIndicatorView.frame = UIScreen.main.bounds + activityIndicatorView.layer.zPosition = 1 + view.addSubview(activityIndicatorView) + } + + deinit { + print("deinit \(Self.self)") + } +} diff --git a/code/IOTBluetooth/Example/Datas.swift b/code/IOTBluetooth/Example/Datas.swift new file mode 100644 index 0000000..98c64a2 --- /dev/null +++ b/code/IOTBluetooth/Example/Datas.swift @@ -0,0 +1,132 @@ +// +// Datas.swift +// Example +// +// Created by yaozuopan on 2024/3/19. +// + +import Foundation +import IOTBluetooth + + + +let datas: [(ability: IBAbility, label: String, parameters: [IBParameter])] = [ + (.userData,"设置用户数据",[.userInfo(IBUserInfo())]), + (.language, "设置语言", [.language(.chinese)]), + (.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(Config.setScreen())]), + (.fetchBattery, "获取电量", []), + (.fetchClock, "获取闹钟设置", []), + (.setClock, "发送闹钟设置", [.clock(IBClock())]), + + (.fetchCurrentDialId, "发送当前表盘ID", []), + (.switchDial, "切换表盘", [.dialID(1028)]), + (.deleteDial, "删除表盘", [.dialID(10001)]), + (.fetchDials, "获取所有表盘UID", []), + (.checkDialID, "获取某表盘ID是否已安装", [.dialID(10001)]), + + (.fetchWatchState, "获取当前手表状态", []), + (.fetchVibrateSound, "获取手表声音与震动参数", []), + (.vibrateSound, "设置手表声音与震动参数", [.vibrateSound(Config.setVibrateSound())]), + (.fetchAuxiliary, "获取手表辅助功能参数", []), + (.auxiliary, "设置手表辅助功能参数", [.auxiliary(.bloodOxygen)]), + (.unit, "设置手表公英制单位", [.value(1)]), + + + (.disk, "获取SD卡剩余空间", []), + (.unpair, "发送解除配对消息", []), + (.sendUnpair, "发送取消解除配对消息", []), + + + (.startSport, "开启指定运动模式", [.motionMode(.walking)]), + (.trainPauseResume, "训练暂停", [.action(0)]), + (.trainPauseResume, "训练恢复", [.action(1)]), + (.endSport, "结束运动模式", []), + + (.heartRateMode, "开启自动获取实时心率", [.action(1)]), + (.heartRateMode, "关闭自动获取实时心率", [.action(0)]), + (.heartRateHigh, "设置最高心率提醒", [.value(180)]), + (.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(0)]), + (.syncBloodOxygen, "同步血氧历史数据", [.value(0)]), + (.syncStepCount, "同步历史步数数据", [.value(0)]), + (.syncFatBurning, "同步燃脂时间历史数据", [.value(0)]), + (.syncStand, "同步站立历史数据", [.value(0)]), + (.syncCalorie, "同步卡路里历史数据", [.value(0)]), + (.syncDistance, "同步距离历史数据", [.value(0)]), + (.syncTrain, "同步基础训练历史数据", [.value(0)]), + (.syncSleep, "同步睡眠历史数据", [.value(0)]), + (.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())]), +] + +class Config{ + static func setScreen()->IBScreenConfig{ + let model = IBScreenConfig() + model.type = 0 + model.brightness = 255 + +// model.type = 1 +// model.duration = .minute +// model.wrist = 0 + return model + } + static func setVibrateSound()->IBVibrateConfig{ + let model = IBVibrateConfig() +// model.type = 0 +// model.systemValue = 92 + +// model.type = 1 +// model.ringsValue = 48 +// + model.type = 3 + model.ringsState = 1 + model.vibrateState = 1 + return model + } +} diff --git a/code/IOTBluetooth/Example/Info.plist b/code/IOTBluetooth/Example/Info.plist new file mode 100644 index 0000000..1ec584a --- /dev/null +++ b/code/IOTBluetooth/Example/Info.plist @@ -0,0 +1,15 @@ + + + + + UIBackgroundModes + + bluetooth-central + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/code/IOTBluetooth/Example/LogViewController.swift b/code/IOTBluetooth/Example/LogViewController.swift new file mode 100644 index 0000000..99f5173 --- /dev/null +++ b/code/IOTBluetooth/Example/LogViewController.swift @@ -0,0 +1,28 @@ +// +// LogViewController.swift +// Example +// +// Created by yaozuopan on 2024/3/14. +// + +import UIKit +import IOTBluetooth +import IOTBluetooth + +class LogViewController: BaseViewController { + + @IBOutlet weak var textView: UITextView! + + override func viewDidLoad() { + title = "蓝牙日志" + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + textView.text = HsFileAgentSdk.shared.logs.joined(separator: "\n") + + let range = NSMakeRange(textView.text.count - 1, 1) + textView.scrollRangeToVisible(range) + } +} + diff --git a/code/IOTBluetooth/Example/Resource/20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg b/code/IOTBluetooth/Example/Resource/20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg new file mode 100644 index 0000000..08e44ee Binary files /dev/null and b/code/IOTBluetooth/Example/Resource/20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg differ diff --git a/code/IOTBluetooth/Example/Resource/AIAudio.sbc b/code/IOTBluetooth/Example/Resource/AIAudio.sbc new file mode 100644 index 0000000..d96322c Binary files /dev/null and b/code/IOTBluetooth/Example/Resource/AIAudio.sbc differ diff --git a/code/IOTBluetooth/Example/Resource/galaxy.lzma b/code/IOTBluetooth/Example/Resource/galaxy.lzma new file mode 100644 index 0000000..fb481b9 Binary files /dev/null and b/code/IOTBluetooth/Example/Resource/galaxy.lzma differ diff --git "a/code/IOTBluetooth/Example/Resource/\345\244\247\347\272\246\345\234\250\345\206\254\345\255\243.mp3" "b/code/IOTBluetooth/Example/Resource/\345\244\247\347\272\246\345\234\250\345\206\254\345\255\243.mp3" new file mode 100644 index 0000000..4209e87 Binary files /dev/null and "b/code/IOTBluetooth/Example/Resource/\345\244\247\347\272\246\345\234\250\345\206\254\345\255\243.mp3" differ diff --git "a/code/IOTBluetooth/Example/Resource/\351\273\204\346\262\231-\345\220\215\345\206\263.mp3" "b/code/IOTBluetooth/Example/Resource/\351\273\204\346\262\231-\345\220\215\345\206\263.mp3" new file mode 100644 index 0000000..680ab53 Binary files /dev/null and "b/code/IOTBluetooth/Example/Resource/\351\273\204\346\262\231-\345\220\215\345\206\263.mp3" differ diff --git a/code/IOTBluetooth/Example/ViewController.swift b/code/IOTBluetooth/Example/ViewController.swift new file mode 100644 index 0000000..5cfa071 --- /dev/null +++ b/code/IOTBluetooth/Example/ViewController.swift @@ -0,0 +1,76 @@ +// +// ViewController.swift +// Example +// +// Created by yaozuopan on 2024/3/11. +// + +import UIKit +import IOTBluetooth +import CoreBluetooth + +class ViewController: BaseViewController { + + @IBOutlet weak var listView: UITableView! + private var dataList: [CBPeripheral] = [] + + override func viewDidLoad() { + super.viewDidLoad() + title = "IOTBluetooth" + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Log", style: .plain, target: self, action: #selector(showLog)) + title = "扫描" + } + + + @IBAction func startScan(_ sender: UIButton) { + IBBluetoothManager.shared.startScan() + IBBluetoothManager.shared.discoverCallBack = { [unowned self] peripheral in + if dataList.contains(peripheral){ + return + } + if let name = peripheral.name,name.isEmpty{ + return + } +// print(peripheral.name) + dataList.append(peripheral) + listView.reloadData() + } + IBBluetoothManager.shared.didConnectedCallBack = {[unowned self] peripheral in + activityIndicatorView.stopAnimating() + let vc = storyboard!.instantiateViewController(identifier: "AbilityViewController") + self.navigationController?.pushViewController(vc, animated: true) + } + IBBluetoothManager.shared.disConnectedCallBack = { peripheral in + print("蓝牙已经断开!!!!!!!!!!!") + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + IBBluetoothManager.shared.stopScan() + } + @objc func showLog(){ + let vc = storyboard!.instantiateViewController(identifier: "LogViewController") + navigationController?.pushViewController(vc, animated: true) + } +} + +extension ViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + dataList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + let label = cell.viewWithTag(10) as! UILabel + label.text = dataList[indexPath.row].name ?? "" + "(点击连接)" + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + activityIndicatorView.startAnimating() + let device = dataList[indexPath.row] + IBBluetoothManager.shared.connect(device) + } +} diff --git a/code/IOTBluetooth/Example/ble/IBBluetoothManager.swift b/code/IOTBluetooth/Example/ble/IBBluetoothManager.swift new file mode 100644 index 0000000..042cadf --- /dev/null +++ b/code/IOTBluetooth/Example/ble/IBBluetoothManager.swift @@ -0,0 +1,243 @@ +// +// IBluetooth.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/2/4. +// + +import Foundation +import CoreBluetooth +import IOTBluetooth + +/// Max is 482 +let dataPacketMaxBytes = 450 + +/// 主服务 +let serviceUUID: String = "18E0" +/// Notify 特征值 +let notifyCharacteristicUUID: String = "2AE5" +/// Read 特征值 +let readCharacteristicUUID: String = "2AE2" +/// Write 特征值(发送字节数≤20) +let writeCharacteristicUUID: String = "2AE3" +/// Write 特征值(发送字节数>20) +let writeBigCharacteristicUUID: String = "2AE4" +/// Write 特征值(发送文件OAT、MP3等) +let writeFileCharacteristicUUID: String = "2AE0" + +public typealias IBDiscoverHandler = (_ peripheral: CBPeripheral) -> Void +public typealias IBDidConectedHandler = (_ peripheral: CBPeripheral) -> Void +public typealias IBDisConectedHandler = (_ peripheral: CBPeripheral) -> Void + +class IBBluetoothManager: NSObject { + static let shared = IBBluetoothManager() + + /// 2AE5 + private var notifyCharacteristic: CBCharacteristic! + /// 2AE2 + private var readCharacteristic: CBCharacteristic! + /// 2AE3 + private var writeCharacteristic: CBCharacteristic! + /// 2AE4 + private var writeBigCharacteristic: CBCharacteristic! + /// 2AE0 + private var writeFileCharacteristic: CBCharacteristic! + + /// 中央管理类,管理蓝牙的扫描连接 + var centralManager: CBCentralManager! + /// 当前连接设备 + var currentPeripheral: CBPeripheral? + + /// 扫描到设备 + var discoverCallBack:IBDiscoverHandler? + /// 连上设备 + var didConnectedCallBack:IBDidConectedHandler? + /// 设备断开连接 + var disConnectedCallBack:IBDisConectedHandler? + + private override init() { + super.init() + centralManager = CBCentralManager(delegate: self, queue: nil) + setupFileSdk() + } + + func setupFileSdk(){ + HsFileAgentSdk.shared.isDebug = true + HsFileAgentSdk.shared.sdkSendToDevice { data in + print(data) + } + } +} + +extension IBBluetoothManager{ + /// 开始扫描 + func startScan() { + Logger.log("开始扫描...") + centralManager.scanForPeripherals(withServices: [CBUUID(string: serviceUUID)], options: nil) + if let peripheral = centralManager.retrieveConnectedPeripherals(withServices: [CBUUID(string: serviceUUID)]).first{ + discoverCallBack?(peripheral) + } + } + + /// 停止扫描 + func stopScan(){ + Logger.log("停止扫描蓝牙设备...") + centralManager.stopScan() + } + + /// 连接蓝牙设备 + func connect(_ peripheral: CBPeripheral) { + centralManager.connect(peripheral) + } + + /// 断开与蓝牙设备的连接 + func cancel(_ peripheral: CBPeripheral) { + centralManager.cancelPeripheralConnection(peripheral) + } + + /// 发送数据 + func send(_ sender: IBSender) { + //// if currentPeripheral.peripheral.state == .disconnected{ + //// sender.completion(.response.code(.failure).message("手机未连上蓝牙设备")) + //// sender.remove() + //// Logger.log("手机未连上蓝牙设备!") + //// return + //// } + // + // + // for dataPacket in sender.dataPacket { + // Thread.sleep(forTimeInterval:0.02) + // + // Logger.log("\(sender.ability.characteristicUUID!) Write:", dataPacket.data.joined(" "), "Bytes(\(dataPacket.count))") + // + // var writeCharacteristic: CBCharacteristic = writeCharacteristic + // if sender.ability.characteristicUUID == writeBigCharacteristicUUID { + // writeCharacteristic = writeBigCharacteristic + // } + // if sender.ability.characteristicUUID == writeFileCharacteristicUUID { + // writeCharacteristic = writeFileCharacteristic + // } + // currentPeripheral.peripheral.writeValue(dataPacket.data, for: writeCharacteristic, type: .withResponse) + // } + } + func send(fileData data: Data) { + //// Thread.sleep(forTimeInterval:0.02) + //// if currentPeripheral.peripheral.state == .disconnected{ + //// self.fileSender?.lastData?.fileWriteHander?(.response.code(.failure).message("手机未连上蓝牙设备")) + //// Logger.log("手机未连上蓝牙设备!") + //// return + //// } + // + // Logger.log("\(writeFileCharacteristicUUID) Write:", data) + // currentPeripheral.peripheral.writeValue(data, for: writeFileCharacteristic, type: .withResponse) + } +} + +extension IBBluetoothManager: CBCentralManagerDelegate { + func centralManagerDidUpdateState(_ central: CBCentralManager) { + switch central.state { + case .unknown: + Logger.log("unknown") + case .resetting: + Logger.log("resetting") + case .unsupported: + Logger.log("unsupported") + case .unauthorized: + Logger.log("unauthorized") + case .poweredOff: + Logger.log("poweredOff") + case .poweredOn: + startScan() + Logger.log("poweredOn") + @unknown default: + break + } + } + + func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { +// IBManager.discoverHandler?(device) + discoverCallBack?(peripheral) + } + + func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + currentPeripheral = peripheral + peripheral.delegate = self + peripheral.discoverServices([CBUUID(string: serviceUUID)]) + Logger.log("连接成功", peripheral.name ?? "") + didConnectedCallBack?(peripheral) + } + + func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { + Logger.log("连接失败",error?.localizedDescription ?? "" ) + currentPeripheral = nil + } + + func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { + Logger.log("已断开连接") + disConnectedCallBack?(peripheral) + } +} + +extension IBBluetoothManager: CBPeripheralDelegate { + func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { + + } + func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { + peripheral + .services? + .forEach { + peripheral.discoverCharacteristics(nil, for: $0) + } + } + func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { + service + .characteristics? + .forEach { + if $0.uuid.uuidString == notifyCharacteristicUUID { + notifyCharacteristic = $0 + peripheral.setNotifyValue(true, for: $0) + return + } + + if $0.uuid.uuidString == readCharacteristicUUID { + readCharacteristic = $0 + peripheral.setNotifyValue(true, for: $0) + return + } + + if $0.uuid.uuidString == writeCharacteristicUUID { + writeCharacteristic = $0 + return + } + + if $0.uuid.uuidString == writeBigCharacteristicUUID { + writeBigCharacteristic = $0 + return + } + + if $0.uuid.uuidString == writeFileCharacteristicUUID { + writeFileCharacteristic = $0 + return + } + } + } + func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { + peripheral.readValue(for: characteristic) + } + + func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { + if let error = error { + Logger.log(error) + } + HsFileAgentSdk.shared.sendToSdk(characteristic.value!) + } + + func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { + if let error = error { + Logger.log("发送失败 ",characteristic.uuid.uuidString,error.localizedDescription) + } + else{ + Logger.log(characteristic.uuid.uuidString, "write success") + } + } +} diff --git a/code/IOTBluetooth/Example/ble/IBManager.swift b/code/IOTBluetooth/Example/ble/IBManager.swift new file mode 100644 index 0000000..18dda24 --- /dev/null +++ b/code/IOTBluetooth/Example/ble/IBManager.swift @@ -0,0 +1,79 @@ +//// +//// IBManager.swift +//// IOTBluetooth +//// +//// Created by yaozuopan on 2024/3/12. +//// +// +//import Foundation +// +//public typealias IBDiscoverHandler = (_ peripheral: IBPeripheral) -> Void +//public typealias IBCompleteHandler = (_ isSuccess: Bool) -> Void +// +///// SDK 管理类 +//public class IBManager { +// +// /// 初始化工作 +// public static func initializationSDK() { +//// IBBluetooth.shared.scan(.default) +// +//// DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { +//// IBBluetooth.shared.reconnected() +//// } +//// 发送天气数据 设备重启 +//// 获取sd卡剩余空间 失败 +//// 同步路径坐标集历史数据 无响应 +//// 结束运动模式后立即发送其他指令 设备重启 +// } +// +// public static var logs: [String] { Logger.logs } +//} +// +//public extension IBManager { +// /// SDK 版本号 +// static let version: String = "1.0.23" +// /// 默认为 false +// static var isDebug: Bool = false { +// didSet { Logger.isDebug = isDebug } +// } +//} +// +//// 蓝牙相关 API +//public extension IBManager { +// +// internal static var discoverHandler: IBDiscoverHandler? +// +// /// 扫描设备 +// /// - Parameters: +// /// - option: 扫描参数 +// /// - discoverHandler: 结果回调 +// static func scan(_ option: IBScanOption = .default, discoverHandler: @escaping IBDiscoverHandler) { +// Self.discoverHandler = discoverHandler +// IBBluetooth.shared.scan(option) +// } +// +// /// 停止扫描蓝牙设备 +// static func stopScan() { +// Self.discoverHandler = nil +// IBBluetooth.shared.stopScan() +// } +// +// internal static var completeHandler: IBCompleteHandler? +// +// /// 连接设备 +// /// - Parameters: +// /// - peripheral: 扫描发现的设备 +// /// - completeHandler: 结果回调 +// static func connect(_ peripheral: IBPeripheral, completeHandler: @escaping IBCompleteHandler) { +// Self.completeHandler = completeHandler +// IBBluetooth.shared.connect(peripheral) +// } +// +// /// 断开与蓝牙设备的连接 +// /// - Parameter peripheral: 蓝牙设备对像 +// static func cancel(_ peripheral: IBPeripheral) { +// IBBluetooth.shared.cancel(peripheral) +// } +// /// 当前已连接设备 +// static var currentConnected: IBPeripheral! { IBBluetooth.shared.currentConnected} +//} diff --git a/code/IOTBluetooth/Example/ble/Logger.swift b/code/IOTBluetooth/Example/ble/Logger.swift new file mode 100644 index 0000000..3dbf8bf --- /dev/null +++ b/code/IOTBluetooth/Example/ble/Logger.swift @@ -0,0 +1,78 @@ +// +// Logger.swift +// +// +// Created by Harvey on 2021/4/27. +// + +import Foundation + +/// 日记管理 +public class Logger { + static var isDebug: Bool = true + + fileprivate static let shared = Logger() + private var fileHandle:FileHandle? + private init() { + let path = getPath() + fileHandle = FileHandle(forWritingAtPath: path)! +// fileHandle?.seekToEndOfFile() + } + + fileprivate var dateString: String { + let f = DateFormatter() + f.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" + return f.string(from: Date()) + } + + static var logs: [String] = [] + + + private func getPath()->String{ +// let path = NSHomeDirectory() + "/Documents/IOTBluetooth/" + "\(Date().format(.middle)).log" + let foldPath = NSHomeDirectory() + "/Documents/IOTBluetooth/" + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: foldPath) { + do { + try FileManager.default.createDirectory(atPath: foldPath, withIntermediateDirectories: true, attributes: nil) + } catch { + return "" + } + } + + let f = DateFormatter() + f.dateFormat = "yyyy-MM-dd HHmmss" + var time = f.string(from: Date()) + + let path = foldPath + "\(time).log" + if !FileManager.default.fileExists(atPath: path) { + FileManager.default.createFile(atPath: path, contents: nil, attributes: nil) + } + return path + } +} + +public extension Logger { + static func log(_ logs: Any..., line: Int = #line, separator: String = " ", terminator: String = "\n") { + objc_sync_enter(Logger.shared) + let logString = Logger.shared.dateString + " [app-\(String(format: "%03d", line))]: " + logs.map({ "\($0)" }).joined(separator: separator) + terminator + if Logger.isDebug { + print(Logger.shared.dateString, terminator: " [app-\(String(format: "%03d", line))]: ") + print(logs, separator: separator, terminator: terminator) + Self.logs.append(logString) + } +// Logger.shared.saveLog(log: logString) + objc_sync_exit(Logger.shared) + + + } + + private func saveLog(log:String){ + fileHandle?.write(log.data(using: .utf8)!) +// if #available(iOS 13.0, *) { +// try? fileHandle.close() +// } else { +// // Fallback on earlier versions +// } + } +} diff --git a/code/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj b/code/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj new file mode 100644 index 0000000..3c4ed9f --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj @@ -0,0 +1,963 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 180220CE2C2D5D560090D34A /* IBAiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180220CD2C2D5D560090D34A /* IBAiResponse.swift */; }; + 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 */; }; + 182DDFD42C5CE68900D44D37 /* IBAiValid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182DDFD32C5CE68900D44D37 /* IBAiValid.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 */; }; + 189C15892C05E0CD00676A80 /* IBTarinState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15882C05E0CD00676A80 /* IBTarinState.swift */; }; + 189C158B2C05F27C00676A80 /* IBAbout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C158A2C05F27C00676A80 /* IBAbout.swift */; }; + 189C158D2C05F58A00676A80 /* IBHistoryBloodOxygen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C158C2C05F58A00676A80 /* IBHistoryBloodOxygen.swift */; }; + 189C158F2C05F85200676A80 /* IBHistoryStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C158E2C05F85200676A80 /* IBHistoryStep.swift */; }; + 189C15912C05FBA100676A80 /* IBHistoryBurning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15902C05FBA100676A80 /* IBHistoryBurning.swift */; }; + 189C15932C05FFA600676A80 /* IBHistoryStand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15922C05FFA600676A80 /* IBHistoryStand.swift */; }; + 189C15952C05FFC700676A80 /* IBHistoryCalorie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15942C05FFC700676A80 /* IBHistoryCalorie.swift */; }; + 189C15972C05FFEE00676A80 /* IBHistoryDistance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15962C05FFEE00676A80 /* IBHistoryDistance.swift */; }; + 18C9589B2C082FA7006A5DE8 /* IBWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C9589A2C082FA7006A5DE8 /* IBWeather.swift */; }; + 18C9589D2C08600C006A5DE8 /* IBHistoryGps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C9589C2C08600C006A5DE8 /* IBHistoryGps.swift */; }; + 18C9589F2C086A9D006A5DE8 /* IBHistorySleep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C9589E2C086A9D006A5DE8 /* IBHistorySleep.swift */; }; + 18E7AC922BF44C6800CB9393 /* IBSleep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E7AC912BF44C6800CB9393 /* IBSleep.swift */; }; + 18E7AC942BF4B31000CB9393 /* IBWater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E7AC932BF4B31000CB9393 /* IBWater.swift */; }; + 18E7AC962BF5E39900CB9393 /* IBHeartRate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E7AC952BF5E39900CB9393 /* IBHeartRate.swift */; }; + 18EC6E282BFF4C6D00757CA8 /* IBPhoneInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EC6E272BFF4C6D00757CA8 /* IBPhoneInfo.swift */; }; + 18EF69512C45130200AA0A36 /* IBAiManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF69502C45130200AA0A36 /* IBAiManager.swift */; }; + 18EF69532C45136800AA0A36 /* IBPairling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF69522C45136800AA0A36 /* IBPairling.swift */; }; + 18EF69552C45266D00AA0A36 /* IBReceiveManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF69542C45266D00AA0A36 /* IBReceiveManager.swift */; }; + 18EF69702C4798BB00AA0A36 /* libbz2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18EF696B2C47766400AA0A36 /* libbz2.tbd */; }; + 18EF69712C4798C600AA0A36 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18EF69692C47765800AA0A36 /* libiconv.tbd */; }; + 18EF69722C4798D100AA0A36 /* liblzma.5.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18EF69672C47764400AA0A36 /* liblzma.5.tbd */; }; + 18EF69732C4798DD00AA0A36 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18EF695F2C476E2000AA0A36 /* libz.tbd */; }; + 18EF69762C47A0F200AA0A36 /* AIAudio.sbc in Resources */ = {isa = PBXBuildFile; fileRef = 18EF69752C47A0F200AA0A36 /* AIAudio.sbc */; }; + 18EF69772C47A0F200AA0A36 /* AIAudio.sbc in Resources */ = {isa = PBXBuildFile; fileRef = 18EF69752C47A0F200AA0A36 /* AIAudio.sbc */; }; + 18EF69792C47A86D00AA0A36 /* IBTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF69782C47A86D00AA0A36 /* IBTools.swift */; }; + 18EF697A2C47A86D00AA0A36 /* IBTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF69782C47A86D00AA0A36 /* IBTools.swift */; }; + 18EFF59B2C080F14001B1E0E /* IBUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */; }; + 3FC0CFCE2CD8634200F97933 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC0CFCD2CD8634200F97933 /* Logger.swift */; }; + 3FFE56DF2CD6157F000C7415 /* IBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9F6AC02BA04BE100DA520F /* IBManager.swift */; }; + 3FFE56E02CD61584000C7415 /* IBBluetoothManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39C52B980C88006A3B18 /* IBBluetoothManager.swift */; }; + 3FFE56E72CD624D6000C7415 /* HsFileAgentSdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFE56E62CD624D6000C7415 /* HsFileAgentSdk.swift */; }; + 8A0A39BF2B980BEF006A3B18 /* IOTBluetooth.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8A0A39C92B980CC2006A3B18 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */; }; + 8A0A39CB2B980CFC006A3B18 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39CA2B980CFC006A3B18 /* Logger.swift */; }; + 8A0A39D22B980E4C006A3B18 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D12B980E4C006A3B18 /* JSON.swift */; }; + 8A0A39D42B980E93006A3B18 /* Any.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D32B980E93006A3B18 /* Any.swift */; }; + 8A0A39D62B980EC9006A3B18 /* IBPeripheralUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D52B980EC9006A3B18 /* IBPeripheralUUID.swift */; }; + 8A0A39D82B980F58006A3B18 /* UTF8.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D72B980F58006A3B18 /* UTF8.swift */; }; + 8A0A39DA2B981037006A3B18 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D92B981037006A3B18 /* Date.swift */; }; + 8A1DFF522B9EB4F800B735FF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */; }; + 8A1DFF562B9EB4F800B735FF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1DFF552B9EB4F800B735FF /* ViewController.swift */; }; + 8A1DFF592B9EB4F900B735FF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8A1DFF572B9EB4F900B735FF /* Main.storyboard */; }; + 8A1DFF5B2B9EB4F900B735FF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8A1DFF5A2B9EB4F900B735FF /* Assets.xcassets */; }; + 8A1DFF5E2B9EB4F900B735FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8A1DFF5C2B9EB4F900B735FF /* LaunchScreen.storyboard */; }; + 8A34B9252BA2A069000F66BD /* IBWatchInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B9242BA2A069000F66BD /* IBWatchInfo.swift */; }; + 8A34B9292BA2AB86000F66BD /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B9282BA2AB86000F66BD /* LogViewController.swift */; }; + 8A34B92B2BA2D446000F66BD /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B92A2BA2D446000F66BD /* BaseViewController.swift */; }; + 8A34B92D2BA2FA02000F66BD /* IBManufacturer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B92C2BA2FA02000F66BD /* IBManufacturer.swift */; }; + 8A5822CF2BA1A67700590AB6 /* IBAbility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A5822CE2BA1A67700590AB6 /* IBAbility.swift */; }; + 8A5822D12BA1C80700590AB6 /* LRC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A5822D02BA1C80700590AB6 /* LRC.swift */; }; + 8A65F95F2BABEC4D005C1EA4 /* IBClock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F95E2BABEC4D005C1EA4 /* IBClock.swift */; }; + 8A65F9612BAC0810005C1EA4 /* IBSimulateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F9602BAC0810005C1EA4 /* IBSimulateParser.swift */; }; + 8A65F9632BAC1018005C1EA4 /* IBTargetStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F9622BAC1018005C1EA4 /* IBTargetStep.swift */; }; + 8A65F9652BAC271B005C1EA4 /* IBScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F9642BAC271B005C1EA4 /* IBScreen.swift */; }; + 8A76B6092BA84C2B0016F2AB /* AbilityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A76B6082BA84C2B0016F2AB /* AbilityViewController.swift */; }; + 8A76B60B2BA851730016F2AB /* IBParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A76B60A2BA851730016F2AB /* IBParser.swift */; }; + 8A79D2252BA4278E0096A98C /* IBSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A79D2242BA4278E0096A98C /* IBSender.swift */; }; + 8A79D2272BA433330096A98C /* IBNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A79D2262BA433330096A98C /* IBNotify.swift */; }; + 8A79D2292BA43F5A0096A98C /* IBResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A79D2282BA43F5A0096A98C /* IBResponse.swift */; }; + 8A878FE02BA93CF1008C7F26 /* Datas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A878FDF2BA93CF1008C7F26 /* Datas.swift */; }; + 8A878FE22BA9444C008C7F26 /* IBParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A878FE12BA9444C008C7F26 /* IBParameter.swift */; }; + 8A878FE42BA94D09008C7F26 /* IBChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A878FE32BA94D09008C7F26 /* IBChecker.swift */; }; + 8A87FBB32BAA7F3900096761 /* IBLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A87FBB22BAA7F3900096761 /* IBLanguage.swift */; }; + 8A87FBB52BAA85FB00096761 /* IBTrainingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A87FBB42BAA85FB00096761 /* IBTrainingData.swift */; }; + 8A9F6ABD2BA0463D00DA520F /* CRC16.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9F6ABC2BA0463D00DA520F /* CRC16.swift */; }; + 8A9F6ABE2BA0475100DA520F /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */; }; + 8A9F6AC22BA05CB500DA520F /* IOTBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */; }; + 8A9F6AC32BA05CB500DA520F /* IOTBluetooth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 8A9F6AC42BA05CB500DA520F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8A0A39B22B980BEF006A3B18 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8A0A39BA2B980BEF006A3B18; + remoteInfo = IOTBluetooth; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8A9F6AC62BA05CB500DA520F /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 8A9F6AC32BA05CB500DA520F /* IOTBluetooth.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 180220CD2C2D5D560090D34A /* IBAiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAiResponse.swift; sourceTree = ""; }; + 180B01972BFC831300524729 /* IBHistoryHeartRate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryHeartRate.swift; sourceTree = ""; }; + 180B019D2BFC9CF400524729 /* IBHistoryBaseTraining.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryBaseTraining.swift; sourceTree = ""; }; + 180CE2642C0D728F00A7006E /* IBAddressBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAddressBook.swift; sourceTree = ""; }; + 18113B2E2C13195900E04997 /* galaxy.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = galaxy.lzma; sourceTree = ""; }; + 18113B302C1400D700E04997 /* 黄沙-名决.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "黄沙-名决.mp3"; sourceTree = ""; }; + 18113B322C1406CB00E04997 /* 大约在冬季.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "大约在冬季.mp3"; sourceTree = ""; }; + 181441CE2C11571400CCDD46 /* IBMp3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBMp3.swift; sourceTree = ""; }; + 181441D02C11575D00CCDD46 /* IBOta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBOta.swift; sourceTree = ""; }; + 181441D22C115AC500CCDD46 /* IBFileSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBFileSender.swift; sourceTree = ""; }; + 181441D42C116AE500CCDD46 /* IBDial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBDial.swift; sourceTree = ""; }; + 18204BFF2C199B8600F94E69 /* IBFileSender+Mp3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Mp3.swift"; sourceTree = ""; }; + 18204C012C199D0E00F94E69 /* IBFileSender+Ota.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Ota.swift"; sourceTree = ""; }; + 18204C032C199E7800F94E69 /* IBFileSender+Dial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Dial.swift"; sourceTree = ""; }; + 182DDFD32C5CE68900D44D37 /* IBAiValid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAiValid.swift; sourceTree = ""; }; + 1839DBCF2C14442200E33B6E /* IBFetchFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBFetchFile.swift; sourceTree = ""; }; + 184431312BF3720E00546E4E /* IBOtherTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBOtherTarget.swift; sourceTree = ""; }; + 1849DC8E2BEC8007002C2D2D /* IBVibrateSound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBVibrateSound.swift; sourceTree = ""; }; + 1849DC902BECD131002C2D2D /* IBCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBCode.swift; sourceTree = ""; }; + 184B94CA2BF606CC0042BEB3 /* IBTrainTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTrainTarget.swift; sourceTree = ""; }; + 1859DF9F2BFF5A2700112E8C /* IBSendTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBSendTime.swift; sourceTree = ""; }; + 185E9C3A2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg */ = {isa = PBXFileReference; lastKnownFileType = file; path = 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg; sourceTree = ""; }; + 18884C432C11DBA300E01458 /* IBDeleteFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBDeleteFile.swift; sourceTree = ""; }; + 18884C452C12A4EA00E01458 /* IBEpo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBEpo.swift; sourceTree = ""; }; + 18993AA12C073FED006742D2 /* IBBloodOxygen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBBloodOxygen.swift; sourceTree = ""; }; + 189C15882C05E0CD00676A80 /* IBTarinState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTarinState.swift; sourceTree = ""; }; + 189C158A2C05F27C00676A80 /* IBAbout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAbout.swift; sourceTree = ""; }; + 189C158C2C05F58A00676A80 /* IBHistoryBloodOxygen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryBloodOxygen.swift; sourceTree = ""; }; + 189C158E2C05F85200676A80 /* IBHistoryStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryStep.swift; sourceTree = ""; }; + 189C15902C05FBA100676A80 /* IBHistoryBurning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryBurning.swift; sourceTree = ""; }; + 189C15922C05FFA600676A80 /* IBHistoryStand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryStand.swift; sourceTree = ""; }; + 189C15942C05FFC700676A80 /* IBHistoryCalorie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryCalorie.swift; sourceTree = ""; }; + 189C15962C05FFEE00676A80 /* IBHistoryDistance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryDistance.swift; sourceTree = ""; }; + 18C9589A2C082FA7006A5DE8 /* IBWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBWeather.swift; sourceTree = ""; }; + 18C9589C2C08600C006A5DE8 /* IBHistoryGps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryGps.swift; sourceTree = ""; }; + 18C9589E2C086A9D006A5DE8 /* IBHistorySleep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistorySleep.swift; sourceTree = ""; }; + 18E7AC912BF44C6800CB9393 /* IBSleep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBSleep.swift; sourceTree = ""; }; + 18E7AC932BF4B31000CB9393 /* IBWater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBWater.swift; sourceTree = ""; }; + 18E7AC952BF5E39900CB9393 /* IBHeartRate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHeartRate.swift; sourceTree = ""; }; + 18EC6E272BFF4C6D00757CA8 /* IBPhoneInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBPhoneInfo.swift; sourceTree = ""; }; + 18EF69502C45130200AA0A36 /* IBAiManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAiManager.swift; sourceTree = ""; }; + 18EF69522C45136800AA0A36 /* IBPairling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBPairling.swift; sourceTree = ""; }; + 18EF69542C45266D00AA0A36 /* IBReceiveManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBReceiveManager.swift; sourceTree = ""; }; + 18EF695F2C476E2000AA0A36 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 18EF69672C47764400AA0A36 /* liblzma.5.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = liblzma.5.tbd; path = usr/lib/liblzma.5.tbd; sourceTree = SDKROOT; }; + 18EF69692C47765800AA0A36 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; + 18EF696B2C47766400AA0A36 /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; }; + 18EF69752C47A0F200AA0A36 /* AIAudio.sbc */ = {isa = PBXFileReference; lastKnownFileType = file; path = AIAudio.sbc; sourceTree = ""; }; + 18EF69782C47A86D00AA0A36 /* IBTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTools.swift; sourceTree = ""; }; + 18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBUserInfo.swift; sourceTree = ""; }; + 3FC0CFCD2CD8634200F97933 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 3FFE56E62CD624D6000C7415 /* HsFileAgentSdk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HsFileAgentSdk.swift; sourceTree = ""; }; + 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = IOTBluetooth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IOTBluetooth.h; sourceTree = ""; }; + 8A0A39C52B980C88006A3B18 /* IBBluetoothManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBBluetoothManager.swift; sourceTree = ""; }; + 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; + 8A0A39CA2B980CFC006A3B18 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 8A0A39D12B980E4C006A3B18 /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; + 8A0A39D32B980E93006A3B18 /* Any.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Any.swift; sourceTree = ""; }; + 8A0A39D52B980EC9006A3B18 /* IBPeripheralUUID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBPeripheralUUID.swift; sourceTree = ""; }; + 8A0A39D72B980F58006A3B18 /* UTF8.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTF8.swift; sourceTree = ""; }; + 8A0A39D92B981037006A3B18 /* Date.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; + 8A1DFF4F2B9EB4F800B735FF /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 8A1DFF552B9EB4F800B735FF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 8A1DFF582B9EB4F900B735FF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 8A1DFF5A2B9EB4F900B735FF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8A1DFF5D2B9EB4F900B735FF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 8A1DFF5F2B9EB4F900B735FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8A34B9242BA2A069000F66BD /* IBWatchInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBWatchInfo.swift; sourceTree = ""; }; + 8A34B9282BA2AB86000F66BD /* LogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = ""; }; + 8A34B92A2BA2D446000F66BD /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; + 8A34B92C2BA2FA02000F66BD /* IBManufacturer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBManufacturer.swift; sourceTree = ""; }; + 8A5822CE2BA1A67700590AB6 /* IBAbility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAbility.swift; sourceTree = ""; }; + 8A5822D02BA1C80700590AB6 /* LRC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRC.swift; sourceTree = ""; }; + 8A65F95E2BABEC4D005C1EA4 /* IBClock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBClock.swift; sourceTree = ""; }; + 8A65F9602BAC0810005C1EA4 /* IBSimulateParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBSimulateParser.swift; sourceTree = ""; }; + 8A65F9622BAC1018005C1EA4 /* IBTargetStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTargetStep.swift; sourceTree = ""; }; + 8A65F9642BAC271B005C1EA4 /* IBScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBScreen.swift; sourceTree = ""; }; + 8A76B6082BA84C2B0016F2AB /* AbilityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AbilityViewController.swift; sourceTree = ""; }; + 8A76B60A2BA851730016F2AB /* IBParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBParser.swift; sourceTree = ""; }; + 8A79D2242BA4278E0096A98C /* IBSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBSender.swift; sourceTree = ""; }; + 8A79D2262BA433330096A98C /* IBNotify.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBNotify.swift; sourceTree = ""; }; + 8A79D2282BA43F5A0096A98C /* IBResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBResponse.swift; sourceTree = ""; }; + 8A878FDF2BA93CF1008C7F26 /* Datas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Datas.swift; sourceTree = ""; }; + 8A878FE12BA9444C008C7F26 /* IBParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBParameter.swift; sourceTree = ""; }; + 8A878FE32BA94D09008C7F26 /* IBChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBChecker.swift; sourceTree = ""; }; + 8A87FBB22BAA7F3900096761 /* IBLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBLanguage.swift; sourceTree = ""; }; + 8A87FBB42BAA85FB00096761 /* IBTrainingData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTrainingData.swift; sourceTree = ""; }; + 8A9F6ABC2BA0463D00DA520F /* CRC16.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRC16.swift; sourceTree = ""; }; + 8A9F6AC02BA04BE100DA520F /* IBManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBManager.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8A0A39B82B980BEF006A3B18 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 18EF69732C4798DD00AA0A36 /* libz.tbd in Frameworks */, + 18EF69722C4798D100AA0A36 /* liblzma.5.tbd in Frameworks */, + 18EF69712C4798C600AA0A36 /* libiconv.tbd in Frameworks */, + 18EF69702C4798BB00AA0A36 /* libbz2.tbd in Frameworks */, + 8A0A39C92B980CC2006A3B18 /* CoreBluetooth.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8A1DFF4C2B9EB4F800B735FF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8A9F6AC22BA05CB500DA520F /* IOTBluetooth.framework in Frameworks */, + 8A9F6ABE2BA0475100DA520F /* CoreBluetooth.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 18113B292C1318FD00E04997 /* Resource */ = { + isa = PBXGroup; + children = ( + 18EF69752C47A0F200AA0A36 /* AIAudio.sbc */, + 18113B322C1406CB00E04997 /* 大约在冬季.mp3 */, + 18113B302C1400D700E04997 /* 黄沙-名决.mp3 */, + 185E9C3A2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg */, + 18113B2E2C13195900E04997 /* galaxy.lzma */, + ); + path = Resource; + sourceTree = ""; + }; + 3FFE56DE2CD614B8000C7415 /* ble */ = { + isa = PBXGroup; + children = ( + 3FC0CFCD2CD8634200F97933 /* Logger.swift */, + 8A9F6AC02BA04BE100DA520F /* IBManager.swift */, + 8A0A39C52B980C88006A3B18 /* IBBluetoothManager.swift */, + ); + path = ble; + sourceTree = ""; + }; + 8A0A39B12B980BEF006A3B18 = { + isa = PBXGroup; + children = ( + 8A0A39BD2B980BEF006A3B18 /* IOTBluetooth */, + 8A1DFF502B9EB4F800B735FF /* Example */, + 8A0A39BC2B980BEF006A3B18 /* Products */, + 8A0A39C72B980CC2006A3B18 /* Frameworks */, + ); + sourceTree = ""; + }; + 8A0A39BC2B980BEF006A3B18 /* Products */ = { + isa = PBXGroup; + children = ( + 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */, + 8A1DFF4F2B9EB4F800B735FF /* Example.app */, + ); + name = Products; + sourceTree = ""; + }; + 8A0A39BD2B980BEF006A3B18 /* IOTBluetooth */ = { + isa = PBXGroup; + children = ( + 8A9F6ABF2BA04B5100DA520F /* Models */, + 8A0A39D02B980DFD006A3B18 /* Tools */, + 18EF69542C45266D00AA0A36 /* IBReceiveManager.swift */, + 8A5822CE2BA1A67700590AB6 /* IBAbility.swift */, + 8A878FE32BA94D09008C7F26 /* IBChecker.swift */, + 8A76B60A2BA851730016F2AB /* IBParser.swift */, + 8A65F9602BAC0810005C1EA4 /* IBSimulateParser.swift */, + 8A878FE12BA9444C008C7F26 /* IBParameter.swift */, + 8A79D2242BA4278E0096A98C /* IBSender.swift */, + 181441D22C115AC500CCDD46 /* IBFileSender.swift */, + 18204BFF2C199B8600F94E69 /* IBFileSender+Mp3.swift */, + 18204C012C199D0E00F94E69 /* IBFileSender+Ota.swift */, + 18204C032C199E7800F94E69 /* IBFileSender+Dial.swift */, + 8A79D2262BA433330096A98C /* IBNotify.swift */, + 18EF69522C45136800AA0A36 /* IBPairling.swift */, + 18EF69502C45130200AA0A36 /* IBAiManager.swift */, + 3FFE56E62CD624D6000C7415 /* HsFileAgentSdk.swift */, + 8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */, + ); + path = IOTBluetooth; + sourceTree = ""; + }; + 8A0A39C72B980CC2006A3B18 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 18EF696B2C47766400AA0A36 /* libbz2.tbd */, + 18EF69692C47765800AA0A36 /* libiconv.tbd */, + 18EF69672C47764400AA0A36 /* liblzma.5.tbd */, + 18EF695F2C476E2000AA0A36 /* libz.tbd */, + 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 8A0A39D02B980DFD006A3B18 /* Tools */ = { + isa = PBXGroup; + children = ( + 8A0A39D52B980EC9006A3B18 /* IBPeripheralUUID.swift */, + 8A0A39D32B980E93006A3B18 /* Any.swift */, + 8A0A39D92B981037006A3B18 /* Date.swift */, + 8A0A39D12B980E4C006A3B18 /* JSON.swift */, + 8A9F6ABC2BA0463D00DA520F /* CRC16.swift */, + 8A5822D02BA1C80700590AB6 /* LRC.swift */, + 8A0A39D72B980F58006A3B18 /* UTF8.swift */, + 8A0A39CA2B980CFC006A3B18 /* Logger.swift */, + 18EF69782C47A86D00AA0A36 /* IBTools.swift */, + ); + path = Tools; + sourceTree = ""; + }; + 8A1DFF502B9EB4F800B735FF /* Example */ = { + isa = PBXGroup; + children = ( + 3FFE56DE2CD614B8000C7415 /* ble */, + 18113B292C1318FD00E04997 /* Resource */, + 8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */, + 8A1DFF552B9EB4F800B735FF /* ViewController.swift */, + 8A76B6082BA84C2B0016F2AB /* AbilityViewController.swift */, + 8A878FDF2BA93CF1008C7F26 /* Datas.swift */, + 8A34B92A2BA2D446000F66BD /* BaseViewController.swift */, + 8A34B9282BA2AB86000F66BD /* LogViewController.swift */, + 8A1DFF572B9EB4F900B735FF /* Main.storyboard */, + 8A1DFF5A2B9EB4F900B735FF /* Assets.xcassets */, + 8A1DFF5C2B9EB4F900B735FF /* LaunchScreen.storyboard */, + 8A1DFF5F2B9EB4F900B735FF /* Info.plist */, + ); + path = Example; + sourceTree = ""; + }; + 8A9F6ABF2BA04B5100DA520F /* Models */ = { + isa = PBXGroup; + children = ( + 8A87FBB22BAA7F3900096761 /* IBLanguage.swift */, + 8A79D2282BA43F5A0096A98C /* IBResponse.swift */, + 8A65F95E2BABEC4D005C1EA4 /* IBClock.swift */, + 18E7AC912BF44C6800CB9393 /* IBSleep.swift */, + 8A65F9642BAC271B005C1EA4 /* IBScreen.swift */, + 18E7AC932BF4B31000CB9393 /* IBWater.swift */, + 8A65F9622BAC1018005C1EA4 /* IBTargetStep.swift */, + 8A87FBB42BAA85FB00096761 /* IBTrainingData.swift */, + 8A34B92C2BA2FA02000F66BD /* IBManufacturer.swift */, + 8A34B9242BA2A069000F66BD /* IBWatchInfo.swift */, + 184431312BF3720E00546E4E /* IBOtherTarget.swift */, + 184B94CA2BF606CC0042BEB3 /* IBTrainTarget.swift */, + 18E7AC952BF5E39900CB9393 /* IBHeartRate.swift */, + 18993AA12C073FED006742D2 /* IBBloodOxygen.swift */, + 180B01972BFC831300524729 /* IBHistoryHeartRate.swift */, + 189C158C2C05F58A00676A80 /* IBHistoryBloodOxygen.swift */, + 189C158E2C05F85200676A80 /* IBHistoryStep.swift */, + 189C15902C05FBA100676A80 /* IBHistoryBurning.swift */, + 189C15922C05FFA600676A80 /* IBHistoryStand.swift */, + 189C15942C05FFC700676A80 /* IBHistoryCalorie.swift */, + 189C15962C05FFEE00676A80 /* IBHistoryDistance.swift */, + 180B019D2BFC9CF400524729 /* IBHistoryBaseTraining.swift */, + 18C9589E2C086A9D006A5DE8 /* IBHistorySleep.swift */, + 18C9589C2C08600C006A5DE8 /* IBHistoryGps.swift */, + 1849DC8E2BEC8007002C2D2D /* IBVibrateSound.swift */, + 18EC6E272BFF4C6D00757CA8 /* IBPhoneInfo.swift */, + 1859DF9F2BFF5A2700112E8C /* IBSendTime.swift */, + 189C15882C05E0CD00676A80 /* IBTarinState.swift */, + 189C158A2C05F27C00676A80 /* IBAbout.swift */, + 18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */, + 18C9589A2C082FA7006A5DE8 /* IBWeather.swift */, + 180CE2642C0D728F00A7006E /* IBAddressBook.swift */, + 181441CE2C11571400CCDD46 /* IBMp3.swift */, + 181441D02C11575D00CCDD46 /* IBOta.swift */, + 181441D42C116AE500CCDD46 /* IBDial.swift */, + 18884C452C12A4EA00E01458 /* IBEpo.swift */, + 18884C432C11DBA300E01458 /* IBDeleteFile.swift */, + 1839DBCF2C14442200E33B6E /* IBFetchFile.swift */, + 180220CD2C2D5D560090D34A /* IBAiResponse.swift */, + 1849DC902BECD131002C2D2D /* IBCode.swift */, + 182DDFD32C5CE68900D44D37 /* IBAiValid.swift */, + ); + path = Models; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 8A0A39B62B980BEF006A3B18 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 8A0A39BF2B980BEF006A3B18 /* IOTBluetooth.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 8A0A39BA2B980BEF006A3B18 /* IOTBluetooth */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8A0A39C22B980BEF006A3B18 /* Build configuration list for PBXNativeTarget "IOTBluetooth" */; + buildPhases = ( + 8A0A39B62B980BEF006A3B18 /* Headers */, + 8A0A39B72B980BEF006A3B18 /* Sources */, + 8A0A39B82B980BEF006A3B18 /* Frameworks */, + 8A0A39B92B980BEF006A3B18 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = IOTBluetooth; + productName = IOTBluetooth; + productReference = 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */; + productType = "com.apple.product-type.framework"; + }; + 8A1DFF4E2B9EB4F800B735FF /* Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8A1DFF622B9EB4F900B735FF /* Build configuration list for PBXNativeTarget "Example" */; + buildPhases = ( + 8A1DFF4B2B9EB4F800B735FF /* Sources */, + 8A1DFF4C2B9EB4F800B735FF /* Frameworks */, + 8A1DFF4D2B9EB4F800B735FF /* Resources */, + 8A9F6AC62BA05CB500DA520F /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 8A9F6AC52BA05CB500DA520F /* PBXTargetDependency */, + ); + name = Example; + productName = Example; + productReference = 8A1DFF4F2B9EB4F800B735FF /* Example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8A0A39B22B980BEF006A3B18 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + 8A0A39BA2B980BEF006A3B18 = { + CreatedOnToolsVersion = 15.0; + LastSwiftMigration = 1500; + }; + 8A1DFF4E2B9EB4F800B735FF = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = 8A0A39B52B980BEF006A3B18 /* Build configuration list for PBXProject "IOTBluetooth" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8A0A39B12B980BEF006A3B18; + productRefGroup = 8A0A39BC2B980BEF006A3B18 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8A0A39BA2B980BEF006A3B18 /* IOTBluetooth */, + 8A1DFF4E2B9EB4F800B735FF /* Example */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8A0A39B92B980BEF006A3B18 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 18EF69762C47A0F200AA0A36 /* AIAudio.sbc in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8A1DFF4D2B9EB4F800B735FF /* Resources */ = { + isa = PBXResourcesBuildPhase; + 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 */, + 18EF69772C47A0F200AA0A36 /* AIAudio.sbc in Resources */, + 18113B332C1406CB00E04997 /* 大约在冬季.mp3 in Resources */, + 18113B2F2C13195900E04997 /* galaxy.lzma in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8A0A39B72B980BEF006A3B18 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 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 */, + 189C158B2C05F27C00676A80 /* IBAbout.swift in Sources */, + 189C15972C05FFEE00676A80 /* IBHistoryDistance.swift in Sources */, + 180B019E2BFC9CF400524729 /* IBHistoryBaseTraining.swift in Sources */, + 8A87FBB52BAA85FB00096761 /* IBTrainingData.swift in Sources */, + 8A878FE42BA94D09008C7F26 /* IBChecker.swift in Sources */, + 18884C442C11DBA300E01458 /* IBDeleteFile.swift in Sources */, + 189C15892C05E0CD00676A80 /* IBTarinState.swift in Sources */, + 182DDFD42C5CE68900D44D37 /* IBAiValid.swift in Sources */, + 8A76B60B2BA851730016F2AB /* IBParser.swift in Sources */, + 18EFF59B2C080F14001B1E0E /* IBUserInfo.swift in Sources */, + 8A65F9612BAC0810005C1EA4 /* IBSimulateParser.swift in Sources */, + 180B01982BFC831300524729 /* IBHistoryHeartRate.swift in Sources */, + 189C158D2C05F58A00676A80 /* IBHistoryBloodOxygen.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 */, + 8A9F6ABD2BA0463D00DA520F /* CRC16.swift in Sources */, + 18E7AC942BF4B31000CB9393 /* IBWater.swift in Sources */, + 8A5822CF2BA1A67700590AB6 /* IBAbility.swift in Sources */, + 8A65F9652BAC271B005C1EA4 /* IBScreen.swift in Sources */, + 1859DFA02BFF5A2700112E8C /* IBSendTime.swift in Sources */, + 18EF69792C47A86D00AA0A36 /* IBTools.swift in Sources */, + 8A65F9632BAC1018005C1EA4 /* IBTargetStep.swift in Sources */, + 181441D32C115AC500CCDD46 /* IBFileSender.swift in Sources */, + 8A34B9252BA2A069000F66BD /* IBWatchInfo.swift in Sources */, + 8A0A39D62B980EC9006A3B18 /* IBPeripheralUUID.swift in Sources */, + 189C15952C05FFC700676A80 /* IBHistoryCalorie.swift in Sources */, + 181441CF2C11571400CCDD46 /* IBMp3.swift in Sources */, + 18EF69512C45130200AA0A36 /* IBAiManager.swift in Sources */, + 18204C022C199D0E00F94E69 /* IBFileSender+Ota.swift in Sources */, + 18EC6E282BFF4C6D00757CA8 /* IBPhoneInfo.swift in Sources */, + 18EF69532C45136800AA0A36 /* IBPairling.swift in Sources */, + 18C9589F2C086A9D006A5DE8 /* IBHistorySleep.swift in Sources */, + 180220CE2C2D5D560090D34A /* IBAiResponse.swift in Sources */, + 8A65F95F2BABEC4D005C1EA4 /* IBClock.swift in Sources */, + 8A0A39D22B980E4C006A3B18 /* JSON.swift in Sources */, + 18E7AC962BF5E39900CB9393 /* IBHeartRate.swift in Sources */, + 18C9589B2C082FA7006A5DE8 /* IBWeather.swift in Sources */, + 8A0A39D82B980F58006A3B18 /* UTF8.swift in Sources */, + 181441D12C11575D00CCDD46 /* IBOta.swift in Sources */, + 18E7AC922BF44C6800CB9393 /* IBSleep.swift in Sources */, + 8A0A39CB2B980CFC006A3B18 /* Logger.swift in Sources */, + 189C158F2C05F85200676A80 /* IBHistoryStep.swift in Sources */, + 8A5822D12BA1C80700590AB6 /* LRC.swift in Sources */, + 8A0A39DA2B981037006A3B18 /* Date.swift in Sources */, + 181441D52C116AE500CCDD46 /* IBDial.swift in Sources */, + 18993AA22C073FED006742D2 /* IBBloodOxygen.swift in Sources */, + 3FFE56E72CD624D6000C7415 /* HsFileAgentSdk.swift in Sources */, + 18884C462C12A4EA00E01458 /* IBEpo.swift in Sources */, + 8A87FBB32BAA7F3900096761 /* IBLanguage.swift in Sources */, + 18EF69552C45266D00AA0A36 /* IBReceiveManager.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8A1DFF4B2B9EB4F800B735FF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8A878FE02BA93CF1008C7F26 /* Datas.swift in Sources */, + 8A34B92B2BA2D446000F66BD /* BaseViewController.swift in Sources */, + 8A76B6092BA84C2B0016F2AB /* AbilityViewController.swift in Sources */, + 3FFE56DF2CD6157F000C7415 /* IBManager.swift in Sources */, + 18EF697A2C47A86D00AA0A36 /* IBTools.swift in Sources */, + 3FC0CFCE2CD8634200F97933 /* Logger.swift in Sources */, + 3FFE56E02CD61584000C7415 /* IBBluetoothManager.swift in Sources */, + 8A1DFF562B9EB4F800B735FF /* ViewController.swift in Sources */, + 8A1DFF522B9EB4F800B735FF /* AppDelegate.swift in Sources */, + 8A34B9292BA2AB86000F66BD /* LogViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 8A9F6AC52BA05CB500DA520F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8A0A39BA2B980BEF006A3B18 /* IOTBluetooth */; + targetProxy = 8A9F6AC42BA05CB500DA520F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 8A1DFF572B9EB4F900B735FF /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8A1DFF582B9EB4F900B735FF /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 8A1DFF5C2B9EB4F900B735FF /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8A1DFF5D2B9EB4F900B735FF /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 8A0A39C02B980BEF006A3B18 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 8A0A39C12B980BEF006A3B18 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 8A0A39C32B980BEF006A3B18 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = M33X987285; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Example", + "$(PROJECT_DIR)/IOTBluetooth", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 8A0A39C42B980BEF006A3B18 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = M33X987285; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Example", + "$(PROJECT_DIR)/IOTBluetooth", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; + 8A1DFF602B9EB4F900B735FF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1001; + DEVELOPMENT_TEAM = M33X987285; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Example", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Example/Info.plist; + INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "扫描智能手表需要开启蓝牙功能"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth.Example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 8A1DFF612B9EB4F900B735FF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1001; + DEVELOPMENT_TEAM = M33X987285; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Example", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Example/Info.plist; + INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "扫描智能手表需要开启蓝牙功能"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth.Example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8A0A39B52B980BEF006A3B18 /* Build configuration list for PBXProject "IOTBluetooth" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8A0A39C02B980BEF006A3B18 /* Debug */, + 8A0A39C12B980BEF006A3B18 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8A0A39C22B980BEF006A3B18 /* Build configuration list for PBXNativeTarget "IOTBluetooth" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8A0A39C32B980BEF006A3B18 /* Debug */, + 8A0A39C42B980BEF006A3B18 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8A1DFF622B9EB4F900B735FF /* Build configuration list for PBXNativeTarget "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8A1DFF602B9EB4F900B735FF /* Debug */, + 8A1DFF612B9EB4F900B735FF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8A0A39B22B980BEF006A3B18 /* Project object */; +} diff --git a/code/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/code/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/code/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/code/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/code/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/code/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/Example.xcscheme new file mode 100644 index 0000000..d80a4c0 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/IOTBluetooth.xcscheme b/code/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/IOTBluetooth.xcscheme new file mode 100644 index 0000000..7630f1c --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/IOTBluetooth.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/IOTBluetooth/IOTBluetooth/HsFileAgentSdk.swift b/code/IOTBluetooth/IOTBluetooth/HsFileAgentSdk.swift new file mode 100644 index 0000000..66571e6 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/HsFileAgentSdk.swift @@ -0,0 +1,60 @@ +// +// HsFileAgentSdk.swift +// IOTBluetooth +// +// Created by liangshi on 2024/11/2. +// + +import Foundation + +public typealias IBCompleteHandler = (_ data: Data) -> Void +/// SDK 管理类 +public class HsFileAgentSdk:NSObject { + public static let shared = HsFileAgentSdk() + + private override init() { + super.init() + } + + /// 初始化工作 + public func initSDK() { + Logger.log("MixSdk version:",HsFileAgentSdk.version) + } + + /// 日志 + public var logs: [String] { Logger.logs } + + /// SDK 版本号 + public let version: String = "1.0.0" + /// 默认为 false + public var isDebug: Bool = false { + didSet { Logger.isDebug = isDebug } + } + + /// 清空日志 + public func cleanLog(){ + Logger.logs.removeAll() + } + + /// 文件发送实例 + internal weak var fileSender: IBFileSender? + + + + public func sendToSdk(_ data:Data){ + + } + + public func sdkSendToDevice(_ callback:IBCompleteHandler){ + + } + + func sendToDevice(fileData:Data){ + + } +} + + +extension HsFileAgentSdk { + +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBAbility.swift b/code/IOTBluetooth/IOTBluetooth/IBAbility.swift new file mode 100644 index 0000000..977cd54 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBAbility.swift @@ -0,0 +1,558 @@ +// +// IBAbility.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/13. +// + +import Foundation +/// 主服务 +let serviceUUID: String = "18E0" +/// Notify 特征值 +let notifyCharacteristicUUID: String = "2AE5" +/// Read 特征值 +let readCharacteristicUUID: String = "2AE2" +/// Write 特征值(发送字节数≤20) +let writeCharacteristicUUID: String = "2AE3" +/// Write 特征值(发送字节数>20) +let writeBigCharacteristicUUID: String = "2AE4" +/// Write 特征值(发送文件OAT、MP3等) +let writeFileCharacteristicUUID: String = "2AE0" + +/// 功能指令 +public class IBAbility { + + /// 命令类型 + let type: `Type` + + /// 命令ID + private(set) var id: UInt8 = 0x0 + + /// 指定解析器 + private(set) var parser: IBParser.Type! + + /// 指定参数检查器 + private(set) var checker: IBChecker.Type! + + /// 服务特征 + private(set) var characteristicUUID: String! + + private init(_ type: `Type`) { + self.type = type + } + + private func id(_ value: UInt8) -> Self { + self.id = value + return self + } + + private func parser(_ parser: T.Type) -> Self { + self.parser = parser + return self + } + + private func checker(_ checker: T.Type) -> Self { + self.checker = checker + return self + } + + private func characteristic(_ uuid: String) -> Self { + characteristicUUID = uuid + return self + } + + + + /// 短指令(不大于20个字节) + private static var short: IBAbility { IBAbility(.short).characteristic(writeCharacteristicUUID) } + /// 长指令(大于20个字节) + private static var long: IBAbility { IBAbility(.long).characteristic(writeBigCharacteristicUUID) } + /// 文件指令(用来发送文件,比如表盘、MP3、OTA等) + private static var file: IBAbility { IBAbility(.file).characteristic(writeFileCharacteristicUUID) } + /// ai指令 + private static var ai: IBAbility { IBAbility(.ai).characteristic(writeCharacteristicUUID) } + + public var description: String { "[\(String(format: "0x%02X, 0x%02X", type.rawValue, id))] \(characteristicUUID!)" } + + func isAnswer(_ data: Data) -> Bool { + let answerTypeValue = data.bytes[1] + let answerID = data.bytes[2] + + return type.rawValue == answerTypeValue && id == answerID + } +} + +extension IBAbility { + enum `Type`: UInt8 { + /// 短指令 + case short = 0x01 + /// 长指令 + case long = 0x02 + /// 文件指令(用来发送文件,比如表盘、MP3、OTA等) + case file = 0x04 + /// Ai指令 + case ai = 0x05 + } +} + +public extension IBAbility { + /// 设置用户数据 + /// - Parameter userInfo: see ``IBUserInfo`` + static let userData = IBAbility.long.id(0x01).parser(IBGeneralParser.self).checker(IBUserInfoChecker.self) + + /// 关于本机内容 + static let about = IBAbility.short.id(0x03).parser(IBAblutParser.self) + + /// 切换表盘 + /// - Parameter dialID:Int + /// 表盘id + static let switchDial = IBAbility.short.id(0x02).parser(IBSwitchDialParser.self).checker(IBSwitchDialChecker.self) + + /// APP删除表盘 + /// - Parameter dialID:Int \ + /// 表盘id + static let deleteDial = IBAbility.short.id(0x1B).parser(IBGeneralParser.self).checker(IBSwitchDialChecker.self) + + /// 发送当前表盘ID + /// - Returns: Int \ + /// Read from ``IBResponse``.value + static let fetchCurrentDialId = IBAbility.short.id(0x17).parser(IBFetchCurrentDialParser.self) + + /// 查找手表 + /// - Parameter action: + /// 0 = 停止查找 \ + /// 1 = 开始查找 + static let find = IBAbility.short.id(0x04).parser(IBFindParser.self).checker(IBActionChecker.self) + + /// 开关消息推送功能 + /// - Parameter action: + /// 0 = 关闭消息 \ + /// 1 = 开启消息 + static let message = IBAbility.short.id(0x08).parser(IBGeneralParser.self).checker(IBActionChecker.self) + + /// 开启指定运动模式 + /// - Parameter motionMode: see ``IBMotionMode`` + static let startSport = IBAbility.short.id(0x0A).parser(IBGeneralParser.self).checker(IBSportChecker.self) + + /// 结束运动模式 + static let endSport = IBAbility.short.id(0x0B).parser(IBGeneralParser.self) + + /// 设置训练实时数据采样频率 + /// - Parameter value:Int \ + /// 单位(秒) + static let trainFrequency = IBAbility.short.id(0x14).parser(IBGeneralParser.self).checker(IBTrainFrequencyChecker.self) + + /// 训练暂停/恢复 + /// - Parameter action: + /// 0 = 暂停 \ + /// 1 = 恢复 + static let trainPauseResume = IBAbility.short.id(0x16).parser(IBGeneralParser.self).checker(IBActionChecker.self) + + /// 设置语言 + /// - Parameter language: see ``IBLanguage`` + static let language = IBAbility.short.id(0x0E).parser(IBGeneralParser.self).checker(IBLanguageChecker.self) + + /// 获取手表心率参数 + /// - Returns: ``IBHeartRate`` \ + /// Read from ``IBResponse``.value + static let fetchHeartRate = IBAbility.short.id(0x1E).parser(IBHeartRateParser.self) + + /// 设置手表心率参数 + /// - Parameter heartRate: see ``IBHeartRate`` + static let heartRate = IBAbility.long.id(0x12).parser(IBGeneralParser.self).checker(IBHeartRateChecker.self) + + /// 获取手表血氧设置 + /// - Returns: ``IBBloodOxygen`` \ + /// Read from ``IBResponse``.value + static let fetchBloodOxygen = IBAbility.short.id(0x1F).parser(IBBloodOxygenParser.self) + + /// 设置手表血氧参数 + /// - Parameter heartRate: see ``IBBloodOxygen`` + static let bloodOxygen = IBAbility.long.id(0x13).parser(IBGeneralParser.self).checker(IBBloodOxygenChecker.self) + + /// 获取实时心率(实时训练数据) + /// - Returns: ``IBTrainingData`` \ + /// Read from ``IBResponse``.value + static let currentHeartRate = IBAbility.short.id(0x0F).parser(IBTrainingDataParser.self) + + /// 开启/关闭自动获取实时心率 + /// - Parameter action: + /// 0 = 关闭自动获取 \ + /// 1 = 开启自动获取 + static let heartRateMode = IBAbility.short.id(0x11).parser(IBGeneralParser.self).checker(IBActionChecker.self) + + /// 设置最高心率提醒 + /// - Parameter value:Int \ + /// 最高不超过 250 ,如果需要关闭最高心率提醒,可将最高心率设为255,开启则设正常值即可。 + static let heartRateHigh = IBAbility.short.id(0x15).parser(IBGeneralParser.self).checker(IBHeartRateHighChecker.self) + + /// 手势拍照 + /// - Parameter action: + /// 0 = 退出手势拍照 \ + /// 1 = 进入手势拍照 + static let takePhoto = IBAbility.short.id(0x23).parser(IBTakePhotoParser.self).checker(IBActionChecker.self) + + /// 恢复出厂设置 + static let restore = IBAbility.short.id(0x25).parser(IBGeneralParser.self) + + /// 重新启动 + static let reboot = IBAbility.short.id(0x26).parser(IBGeneralParser.self) + + /// 关机 + static let shutdown = IBAbility.short.id(0x27).parser(IBGeneralParser.self) + + /// 获取手表电池电量。 + /// - Returns: Int \ + /// Read from ``IBResponse``.value + static let fetchBattery = IBAbility.short.id(0x07).parser(IBBatteryParser.self) + + /// 获取闹钟设置 + /// - Returns: ``[IBClockInfo]`` \ + /// Read from ``IBResponse``.items + static let fetchClock = IBAbility.short.id(0x01).parser(IBClockParser.self) + + /// 发送闹钟设置 + /// - Parameter clock: see ``IBClock`` + static let setClock = IBAbility.long.id(0x21).parser(IBSetClockParser.self).checker(IBClockChecker.self) + + /// 获取所有表盘UID + /// - Returns: [Int] \ + /// Read from ``IBResponse``.items + static let fetchDials = IBAbility.short.id(0x2A).parser(IBFetchDialsParser.self) + + /// APP获取某表盘ID是否已安装 + /// - Parameter value:Int + /// - Returns: Int + /// 1 = 已安装 \ + /// 2 = 未安装 \ + /// Read from ``IBResponse``.value + static let checkDialID = IBAbility.long.id(0x0D).parser(IBCheckDialParser.self).checker(IBSwitchDialChecker.self) + + /// 获取当前手表状态 + /// - Returns: Int \ + /// 0 = 空闲状态 \ + /// 9 = USB存储状态 \ + /// 10 = 运动训练状态 \ + /// Read from ``IBResponse``.value + static let fetchWatchState = IBAbility.short.id(0x19).parser(IBWatchStateParser.self) + + /// 获取手表屏幕参数 + /// - Returns: ``IBScreen`` \ + /// Read from ``IBResponse``.value + static let fetchScreen = IBAbility.short.id(0x1A).parser(IBScreenParser.self) + + /// 设置手表屏幕参数 + /// - Parameter screen: see ``screen`` + static let screen = IBAbility.long.id(0x0E).parser(IBGeneralParser.self).checker(IBScreenChecker.self) + + /// 获取手表声音与震动参数 + /// - Returns: ``IBVibrateSound`` \ + /// Read from ``IBResponse``.value + static let fetchVibrateSound = IBAbility.short.id(0x1C).parser(IBVibrateSoundParser.self) + + /// APP设置手表声音与震动参数 + /// - Parameter vibrateSound: see ``IBVibrateSound`` + static let vibrateSound = IBAbility.long.id(0x0F).parser(IBGeneralParser.self).checker(IBVibrateSoundChecker.self) + + + /// 设置手表辅助功能参数 + /// - Parameter auxiliary: see ``IBAuxiliary`` + static let auxiliary = IBAbility.long.id(0x11).parser(IBGeneralParser.self).checker(IBAuxiliaryChecker.self) + + /// 获取手表辅助功能参数 + /// - Returns: IBAuxiliary \ + /// Read from ``IBResponse``.value + static let fetchAuxiliary = IBAbility.short.id(0x1D).parser(IBFetchAuxiliaryParser.self) + + /// 获取目标步数设置 + /// - Returns: ``IBTargetStep`` \ + /// Read from ``IBResponse``.value + static let fetchTargetStep = IBAbility.short.id(0x2B).parser(IBTargetStepParser.self) + + /// 设置目标步数 + /// - Parameter targetStep: see ``IBTrainTarget`` + static let targetStep = IBAbility.long.id(0x16).parser(IBGeneralParser.self).checker(IBTargetStepChecker.self) + + /// 获取其他目标设置 + /// - Returns: ``IBOtherTarget`` \ + /// Read from ``IBResponse``.value + static let fetchOtherTarget = IBAbility.short.id(0x2C).parser(IBOtherTargetParser.self) + + /// 设置其他目标 + /// - Parameter targetOther: see ``IBOtherTarget`` + static let targetOther = IBAbility.long.id(0x17).parser(IBGeneralParser.self).checker(IBOtherTargetChecker.self) + + /// 获取睡眠设置 + /// - Returns: ``IBSleep`` \ + /// Read from ``IBResponse``.value + static let fetchSleep = IBAbility.short.id(0x29).parser(IBSleepParser.self) + + /// 设置睡眠 + /// - Parameter sleep: see ``IBSleep`` + static let sleep = IBAbility.long.id(0x15).parser(IBGeneralParser.self).checker(IBSleepChecker.self) + + /// 获取手表喝水提醒设置 + /// - Returns: Int \ + /// 0 = 关闭 \ + /// 1 = 开启 \ + /// Read from ``IBResponse``.value + static let fetchWater = IBAbility.short.id(0x24).parser(IBWaterParser.self) + + /// 设置喝水提醒 + /// - Parameter water: see ``IBWater`` + static let water = IBAbility.long.id(0x14).parser(IBGeneralParser.self).checker(IBWaterChecker.self) + + /// 设置手表公英制单位 + /// - Parameter value:Int \ + /// 0 = 公制 \ + /// 1 = 英制 + static let unit = IBAbility.short.id(0x28).parser(IBGeneralParser.self).checker(IBValueChecker.self) + + /// 设置训练目标 + /// - Parameter trainTarget:see ``IBTrainTarget`` + static let trainTarget = IBAbility.long.id(0x04).parser(IBGeneralParser.self).checker(IBTrainTargetChecker.self) + + // MARK: - 同步数据 + /// 同步心率历史数据 + /// - Parameter value: + /// 0 = 今天 \ + /// 1 = 前1天 \ + /// 2 = 前2天 \ + /// 3 = 前3天 \ + /// 依此类推,取值范围0~29。 + /// - Returns: [IBHistoryHeartRateItem] \ + /// Read from ``IBResponse``.items + static let syncHeartRate = IBAbility.short.id(0x40).parser(IBSyncHeartRateParser.self).checker(IBValueChecker.self) + + /// 同步血氧历史数据 + /// - Parameter value: + /// 0 = 今天 \ + /// 1 = 前1天 \ + /// 2 = 前2天 \ + /// 3 = 前3天 \ + /// 依此类推,取值范围0~29。 + /// - Returns: [IBHistoryBloodOxygenItem] \ + /// Read from ``IBResponse``.items + static let syncBloodOxygen = IBAbility.short.id(0x41).parser(IBSyncBloodOxygenParser.self).checker(IBValueChecker.self) + + /// 同步历史步数 + /// - Parameter value: + /// 0 = 今天 \ + /// 1 = 前1天 \ + /// 2 = 前2天 \ + /// 3 = 前3天 \ + /// 依此类推,取值范围0~29。 + /// - Returns: [IBHistoryStepItem] \ + /// Read from ``IBResponse``.items + static let syncStepCount = IBAbility.short.id(0x43).parser(IBSyncStepParser.self).checker(IBValueChecker.self) + + /// 同步燃脂时间历史数据 + /// - Parameter value: + /// 0 = 今天 \ + /// 1 = 前1天 \ + /// 2 = 前2天 \ + /// 3 = 前3天 \ + /// 依此类推,取值范围0~29。 + /// - Returns: [IBHistoryBurningItem] \ + /// Read from ``IBResponse``.items + static let syncFatBurning = IBAbility.short.id(0x44).parser(IBSyncBurningParser.self).checker(IBValueChecker.self) + + /// 同步站立历史数据 + /// - Parameter value: + /// 0 = 今天 \ + /// 1 = 前1天 \ + /// 2 = 前2天 \ + /// 3 = 前3天 \ + /// 依此类推,取值范围0~29。 + /// - Returns: [IBHistoryStandItem] \ + /// Read from ``IBResponse``.items + static let syncStand = IBAbility.short.id(0x45).parser(IBSyncStandParser.self).checker(IBValueChecker.self) + + /// 同步卡路里历史数据 + /// - Parameter value: + /// 0 = 今天 \ + /// 1 = 前1天 \ + /// 2 = 前2天 \ + /// 3 = 前3天 \ + /// 依此类推,取值范围0~29。 + /// - Returns: [IBHistoryCalorieItem] \ + /// Read from ``IBResponse``.items + static let syncCalorie = IBAbility.short.id(0x46).parser(IBSyncCalorieParser.self).checker(IBValueChecker.self) + + /// 同步距离历史数据 + /// - Parameter value: + /// 0 = 今天 \ + /// 1 = 前1天 \ + /// 2 = 前2天 \ + /// 3 = 前3天 \ + /// 依此类推,取值范围0~29。 + /// - Returns: [IBHistoryDistanceItem] \ + /// Read from ``IBResponse``.items + static let syncDistance = IBAbility.short.id(0x47).parser(IBSyncDistanceParser.self).checker(IBValueChecker.self) + + /// 同步基础训练历史数据 + /// - Parameter value: + /// 0 = 今天 \ + /// 1 = 前1天 \ + /// 2 = 前2天 \ + /// 3 = 前3天 \ + /// 依此类推,取值范围0~29。 + /// - Returns: [IBHistoryBaseTrainingItem] \ + /// Read from ``IBResponse``.items + static let syncTrain = IBAbility.short.id(0x49).parser(IBSyncTrainParser.self).checker(IBValueChecker.self) + + /// 同步睡眠历史数据 + /// - Parameter value: + /// 0 = 今天 \ + /// 1 = 前1天 \ + /// 2 = 前2天 \ + /// 3 = 前3天 \ + /// 依此类推,取值范围0~29。 + /// - Returns: [IBHistorySleepItem] \ + /// Read from ``IBResponse``.items + static let syncSleep = IBAbility.short.id(0x4B).parser(IBSyncSleepParser.self).checker(IBValueChecker.self) + + /// 同步路径坐标集历史数据 + /// - Parameter value:开始训练时间戳 + /// - Returns: [IBHistoryGpsItem] \ + /// Read from ``IBResponse``.items + static let syncGPS = IBAbility.long.id(0x4F).parser(IBSyncGpsParser.self).checker(IBSyncGpsChecker.self) + + /// 发送同步数据结束消息 + static let sendSyncEnd = IBAbility.short.id(0x2E).parser(IBGeneralParser.self) + + /// 获取SD卡剩余空间 + /// - Returns: Int \ + /// Read from ``IBResponse``.value + static let disk = IBAbility.short.id(0x05).parser(IBDiskParser.self) + + /// 发送手机信息 + /// - Returns: Int \ + /// 0 = 指令校验失败 \ + /// 1 = 手表未与任何手机绑定 \ + /// 2 = 手表已与该手机绑定 \ + /// 3 = 手表已与其他手机绑定 \ + /// Read from ``IBResponse``.value + static let sendPhone = IBAbility.long.id(0x10).parser(IBSendPhoneParser.self).checker(IBSendPhoneChecker.self) + + /// 发送当前时间 + /// - Parameter sendTime: see ``IBSendTime`` + static let sendTime = IBAbility.long.id(0x02).parser(IBGeneralParser.self).checker(IBSendTimeChecker.self) + + /// 发送解除配对消息 + static let unpair = IBAbility.short.id(0x10).parser(IBGeneralParser.self) + + /// 发送取消解除配对消息 + static let sendUnpair = IBAbility.short.id(0x2D).parser(IBGeneralParser.self) + + /// 发送天气数据 + /// - Parameter weather: see ``IBWeather`` + static let sendWeather = IBAbility.long.id(0x20).parser(IBGeneralParser.self).checker(IBWeatherChecker.self) + + /// 发送通讯录 + /// - Parameter addressBook: see ``IBAddressBook`` + /// 联系人个数0~20 + static let addressBook = IBAbility.long.id(0x0B).parser(IBGeneralParser.self).checker(IBAddressBookChecker.self) + + // MARK: - 文件 + + /// 发送MP3文件信息 + /// - Parameter mp3: see ``IBMp3File`` + static let mp3 = IBAbility.file.id(0x21).parser(IBGeneralParser.self).checker(IBMp3Checker.self) + +// /// 发送自定义表盘文件汇总信息 +// /// - Parameter customDial: see ``IBCustomDial`` +// static let customDial = IBAbility.file.id(0x22).parser(IBGeneralParser.self).checker(IBCustomDialChecker.self) + + /// 发送表盘文件汇总信息 + /// - Parameter dial: see ``IBDial`` + static let dial = IBAbility.file.id(0x22).parser(IBGeneralParser.self).checker(IBDialChecker.self) + + /// 发送单个表盘文件信息 + /// - Parameter dialFile: see ``IBDailFile`` + internal static let dialFile = IBAbility.file.id(0x24).parser(IBGeneralParser.self).checker(IBDialFileChecker.self) + + /// 发送文件校验码 + /// - Parameter value:Int + internal static let fileCheckCode = IBAbility.file.id(0x23).parser(IBGeneralParser.self).checker(IBValueChecker.self) + + /// 删除指定文件 + /// - Parameter deleteFile: see ``IBDeleteFile`` + static let deleteFile = IBAbility.long.id(0x22).parser(IBGeneralParser.self).checker(IBDeleteFileChecker.self) + + /// 删除指定类型所有文件(当前只支持 MP3 和 表盘) + /// - Parameter file: see ``IBFile`` + static let deleteFiles = IBAbility.short.id(0x22).parser(IBGeneralParser.self).checker(IBFileChecker.self) + + /// 发送OTA文件信息 + /// - Parameter ota: see ``IBOtaFile`` + static let ota = IBAbility.file.id(0x20).parser(IBGeneralParser.self).checker(IBOtaChecker.self) + + + /// 发送EPO文件信息 + /// - 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 和 表盘) + /// - Parameter file: see ``IBFile`` + /// - Returns: IBFetchFile \ + /// Read from ``IBResponse``.value + static let fetchFiles = IBAbility.short.id(0x21).parser(IBFetchFilesParser.self).checker(IBFileChecker.self) + + + /// 询问AI模型是否可用回执 + /// - Parameter aiValid:IBAiValid + internal static let aiValid = IBAbility(.ai).characteristic(writeBigCharacteristicUUID).id(0x06).parser(IBGeneralParser.self).checker(IBAiValidChecker.self) + + /// 发送Ai准备状态回执 + /// - Parameter value:Int + /// 0 = 失败 \ + /// 1 = 成功 \ + internal static let aiReady = IBAbility.ai.id(0x05).parser(IBGeneralParser.self).checker(IBValueChecker.self) + + /// 获取Ai录音文件 + /// - Parameter value:Data \ AI录音文件内容(pcm格式) + /// Read from ``IBResponse``.value + internal static let fetchAiAudio = IBAbility.short.id(0x34).parser(IBfetchAiAudioParser.self) + + /// 发送Ai回应内容 + /// - Parameter aiResponse: see ``IBAiResponse`` + internal static let aiResponse = IBAbility.long.id(0x1f).parser(IBGeneralParser.self).checker(IBAiReponseChecker.self) +} + +extension IBAbility { + + + // + // + // // MARK: - 私有 + // + // /// 获取当前数据(jSON格式)【不使用】 + // static let var681B9E92 = IBAbility.short.id(0x06) + // /// 开关多媒体功能(播放手机音乐、通话等)【不使用】 + // static let var681254AC = IBAbility.short.id(0x09) + // /// 进入工厂测试模式【不使用】 + // static let varD3D21DC8 = IBAbility.short.id(0x30) + // /// 同步压力历史数据【不使用】 + // static let varA2A25CD8 = IBAbility.short.id(0x42) + // + // /// 修改手表蓝牙名称【未使用】 + // static let var8FA46D73 = IBAbility.long.id(0x03) + // /// 同步基础训练实时心率记录【不使用】 + // static let var6CFBF636 = IBAbility.long.id(0x4B) + // /// 同步当天任意时间之后的基础训练记录【不使用】 + // static let var4D0264A9 = IBAbility.long.id(0x4D) + // /// APP发送靶心率区间值给手表(暂不用) + // static let var7EE39B21 = IBAbility.long.id(0x0C) + // + // /// 发送模块升级信息(04,26)【不使用】 + // static let var1FFA68BC = IBAbility.file.id(0x26) + // ///  发送模块单个文件升级信息(04,27)【不使用】 + // static let var9AD723C3 = IBAbility.file.id(0x27) + // /// 发送EPO文件信息(04,25)(旧版,不用) + // static let var4F791698 = IBAbility.file.id(0x25) +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBAiManager.swift b/code/IOTBluetooth/IOTBluetooth/IBAiManager.swift new file mode 100644 index 0000000..ba96e93 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBAiManager.swift @@ -0,0 +1,196 @@ +//// +//// IBAiManager.swift +//// IOTBluetooth +//// +//// Created by incar on 2024/7/15. +//// +// +//import Foundation +////import MixSdk +//class IBAiManager{ +// static let shared = IBAiManager() +// var audioData = Data() +// +// var reply = "" +// +// init(){ +// MixSdk.shared.isDebug = true +// setupChatCallBack() +// } +// +// func aiValid(_ data:Data){ +// let result = IBGeneralParser.dataPacket(data) +// let code = result.int +// let desc = ["文心一言","ChatGPT","KIMI",":豆包"] +// Logger.log("询问模型是否可用:",desc[code]) +// let model = IBAiValid() +// model.isValid = true +// if code == 1 { +// model.key = "sk-proj-04ogvrZQqyaiv1OWw8OZT3BlbkFJG9Gyr8oFvRdmr3oXQNbX" +// } +// IBSender() +// .ability(IBAbility.aiValid) +// .parameters([.aiValid(model)]) +// .response { result in +// +// } +// +// } +// func aiRequest(_ data:Data){ +// let result = IBGeneralParser.dataPacket(data) +// let code = result.int +// let desc = ["文心一言","ChatGPT","KIMI",":豆包"] +// Logger.log("使用模型:",desc[code]) +// IBSender() +// .ability(IBAbility.aiReady) +// .parameters([.value(1)]) +// .response {[weak self] result in +// self?.fectchAiAudio() +// } +// +// +// var platform = MSPlatform.doubao +// var key = "" +// if code == 0 { +// platform = .yiyan +// } +// else if code == 1 { +// platform = .chatGPT +// key = "sk-proj-04ogvrZQqyaiv1OWw8OZT3BlbkFJG9Gyr8oFvRdmr3oXQNbX" +//// let gptModel = "gpt-4-turbo" +//// MixSdk.shared.chatGpt.changeModel(model:gptModel ) +// } +// else if code == 2 { +// platform = .kimi +// } +// else if code == 3 { +// platform = .doubao +// } +// MixSdk.shared.setPlatform(platform,key: key) +// } +// func fectchAiAudio(){ +// IBSender() +// .ability(IBAbility.fetchAiAudio) +// .parameters([]) +// .response {[weak self] result in +// guard let self = self else {return} +// if result.isSuccess,let data = result.value as? Data{ +// receiveAiData(sbcData: data) +// } +// else{ +// Logger.log(result) +// let model = IBAiResponse() +// model.type = 0 +// sendRelpy(model: model) +// } +// } +// } +// +// +//} +//extension IBAiManager{ +// func receiveAiData(sbcData:Data){ +// let pcmData = MSAudioConvert.sbcTopcm(sbcData) +// if pcmData.count == 0{ +// Logger.log("sbc2pcm失败") +// let model = IBAiResponse() +// model.type = 0 +// sendRelpy(model: model) +// return +// } +// guard let pcmPath = IBTools.saveToFile(data: pcmData) else {return} +// MixSdk.shared.stt.sentenceRecognize(filePath: pcmPath,format:"pcm",sample_rate:"16000",lang: "zh-cn") {[weak self] text, error in +// guard let self = self else {return} +// if let text = text,text.count > 0{ +// Logger.log(text) +// let model = IBAiResponse() +// model.type = 1 +// model.value = text +// sendRelpy(model: model) +// +// MixSdk.shared.chat.send(content: text) +// } +// else{ +// Logger.log(error ?? "") +// let model = IBAiResponse() +// model.type = 0 +// sendRelpy(model: model) +// } +// } +// } +// +// +// func setupChatCallBack(){ +// let chat = MixSdk.shared.chat +// chat.callBack = {[weak self] model in +// guard let self = self else {return} +// print("\(model.type) : \(model.content ?? "")") +// +// if model.type == "start" { +// reply = "" +// } +// else if model.type == "end" { +//// let model = IBAiResponse() +//// model.type = 2 +//// model.value = reply +//// sendRelpy(model: model) +// } +// else if model.type == "middle"{ +//// reply += model.content ?? "" +// let data = IBAiResponse() +// data.type = 2 +// data.value = model.content ?? "" +// sendRelpy(model: data) +// } +// } +// } +// +// func sendRelpy(model:IBAiResponse){ +// IBSender() +// .ability(IBAbility.aiResponse) +// .parameters([.aiResponse(model)]) +// .response { result in +// Logger.log(result) +// } +// } +//} +// +//extension IBAiManager{ +// func loginAction() { +// MixSdk.shared.isDebug = true +// let pwd = "aWGKrBWaijsKD5eEsVVU5rjWPI1sVy"//注册获得的密码 +// self.startLogin(password:pwd) +// } +// func startLogin(password:String) { +// MixSdk.shared.isDebug = true +// let deviceId = "281106AFB0F7" +// let productKey = "HHosiYUNdG42t1cTgKOzIUPOvAVMI2QT" +// +// MixSdk.shared.auth.login(productKey:productKey,deviceId: deviceId, password: password){[weak self] token,error in +// if let err = error{ +// if case .reponseError(let code,let message) = err,code == 50021{ +// //50021是账号或密码错误.可以在这里调用注册,重新注册后,之前的密码会被覆盖,APP更新保存的新密码 +// self?.registerAction() +// } +// } +// else{ +// print(token ?? "") +// } +// } +// } +// @objc func registerAction() { +// let deviceId = "281106AFB0F7" +// let productId = "pafOCKLiUbeVHbdZlddH" +// let productKey = "HHosiYUNdG42t1cTgKOzIUPOvAVMI2QT" +// MixSdk.shared.auth.register(productId: productId, productKey: productKey, deviceId: deviceId){[weak self] password,error in +// if let pwd = password{ +// print(pwd) +// //deviceId和password APP要确保这个一一对应。 +// self?.startLogin(password: pwd) +// } +// else{ +// print(error!) +// } +// } +// } +//} diff --git a/code/IOTBluetooth/IOTBluetooth/IBChecker.swift b/code/IOTBluetooth/IOTBluetooth/IBChecker.swift new file mode 100644 index 0000000..e414359 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBChecker.swift @@ -0,0 +1,467 @@ +// +// IBChecker.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/19. +// + +import Foundation + +/// 参数检查器协议 +protocol IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] +} + +extension Array where Element == IBParameter { + subscript(_ key: IBKey) -> Element? { + self.filter({ $0.key == key }).first + } +} + +struct IBSwitchDialChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .dialID(dailID) = parameters[.dialID] else { fatalError("缺少参数: dialID") } + Logger.log(dailID) + return dailID.byte2 + } +} + +struct IBActionChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .action(action) = parameters[.action] else { fatalError("缺少参数: action") } + Logger.log(action) + return [action.byte] + } +} + +struct IBSportChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .motionMode(mode) = parameters[.motionMode] else { fatalError("缺少参数: motionMode") } + Logger.log(mode) + return [mode.rawValue.byte] + } +} + +struct IBLanguageChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .language(value) = parameters[.language] else { fatalError("缺少参数: language") } + Logger.log(value) + return [value.rawValue.byte] + } +} +struct IBAuxiliaryChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .auxiliary(value) = parameters[.auxiliary] else { fatalError("缺少参数: auxiliary") } + Logger.log(value) + return [value.rawValue.byte] + } +} +struct IBValueChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .value(value) = parameters[.value] else { fatalError("缺少参数: value") } + Logger.log(value) + return [value.byte] + } +} + +struct IBHeartRateHighChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .value(value) = parameters[.value] else { fatalError("缺少参数: value") } + Logger.log(value) + return [(value == 0 ? 255 : value).byte] + } +} + +struct IBTrainFrequencyChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .value(value) = parameters[.value] else { fatalError("缺少参数: value") } + Logger.log(value) + return value.byte2 + } +} + +struct IBSyncGpsChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .value(value) = parameters[.value] else { fatalError("缺少参数: value") } + Logger.log(value) + return value.byte4 + } +} + +struct IBFileChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .file(value) = parameters[.file] else { fatalError("缺少参数: file") } + Logger.log(value) + return [value.rawValue.byte] + } +} +struct IBOtaChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .ota(value) = parameters[.ota] else { fatalError("缺少参数: ota") } + guard let data = value.encode else { fatalError("json解析异常: ota") } + Logger.log(value.jsonString ?? "") + return data.bytes + } +} + +struct IBAiReponseChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .aiResponse(value) = parameters[.aiResponse] else { fatalError("缺少参数: aiReponse") } + Logger.log(value.description) + + var data = [value.type.byte] + if let value = value.value.data(using: .utf8),!value.isEmpty { + data += value.count.byte2 + data += value.bytes + } + return data + } +} +struct IBAiValidChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .aiValid(value) = parameters[.aiValid] else { fatalError("缺少参数: aiValid") } + Logger.log(value.description) + + var data = value.isValid ? [1.byte]:[0.byte] + if value.isValid == true,let value = value.key?.data(using: .utf8),!value.isEmpty { + data += value.bytes + } + return data + } +} +struct IBTargetStepChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .targetStep(value) = parameters[.targetStep] else { fatalError("缺少参数: targetStep") } + Logger.log(value.description) + return value.value.byte4 + [value.ring.byte] + [value.vibrate.byte] + [value.notice.byte] + } +} + +struct IBSleepChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .sleep(value) = parameters[.sleep] else { fatalError("缺少参数: sleep") } + Logger.log(value.description) + if value.sleepTimeSet == 1,let sleep = value.sleepTime,let getUp = value.getUpTime { + return [value.sleepTimeSet.byte] + [sleep.hour.byte] + [sleep.minute.byte] + [getUp.hour.byte] + [getUp.minute.byte] + [value.alertBeforeMin20.byte] + } + return [value.sleepTimeSet.byte] + } +} +struct IBWaterChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { +// guard case let .water(value) = parameters[.water] else { fatalError("缺少参数: water") } +// Logger.log(value.description) +// +// var data = [value.alertState.byte] +// if value.alertState == 1 { +// data = data + [value.alertType.byte] + value.drinkingValue.byte2 + [value.start.hour.byte] + [value.start.minute.byte] +// } +// if value.alertType == 1 { +// data = data + [value.end.hour.byte] + [value.end.minute.byte] + [value.frequency.rawValue.byte] +// } +// return data + + guard case let .water(value) = parameters[.water] else { fatalError("缺少参数: water") } + Logger.log(value.description) + + if value.alertState == 0 { + return [value.alertState.byte] + } + var data = [value.alertState.byte] + [value.alertType.byte] + value.drinkingValue.byte2 + [value.start.hour.byte] + [value.start.minute.byte] + if value.alertType == 1 { + data = data + [value.end.hour.byte] + [value.end.minute.byte] + [value.frequency.rawValue.byte] + } + return data + } +} + +struct IBHeartRateChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .heartRate(value) = parameters[.heartRate] else { fatalError("缺少参数: heartRate") } + Logger.log(value.description) + var data = [value.rate.rawValue.byte] + [value.state.byte] + if value.state == 1 { + data = data + [value.higherOrLower.byte] + [value.value.byte] + } + return data + } +} +struct IBBloodOxygenChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .bloodOxygen(value) = parameters[.bloodOxygen] else { fatalError("缺少参数: heartRate") } + Logger.log(value.description) + var data = [value.rate.rawValue.byte] + [value.state.byte] + if value.state == 1 { + data = data + [value.value.byte] + } + return data + } +} + + +struct IBOtherTargetChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .otherTarget(value) = parameters[.otherTarget] else { fatalError("缺少参数: otherTarget") } + Logger.log(value.description) + var data = value.calorie.byte2 + [value.mileage.byte] + [value.stand.byte] + [value.burning.byte] + [value.ringsState.byte] + [value.vibrateState.byte] + data = data + [value.noticeState.byte] + return data + } +} + +struct IBVibrateSoundChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .vibrateSound(value) = parameters[.vibrateSound] else { fatalError("缺少参数: IBVibrateConfig") } + Logger.log(value.description) + if value.type == 0{ + if let systemValue = value.systemValue { + return [value.type.byte] + [systemValue.byte] + } + else{ + fatalError("参数错误: IBScreenConfig") + } + } + else if value.type == 1{ + if let ringsValue = value.ringsValue{ + return [value.type.byte] + [ringsValue.byte] + } + else{ + fatalError("参数错误: IBScreenConfig") + } + } + else if value.type == 3{ + if let ringsState = value.ringsState,let vibrateState = value.vibrateState{ + return [value.type.byte] + [ringsState.byte] + [vibrateState.byte] + } + else{ + fatalError("参数错误: IBScreenConfig") + } + } + return [] + } +} +struct IBScreenChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .screen(value) = parameters[.screen] else { fatalError("缺少参数: IBScreenConfig") } + Logger.log(value.description) + if value.type == 0{ + if let brightness = value.brightness { + return [value.type.byte] + [brightness.byte] + } + else{ + fatalError("参数错误: IBScreenConfig") + } + } + else{ + if let duration = value.duration,let wrist = value.wrist { + return [value.type.byte] + [duration.rawValue.byte] + [wrist.byte] + } + else{ + fatalError("参数错误: IBScreenConfig") + } + } + } +} +struct IBUserInfoChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .userInfo(value) = parameters[.userInfo] else { fatalError("缺少参数: userInfo") } + Logger.log(value.description) + var data = [value.age.byte] + [value.gender.byte] + [value.height.byte] + value.weight.byte2 + value.step.byte4 + value.burning.byte2 + data = data + [value.stand.byte] + [value.hourSystem.byte] + return data + } +} + + +struct IBClockChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .clock(value) = parameters[.clock] else { fatalError("缺少参数: clock") } + guard let data = value.encode else { fatalError("json解析异常: clock") } + Logger.log(value.jsonString ?? "") + + return data.bytes + } +} +struct IBTrainTargetChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .trainTarget(value) = parameters[.trainTarget] else { fatalError("缺少参数: trainTarget") } + guard let data = value.encode else { fatalError("json解析异常: trainTarget") } + Logger.log(value.jsonString ?? "") + + return data.bytes + } +} +struct IBSendPhoneChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .sendPhone(value) = parameters[.sendPhone] else { fatalError("缺少参数: sendPhone") } + guard let data = value.encode else { fatalError("json解析异常: sendPhone") } + Logger.log(value.jsonString ?? "") + + return data.bytes + } +} +struct IBSendTimeChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .sendTime(value) = parameters[.sendTime] else { fatalError("缺少参数: sendTime") } + guard let data = value.encode else { fatalError("json解析异常: sendTime") } + Logger.log(value.jsonString ?? "") + return data.bytes + } +} +struct IBWeatherChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .weather(value) = parameters[.weather] else { fatalError("缺少参数: weather") } + guard let data = value.encode else { fatalError("json解析异常: weather") } + Logger.log(value.jsonString ?? "") + return data.bytes + } +} +struct IBAddressBookChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .addressBook(list) = parameters[.addressBook] else { fatalError("缺少参数: addressBook") } + if list.count > 20 { fatalError("参数: addressBook,最多不能超过20个联系人") } + var data = [list.count.byte] + var des = "[ " + for item in list { + des += item.description + //性别 + data = data + [item.gender.byte] + //姓名 + if let name = item.name,let value = name.data(using: .utf8),!value.isEmpty { + data += [value.count.byte] + data += value.bytes + } + else{ + data += [0.byte] + } + //电话号码个数 + data += [item.phoneNumbers.count.byte] + if item.phoneNumbers.count > 2 { fatalError("参数: addressBook,电话号码书数不能超过两个") } + + for phone in item.phoneNumbers { + //电话号码 + if let value = phone.number.data(using: .utf8),!value.isEmpty { + data += [value.count.byte] + data += value.bytes + } + else{ + data += [0.byte] + } + //归属地 + if let value = phone.location?.data(using: .utf8),!value.isEmpty { + data += [value.count.byte] + data += value.bytes + } + else{ + data += [0.byte] + } + //运营商 + if let value = phone.operator?.data(using: .utf8),!value.isEmpty { + data += [value.count.byte] + data += value.bytes + } + else{ + data += [0.byte] + } + } + } + des += " ]" + Logger.log(des) + + return data + } +} + +// MARK: - 文件传输 +struct IBMp3Checker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .MP3(value) = parameters[.mp3] else { fatalError("缺少参数: MP3") } + guard let data = value.encode else { fatalError("json解析异常: MP3") } + Logger.log(value.jsonString ?? "") + return data.bytes + } +} +struct IBEpoChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + 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 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 { + data += [name.count.byte] + data += name + } + else{ + data += [0.byte] + } + if let list = value.files{ + data += [list.count.byte] + } + else{ + data += [0.byte] + } + return data + } +} +struct IBDialFileChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .dialFile(value) = parameters[.dialFile] else { fatalError("缺少参数: dialFile") } + Logger.log(value.description) + var data = value.UID.byte2 + [value.index.byte] + [value.fileType.byte] + if let name = value.fileName.data(using: .utf8),!name.isEmpty { + data += [name.count.byte] + data += name + } + else{ + data += [0.byte] + } + data += value.size.byte4 + return data + } +} + +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.rawValue.byte] + for uid in value.uids { + data += uid.byte2 + } + return data + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBFileSender+Dial.swift b/code/IOTBluetooth/IOTBluetooth/IBFileSender+Dial.swift new file mode 100644 index 0000000..672927b --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBFileSender+Dial.swift @@ -0,0 +1,165 @@ +// +// 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() + HsFileAgentSdk.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 + Logger.log("\(total) : \(current)") + self.progress?(self.total,self.current) + self.sendContentDial() + } + else{ + completion(.response.code(.failure).message("发送文件超时,发送失败!")) + self.remove() + } + } + self.lastData = model + HsFileAgentSdk.shared.sendToDevice(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/code/IOTBluetooth/IOTBluetooth/IBFileSender+Mp3.swift b/code/IOTBluetooth/IOTBluetooth/IBFileSender+Mp3.swift new file mode 100644 index 0000000..90e4e32 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBFileSender+Mp3.swift @@ -0,0 +1,88 @@ +// +// 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() + HsFileAgentSdk.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 + Logger.log("\(total) : \(current)") + self.progress?(self.total,self.current) + self.sendContentMp3() + } + else{ + completion(.response.code(.failure).message("发送文件超时,发送失败!")) + self.remove() + } + } + self.lastData = model + HsFileAgentSdk.shared.sendToDevice(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/code/IOTBluetooth/IOTBluetooth/IBFileSender+Ota.swift b/code/IOTBluetooth/IOTBluetooth/IBFileSender+Ota.swift new file mode 100644 index 0000000..100c8da --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBFileSender+Ota.swift @@ -0,0 +1,87 @@ +// +// 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() + HsFileAgentSdk.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 + Logger.log("\(total) : \(current)") + self.progress?(self.total,self.current) + self.sendContentOta() + } + else{ + completion(.response.code(.failure).message("发送文件超时,发送失败!")) + self.remove() + } + } + self.lastData = model + HsFileAgentSdk.shared.sendToDevice(fileData: data) + } + else{ + Logger.log( "文件内容发送结束...") + startTimer() + } + } + func sendOtaLrc(){ + IBSender() + .ability(.fileCheckCode) + .parameters([.value(fileList.first!.lrc)]) + .response {[weak self] result in + guard let self = self else {return} + if result.isSuccess{ + completion(result) + self.remove() + } + else{ + completion(.response.code(.failure).message("发送文件校验码失败")) + self.remove() + } + } + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBFileSender.swift b/code/IOTBluetooth/IOTBluetooth/IBFileSender.swift new file mode 100644 index 0000000..f36d7bf --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBFileSender.swift @@ -0,0 +1,261 @@ +// +// IBFileSender.swift +// IOTBluetooth +// +// Created by incar on 2024/6/6. +// + +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() + } + } + + func sendFileFinish(){ + if let _ = self.mp3Info{ + sendMp3Lrc() + } + else if let _ = self.otaInfo { + sendOtaLrc() + } + else if let _ = self.dialInfo{ + sendDialLrc() + } +// else if let _ = self.marketInfo{ +// sendDialLrc() +// } + // else if let path = self.epoInfo.filePath { + // filePath = path + // } + } +} + + + +extension IBFileSender{ + func start(){ + if let info = self.mp3Info{ + self.sendMp3(info) + } + else if let info = self.otaInfo { + self.sendOta(info) + } + else if let info = self.dialInfo{ + self.sendDial(info) + } +// else if let info = self.marketInfo{ +// self.sendDial(info) +// } + // else if let path = self.epoInfo.filePath { + // filePath = path + // } + } + func readFile(from path:String) ->([Data]?,Int,Int){ + let fileURL = URL(fileURLWithPath: path) + do { + let data = try Data(contentsOf:fileURL) + let seg = 495 + var list = [Data]() + for i in stride(from: 0, to: data.count, by: seg) { + if i+seg < data.count { + list.append(data[i ... i+seg - 1]) + } + else{ + list.append(data[i ... data.count - 1]) + } + } + let lrc = LRC.calculateLRC(data.bytes).int + return (list,data.count,lrc) + } catch { + return (nil,0,0) + } + } +} + +public extension IBFileSender{ + @discardableResult + /// 发送OTA + /// - Parameters: + /// - filePath: 文件路径 + /// - info: ota信息 + /// - Returns: self + func ota(_ info:IBSendOta) ->Self{ + self.otaInfo = info + return self + } + @discardableResult + + + /// 发送mp3 + /// - Parameter info: mp3信息 + /// - Returns: Self + func mp3(_ info:IBSendMp3) ->Self{ + self.mp3Info = info + return self + } + +// /// 发送自定义表盘 +// /// - Parameter info: 表盘信息 +// /// - Returns: Self +// func customDial(_ info:IBCustomDial) ->Self{ +// if let list = info.files,list.count > 0{ +// for i in 0...list.count - 1 { +// list[i].index = i +// list[i].UID = info.UID +// } +// } +// self.customInfo = info +// return self +// } + /// 发送表盘 + /// - Parameter info: 表盘信息 + /// - Returns: Self + func dial(_ info:IBDial) ->Self{ +// IBMarketDial + + if let list = info.files,list.count > 0{ + for i in 0...list.count - 1{ + list[i].index = i + list[i].UID = info.UID + } + } + + self.dialInfo = info + return self + } + + /// 发送EPO文件 + /// - Parameters: + /// - filePath: 文件路径 + /// - info: epo信息 + /// - Returns: self + // func epo(filePath:URL,info:[IBEpo]) ->Self{ + // self.filePath = filePath + // self.epoInfo = info + // return self + // } + + /// 进度 + @discardableResult + func progress(_ value: @escaping IBProgressHandler) -> Self { + progress = value + return self + } + + /// 执行与响应处理 + @discardableResult + func response(_ handler: @escaping IBCompletionHandler) -> Self { + if Self.all.count > 1{ + handler(.response.code(.failure).message("当前有文件正在传输中...")) + self.remove() + return self + } + + completion = handler + self.start() + return self + } + + + /// 撤销当前的文件传输 + func revokeFileSend(_ handler: @escaping IBCompletionHandler){ + for item in Self.all{ + item.cancelSend = true + } + + IBSender() + .ability(.revokeFileSend) + .parameters([.file(.mp3)]) + .response { result in + handler(result) + self.remove() + } + } +} + +extension IBFileSender { + static var all: [IBFileSender] = [] + func remove() { + Self.all.removeAll { $0 === self } + } +} + +extension IBFileSender { + static var timer: Timer? + func startTimer() { + deinitTimer() + + Self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false, block: { [unowned self] _ in + deinitTimer() + self.completion(.response.code(.failure).message("发送文件失败")) + self.remove() + }) + Logger.log("IBFileSender创建定时器") + } + + func deinitTimer() { + guard let _ = Self.timer else { return } + Self.timer?.invalidate() + Self.timer = nil + + Logger.log("IBFileSender销毁定时器") + + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBKeeper.swift b/code/IOTBluetooth/IOTBluetooth/IBKeeper.swift new file mode 100644 index 0000000..53b8a37 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBKeeper.swift @@ -0,0 +1,104 @@ +// +// IBKeeper.swift +// UnifyBluetooth +// +// Created by yaozuopan on 2024/2/5. +// + +import Foundation +import CoreBluetooth + +class UBDevice: Codable { + let uuid: String + let name: String + let vendors: Int + + var battery: Int + var rssi: Int + + init(_ uuid: String, vendors: Int, name: String, battery: Int, rssi: Int) { + self.uuid = uuid + self.name = name + self.vendors = vendors + self.battery = battery + self.rssi = rssi + } +} + +/// 数据保存 +struct IBKeeper { + static let saver = UserDefaults.standard + + private static let serviceKey = "IBKeeper.Services" + private static let connectedKey = "IBKeeper.Connected" + + /// 记录最新的连接设备 + static func lastConnected(_ peripheral: IBPeripheral?) { + guard let peripheral = peripheral else { return } + addServices(peripheral.peripheral.services) + addConnected(peripheral) + } + + /// 保存蓝牙服务特征 + private static func addServices(_ services: [CBService]?) { + guard let services = services?.map({ $0.uuid.uuidString }) else { return } + + var recordServices = saver.stringArray(forKey: serviceKey) ?? [] + recordServices.append(contentsOf: services) + recordServices = Array(Set(recordServices)) + saver.setValue(recordServices, forKey: serviceKey) + } + + /// 记录已连接过的设备 + private static func addConnected(_ peripheral: IBPeripheral) { + var recordConnected: [UBDevice] = [] + if let jsonData = saver.data(forKey: connectedKey) { + recordConnected = JSON.decode([UBDevice].self, from: jsonData) ?? [] + } + + if let device = recordConnected.filter({ $0.uuid == peripheral.uuid.uuidString }).first { + device.battery = peripheral.battery + device.rssi = peripheral.rssi + } else { + recordConnected.append(UBDevice(peripheral.uuid.uuidString, + vendors: peripheral.vendors.rawValue, + name: peripheral.peripheral.name ?? "", + battery: peripheral.battery, + rssi: peripheral.rssi)) + } + + saver.setValue(recordConnected.jsonData, forKey: connectedKey) + } + + /// 从已连接记录中移除已解绑设备 + static func removeConnected(_ uuid: UUID?) { + guard let uuid = uuid else { return } + + var recordConnected: [UBDevice] = [] + if let jsonData = saver.data(forKey: connectedKey) { + recordConnected = JSON.decode([UBDevice].self, from: jsonData) ?? [] + } + + recordConnected.removeAll { $0.uuid == uuid.uuidString } + saver.setValue(recordConnected.jsonData, forKey: connectedKey) + } + + /// 已连接过设备的服务特征列表 + static var services: [CBUUID] { + (saver.stringArray(forKey: serviceKey) ?? []).map { CBUUID(string: $0) } + } + + /// 已连接过的设备列表 + static var connected: [IBPeripheral] { + guard let jsonData = saver.data(forKey: connectedKey) else { return [] } + let recordConnected = JSON.decode([UBDevice].self, from: jsonData) ?? [] + return recordConnected.map { + IBPeripheral(nil, uuid: $0.uuid, name: $0.name, vendors: $0.vendors, rssi: $0.rssi, battery: $0.battery) } + } + + /// 已连接过的设备UUID列表 + static var connectedUUIDs: [UUID] { + guard let jsonData = saver.data(forKey: connectedKey) else { return [] } + return (JSON.decode([UBDevice].self, from: jsonData) ?? []).compactMap { UUID(uuidString: $0.uuid) } + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBNotify.swift b/code/IOTBluetooth/IOTBluetooth/IBNotify.swift new file mode 100644 index 0000000..cd5b516 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBNotify.swift @@ -0,0 +1,244 @@ +// +// IBNotify.swift +// +// +// Created by Harvey on 2020/1/5. +// Copyright © 2020 姚作潘/Harvey. All rights reserved. +// + +import Foundation + +fileprivate let center = NotificationCenter.default + +fileprivate class Weakly: NSObject { + weak var owner: NSObjectProtocol? + var delegateObservers: [NSObjectProtocol] + init(_ owner: NSObjectProtocol, delegateObservers: [NSObjectProtocol] = []) { + self.owner = owner + self.delegateObservers = delegateObservers + } +} + +extension IBNotify { + + fileprivate struct Keeper { + static var observers: [Weakly] = [] + static func remove(_ observer: NSObjectProtocol) { + observers + .filter { $0.owner === observer || isNil($0.owner) } + .forEach { + $0.delegateObservers.forEach { center.removeObserver($0) } + } + observers.removeAll { isNil($0.owner) || $0.owner === observer } + } + + static func append(_ delegateObserver: NSObjectProtocol, owner: NSObjectProtocol) { + guard let weakly = observers.filter({ $0.owner === owner }).first else { + observers.append(Weakly(owner, delegateObservers: [delegateObserver])) + return + } + weakly.delegateObservers.append(delegateObserver) + } + } + + public struct Name { + fileprivate let rawValue: String + init(_ rawValue: String) { + self.rawValue = rawValue + } + } +} + +public class IBNotify { + + init() {} + + private(set) var name: Notification.Name! + @discardableResult + func name(_ value: IBNotify.Name) -> Self { + name = Notification.Name(rawValue: value.rawValue) + return self + } + + private(set) var observer: NSObjectProtocol! + @discardableResult + func observer(_ value: NSObjectProtocol) -> Self { + observer = value + return self + } + + private(set) var queue: OperationQueue = .main + @discardableResult + func queue(_ value: OperationQueue) -> Self { + queue = value + return self + } + + private(set) var object: Any? + @discardableResult + func object(_ value: Any) -> Self { + object = value + return self + } + + func subscribe(_ value: @escaping (Notification) -> Void) { + if isNil(observer) || isNil(name) { + fatalError("observer or name must be not nil") + } + + let delegateObserver = center + .addObserver(forName: name, + object: object, + queue: queue, + using: value) + Keeper.append(delegateObserver, owner: observer) + } + + static func remove(_ observer: NSObjectProtocol) { + center.removeObserver(observer) + Keeper.remove(observer) + } +} + +extension IBNotify { + static func post(_ name: IBNotify.Name, + object: Any? = nil, + userInfo: [AnyHashable : Any] = [:]) { + center.post(name: Notification.Name(rawValue: name.rawValue), + object: object, userInfo: userInfo) + } +} + +//public extension IBManager { +// +// /// 订阅通知 +// static func subscribe(_ name: IBNotify.Name, +// observer: NSObjectProtocol!, +// receive: @escaping (_ object: Any?, _ userInfo: [String: Any]) -> Void) { +// IBNotify() +// .name(name) +// .observer(observer) +// .subscribe { notification in +// receive(notification.object, notification.userInfo as? [String: Any] ?? [:]) +// } +// } +// +// /// 移除订阅通知的观察者 +// static func remove(_ observer: NSObjectProtocol) { +// IBNotify.remove(observer) +// } +//} + +public extension IBNotify.Name { + /// 比如电量、信号强度 +// static let deviceInfoUpdate = IBNotify.Name("IOTBluetooth.DeviceInfoUpdate") + + /// 连接设备发生变动,一种情况断开连接,一种是切换设备 +// static let connectedDeviceDidChange = IBNotify.Name("IOTBluetooth.DeviceConnected") + + /// 鉴权状态改变,通过object 获取状态,0准备鉴权。1等待手表同意连接。 2等待手机确认配对请求。3鉴权成功。4鉴权失败。 +// static let authStateDidChange = IBNotify.Name("IOTBluetooth.AuthStateDidChange") + + /// 发现设备 +// static let deviceHasFound = IBNotify.Name("IOTBluetooth.DeviceHasFound") + + + /// 连接状态发生改变。通过object 获取状态,0断开连接。1连上设备。 + static let connectStateDidChange = IBNotify.Name("IOTBluetooth.ConnectStateDidChange") + + /// 远程拍照动作。通过object获取状态。2进行手势拍照,3退出 + static let takePhoto = IBNotify.Name("IOTBluetooth.TakePhoto") + + /// 手表请求天气数据 + static let weather = IBNotify.Name("IOTBluetooth.weather") + + /// 找到设备 + static let didFindDevice = IBNotify.Name("IOTBluetooth.FindDevice") + + /// 训练状态变化,通过object获取状态IBTarinState对象 + static let trainStateDidChanged = IBNotify.Name("IOTBluetooth.TrainStateChanged") + + /// 获取手表推送的训练实时数据,通过object获取状态IBTrainingData对象 + static let trainingData = IBNotify.Name("IOTBluetooth.TrainingData") + + /// 手表发送文件已接收完成 + internal static let fileReceiveFinish = IBNotify.Name("IOTBluetooth.TileReceiveFinish") +} + +extension IBNotify { + /// 处理手表推送过来的通知 + static func handle(_ data: Data) -> Bool { + //拍照 + if IBAbility.takePhoto.isAnswer(data) { + let result = IBGeneralParser.dataPacket(data) + let code = result[0].int + if code == 2 || code == 3 { + post(.takePhoto, object:code) + } + return true + } + //找到设备 + if IBAbility.find.isAnswer(data) { + var bytes = data.bytes + bytes.removeLast() + let code = bytes.last!.int + if code == 2 { + post(.didFindDevice) + } + return true + } + //请求天气 + if data.bytes[1] == 0x05 && data.bytes[2] == 0x03 { + post(.weather) + return true + } + //表盘安装状态 + if(data.bytes[1] == 0x05 && data.bytes[2] == 0x04){ + let result = IBGeneralParser.dataPacket(data) + let code = result.int + let desc = ["安装失败","安装成功"] + Logger.log("表盘安装状态:",desc[code]) + return true + } + //手表询问APP AI模型是否可用 + if(data.bytes[1] == 0x05 && data.bytes[2] == 0x06){ +// IBAiManager.shared.aiValid(data) + return true + } + //AI录音已准备好 + if(data.bytes[1] == 0x05 && data.bytes[2] == 0x05){ +// IBAiManager.shared.aiRequest(data) + return true + } + //文件已接收完成 + if data.bytes[1] == 0x05 && data.bytes[2] == 0x20 { + let result = IBGeneralParser.dataPacket(data) + HsFileAgentSdk.shared.fileSender?.sendFileResult(result[0].int == 1) + return true + } + //训练状态变化 + if data.bytes[1] == 0x05 && data.bytes[2] == 0x50 { + let result = IBGeneralParser.dataPacket(data) + let model = IBTarinState(result.data) + post(.trainStateDidChanged, object: model) + return true + } + //训练实时数据 + if IBAbility.currentHeartRate.isAnswer(data) { + let dataPacket = IBGeneralParser.dataPacket(data) + if let model = IBTrainingData(dataPacket.data) { + post(.trainingData, object: model) + } + return true + } + //配对 + if IBPairing.checkPairing(data){ + return true + } + return false + } + + + + +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBPairling.swift b/code/IOTBluetooth/IOTBluetooth/IBPairling.swift new file mode 100644 index 0000000..69d2b8c --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBPairling.swift @@ -0,0 +1,72 @@ +// +// IBPairling.swift +// IOTBluetooth +// +// Created by incar on 2024/7/15. +// + +import Foundation + +class IBPairing{ + static func checkPairing(_ data:Data)->Bool{ + if data.bytes[1] == 0x05 && data.bytes[2] == 0x11 { + let result = IBGeneralParser.dataPacket(data) + let code = result[0].int + let desc = ["拒绝配对","确认配对","确认解绑其他手机","其他"] + Logger.log("收到确认配对消息:",desc[code]) + return true + } + if data.bytes[1] == 0x05 && data.bytes[2] == 0x1 { + //解绑 + let result = IBGeneralParser.dataPacket(data) + let code = result[0].int + let desc = ["拒绝解绑","确认解绑"] + Logger.log("收到确认解绑消息 ",desc[code]) + return true + } + if data.bytes[1] == 0x05 && data.bytes[2] == 0x10 { + //配对结果 + var result = IBGeneralParser.dataPacket(data) + let code = result[0].int + if code == 1{ + result.removeFirst() + UserDefaults.standard.setValue(result, forKey: "IOTBletooth.mac") + UserDefaults.standard.synchronize() + + Logger.log("收到配对成功消息",result) + //发送时间 + IBPairing.sendTime() + } + else{ + Logger.log("收到配对失败消息") +// IBManager.completeHandler?(false) +// IBManager.completeHandler = nil + } + + return true + } + return false + } + + static func sendTime(){ + let date = Date() + let model = IBSendTime() + model.time = Int(date.timeIntervalSince1970) + model.week = date.weekDay.rawValue - 1 + model.year = date.year - 2000 + model.month = date.month + model.day = date.day + model.hour = date.hour + model.minute = date.minute + model.second = date.second + + IBSender() + .ability(IBAbility.sendTime) + .parameters([.sendTime(model)]) + .response { result in + Logger.log(result) +// IBManager.completeHandler?(true) +// IBManager.completeHandler = nil + } + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBParameter.swift b/code/IOTBluetooth/IOTBluetooth/IBParameter.swift new file mode 100644 index 0000000..853f8a4 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBParameter.swift @@ -0,0 +1,216 @@ +// +// IBParameter.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/19. +// + +import Foundation + +enum IBKey: String { + case dialID = "dialID" + case action = "action" + case motionMode = "motionMode" + case language = "language" + case value = "value" + case file = "file" + case targetStep = "targetStep" + case sleep = "sleep" + case water = "water" + case clock = "clock" + case heartRate = "heartRate" + case bloodOxygen = "bloodOxygen" + case screen = "screen" + case vibrateSound = "vibrateSound" + case otherTarget = "otherTarget" + case trainTarget = "trainTarget" + case sendPhone = "sendPhone" + case sendTime = "sendTime" + case auxiliary = "auxiliary" + case userInfo = "userInfo" + case weather = "weather" + case addressBook = "addressBook" + case epo = "epo" + case ota = "ota" + case mp3 = "mp3" + case dial = "dial" + case dialFile = "dialFile" + case deleteFile = "deleteFile" + case fetchFils = "fetchFils" + case aiValid = "aiValid" + case aiResponse = "aiResponse" +} + +/// 参数 +public enum IBParameter { + /// 表盘ID + case dialID(Int) + /// 1=(开始、启用、开启),0=(停止、禁用、关闭) + case action(Int) + /// 运动模式 + case motionMode(IBMotionMode) + /// 语言 + case language(IBLanguage) + /// 通用值设置 + case value(Int) + /// 文件 + case file(IBFile) + /// 训练目标 + case targetStep(IBTargetStep) + /// 睡眠 + case sleep(IBSleep) + /// 喝水 + case water(IBWater) + /// 设置闹钟 + case clock(IBClock) + /// 设置心率 + case heartRate(IBHeartRate) + /// 血氧 + case bloodOxygen(IBBloodOxygen) + /// 设置屏幕 + case screen(IBScreenConfig) + /// 设置声音和震动 + case vibrateSound(IBVibrateConfig) + /// 设置目标设置 + case otherTarget(IBOtherTarget) + /// 设置训练目标 + case trainTarget(IBTrainTarget) + /// 发送手机信息 + case sendPhone(IBPhoneInfo) + /// 发送当前时间 + case sendTime(IBSendTime) + /// 设置辅助功能 + case auxiliary(IBAuxiliary) + /// 设置用户数据 + case userInfo(IBUserInfo) + /// 发送天气数据 + case weather(IBWeather) + /// 发送通讯录 + case addressBook([IBAddressBook]) + /// 发送EPO文件信息 + case epo([IBEpo]) + /// 发送OTA文件信息 + case ota(IBOtaFile) + /// 发送mp3文件信息 + case MP3(IBMp3File) + /// 发送表盘信息汇总 + case dial(IBDial) + /// 发送单个表盘文件信息 + case dialFile(IBDialFile) + /// 删除指定文件 + case deleteFile(IBDeleteFile) + /// 获取指定类型文件目录 + case fetchFils(IBFile) + /// 发送Ai是否可用回执 + case aiValid(IBAiValid) + /// 发送Ai回应内容 + case aiResponse(IBAiResponse) + + var key: IBKey { + switch self { + case .dialID(_): return .dialID + case .action(_): return .action + case .motionMode(_): return .motionMode + case .language(_): return .language + case .value(_): return .value + case .file(_): return .file + case .targetStep(_): return .targetStep + case .sleep(_): return .sleep + case .water(_): return .water + case .clock(_): return .clock + case .heartRate(_): return .heartRate + case .bloodOxygen(_): return .bloodOxygen + case .screen(_): return .screen + case .vibrateSound(_): return .vibrateSound + case .auxiliary(_): return .auxiliary + case .otherTarget(_): return .otherTarget + case .trainTarget(_): return .trainTarget + case .sendPhone(_): return .sendPhone + case .sendTime(_): return .sendTime + case .userInfo(_): return .userInfo + case .weather(_): return .weather + case .addressBook(_): return .addressBook + case .epo(_): return .epo + case .ota(_): return .ota + case .MP3(_): return .mp3 + case .dial(_): return .dial + case .dialFile(_): return .dialFile + case .deleteFile(_): return .deleteFile + case .fetchFils(_): return .fetchFils + case .aiValid(_): return .aiValid + case .aiResponse(_): return .aiResponse + } + } +} + +public enum IBMotionMode: Int { + /// 健走 + case walking = 0 + /// 跑步 + case running + /// 骑行 + case ride +} + +public enum IBFile: Int,Codable { + /// OTA 文件 + case ota = 1 + /// MP3 文件 + case mp3 + /// 表盘信息 + case dial + /// OTA(W4) + case ota_w4 + ///EPO + case epo +} +//0:运动 +//1:心率 +//2:血氧监测 +//3:训练 +//4:训练记录 +//5:指南针 +//6:天气 +//7:音乐 +//8:电话 +//9:录音 +//10:秒表 +//11:计时器 +//12:闹钟 +//13:蓝牙 +//14:睡眠 +//15:设置 +public enum IBAuxiliary: Int { + /// 运动 + case sport = 0 + /// 心率 + case heart + /// 血氧监测 + case bloodOxygen + /// 训练 + case train + /// 训练记录 + case tarinRecord + /// 指南针 + case compass + /// 天气 + case weather + /// 音乐 + case music + /// 电话 + case mobile + /// 录音 + case record + /// 秒表 + case secondTimer + /// 计时器 + case timer + /// 闹钟 + case clock + /// 蓝牙 + case bluetooth + /// 睡眠 + case sleep + /// 设置 + case setting +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBParser.swift b/code/IOTBluetooth/IOTBluetooth/IBParser.swift new file mode 100644 index 0000000..f77524f --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBParser.swift @@ -0,0 +1,345 @@ +// +// IBParser.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/18. +// + +import Foundation + +/// 蓝牙数据解析协议 +protocol IBParser { + /// 解析数据 + static func parse(_ data: Data) -> IBResponse +} + +extension IBParser { + static func dataPacket(_ data: Data) -> [UInt8] { + let dataLength = data[7...10].int + return data.suffix(from: 17).prefix(dataLength).map({ $0 }) + } +} + +struct IBBatteryParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + var bytes = data.bytes + bytes.removeLast() + return .response.code(.success).value(bytes.last!.int) + } +} + +struct IBSwitchDialParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + var bytes = data.bytes + bytes.removeLast() + let code = bytes.last!.int + let desc = ["指令校验失败", "切换成功", "无该表盘资源", "切换失败"] + return .response.code(code == 1 ? .success:.failure).message(desc[code]).value(code) + } +} +struct IBFetchCurrentDialParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let bytes = dataPacket(data) + let value = bytes.int + return .response.code(.success).value(value) + } +} +struct IBFetchAuxiliaryParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let bytes = dataPacket(data) + guard let value = IBAuxiliary(rawValue: bytes.int) else {return .response.code(.parseError) } + return .response.code(.success).value(value) + } +} + +struct IBCheckDialParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + + let code = dataPacket(data).int + let desc = ["指令校验失败", "已安装", "未安装"] + return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code) + } +} + +/// 通用解析(1 Byte) +struct IBGeneralParser: IBParser { + static func parse(_ data: Data) -> IBResponse { +// guard data.isEmpty == false else { return IBSingleByteSimulateParser.parse() } + + let code = dataPacket(data).int + return .response.code(code == 1 ? .success:.failure) + } +} +struct IBTakePhotoParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let code = dataPacket(data).int + let desc = ["指令校验失败", "操作成功", "进行手势拍照","退出"] + return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code) + } +} +struct IBWaterParser: IBParser { + static func parse(_ data: Data) -> IBResponse { +// guard data.isEmpty == false else { return IBSingleByteSimulateParser.parse() } + + let code = dataPacket(data).int + let desc = code == 0 ? "关闭":"开启" + return .response.code(.success).value(code).message(desc) + } +} +struct IBFindParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let code = dataPacket(data).int + let desc = ["指令校验失败", "操作成功", "手表已找到,停止查找"] + return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code) + } +} +struct IBSendPhoneParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let code = dataPacket(data).int + let desc = ["指令校验失败","手表未与任何手机绑定", "手表已与该手机绑定", "手表已与其他手机绑定"] + return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code) + } +} + + +struct IBWatchStateParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + var bytes = data.bytes + bytes.removeLast() + let code = bytes.last!.int + let desc = [0:"空闲状态", 9:"USB存储状态", 10:"运动训练状态"] + return .response.code(.success).value(code).message(desc[code] ?? "") + } +} + + +struct IBDiskParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let code = dataPacket[0].int + if code == 1 { + var value: Int = 0 + value = dataPacket.suffix(from: 1).map({ $0 }).int + return .response.code(.success).value(value) + } + else { + return .response.code(.failure) + } + } +} + +struct IBTrainingDataParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + if dataPacket.count == 0 { + return .response.code(.success) + } + guard let model = IBTrainingData(dataPacket.data) else { return .response.code(.parseError) } + return .response.code(.success).value(model) + } +} +struct IBHeartRateParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBHeartRate(dataPacket.data) + Logger.log(model.description) + return .response.code(.success).value(model) + } +} +struct IBBloodOxygenParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBBloodOxygen(dataPacket.data) + Logger.log(model.description) + return .response.code(.success).value(model) + } +} + +struct IBSyncTrainParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + guard let model = JSON.decode(IBHistoryBaseTraining.self, from: dataPacket.data) else { return .response.code(.parseError) } + return .response.code(.success).items(model.list) + +// let dataPacket = baseData.jsonString()! +// guard let model = JSON.decode(IBHistoryBaseTraining.self, from: dataPacket.data!) else { return .response.code(.parseError) } +// Logger.log(model.jsonString ?? "") +// return .response.code(.success).items(model.list) + } +} +struct IBSyncHeartRateParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBHistoryHeartRate(dataPacket.data) +// Logger.log(model.list) + return .response.code(.success).items(model.list) + } +} +struct IBSyncBloodOxygenParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBHistoryBloodOxygen(dataPacket.data) +// Logger.log(model.list) + return .response.code(.success).items(model.list) + } +} +struct IBSyncStepParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBHistoryStep(dataPacket.data) +// Logger.log(model.list) + return .response.code(.success).items(model.list) + } +} +struct IBSyncBurningParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBHistoryBurning(dataPacket.data) +// Logger.log(model.list) + return .response.code(.success).items(model.list) + } +} +struct IBSyncStandParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBHistoryStand(dataPacket.data) +// Logger.log(model.list) + return .response.code(.success).items(model.list) + } +} +struct IBSyncCalorieParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBHistoryCalorie(dataPacket.data) +// Logger.log(model.list) + return .response.code(.success).items(model.list) + } +} +struct IBSyncDistanceParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBHistoryDistance(dataPacket.data) +// Logger.log(model.list) + return .response.code(.success).items(model.list) + } +} +struct IBSyncSleepParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBHistorySleep(dataPacket.data) +// Logger.log(model.list) + return .response.code(.success).items(model.list) + } +} +struct IBSyncGpsParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBHistoryGps(dataPacket.data) +// Logger.log(model.list) + 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 { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + guard let model = JSON.decode(IBAbout.self, from: dataPacket.data) else { return .response.code(.parseError) } + Logger.log(model.jsonString ?? "") + return .response.code(.success).value(model) + } +} +struct IBClockParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + guard let model = JSON.decode(IBClock.self, from: dataPacket.data) else { return .response.code(.parseError) } + Logger.log(model.jsonString ?? "") + return .response.code(.success).items(model.alarms) + } +} +struct IBSetClockParser: IBParser { + static func parse(_ data: Data) -> IBResponse { +// guard data.isEmpty == false else { return IBClockSimulateParser.parse() } + let code = dataPacket(data).int + return .response.code(code == 1 ? .success:.failure) + } +} + +struct IBFetchDialsParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let num = dataPacket[0].int + + var list:[Int] = [] + for i in 0.. IBResponse { + let dataPacket = dataPacket(data) + guard let model = IBTargetStep(dataPacket.data) else { return .response.code(.parseError) } + + Logger.log(model.description) + return .response.code(.success).value(model) + } +} +struct IBSleepParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBSleep(dataPacket.data) + Logger.log(model.description) + return .response.code(.success).value(model) + } +} +struct IBScreenParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBScreen(dataPacket.data) + Logger.log(model.description) + return .response.code(.success).value(model) + } +} +struct IBVibrateSoundParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBVibrateSound(dataPacket.data) + Logger.log(model.description) + return .response.code(.success).value(model) + } +} +struct IBOtherParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBVibrateSound(dataPacket.data) + Logger.log(model.description) + return .response.code(.success).value(model) + } +} +struct IBOtherTargetParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data) + let model = IBOtherTarget(dataPacket.data) + Logger.log(model.description) + return .response.code(.success).value(model) + } +} + +struct IBfetchAiAudioParser: IBParser { + static func parse(_ data: Data) -> IBResponse { + let dataPacket = dataPacket(data).data + return .response.code(.success).value(dataPacket) + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBReceiveManager.swift b/code/IOTBluetooth/IOTBluetooth/IBReceiveManager.swift new file mode 100644 index 0000000..e8ad4a0 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBReceiveManager.swift @@ -0,0 +1,113 @@ +// +// IBReceiveManager.swift +// IOTBluetooth +// +// Created by incar on 2024/7/15. +// + +import Foundation +import CoreBluetooth + + +@objc protocol IBReceiveManagerDelegate { + func receiveManagerDidReceiveFullData(characteristic: CBCharacteristic,_ data:Data) +} + +class IBReceiveManager{ + var delegate:IBReceiveManagerDelegate? + static let shared = IBReceiveManager() + + var headData = Data() + var lastData = Data() + var totalData = Data() + + + func receice(characteristic: CBCharacteristic){ + guard let data = characteristic.value else { return } + if(data.count == 2 && data.int == 0){ +// Logger.log("收到0000指令异常") + return + } + + if data.count >= 18,data[0] == 0x72{ + let mark = characteristic.isNotifying ? "Notify Read" : "Read" + let uuid = characteristic.uuid.uuidString + let pkgTotal = data[3...6].int + let dataTotal = data[7...10].int + let pkgIndex = data[11...14].int + let pkgLen = data[15...16].int + + Logger.log(mark, uuid, "->", "pkgTotal = \(pkgTotal),dataTotal = \(dataTotal),pkgIndex = \(pkgIndex),pkgLen = \(pkgLen)",data.joined(" ")) + +// if data[1] == 0x01,data[2] == 0x34{ +// //ai语音 + if pkgIndex == 1{ + headData = data.prefix(17) + lastData = data.suffix(1) + } + + totalData = totalData + data.suffix(from: 17).prefix(pkgLen) + if pkgTotal == pkgIndex{ + Logger.log("total = \(dataTotal),receive = \(totalData.count)") + let fullData = headData + totalData + lastData + didGetFullData(characteristic: characteristic, data: fullData) + } +// } +// else{ +// didGetFullData(characteristic: characteristic, data: data) +// } + } + else{ + Logger.log( data.joined(" ")) + didGetFullData(characteristic: characteristic, data: data) + } + } + + func didGetFullData(characteristic: CBCharacteristic,data:Data){ +// Logger.log( data.joined(" ")) + + delegate?.receiveManagerDidReceiveFullData(characteristic: characteristic, data) + totalData = Data() + + if characteristic.uuid.uuidString == readCharacteristicUUID { + let info = IBWatchInfo(data) + sendDeviceInfo() + } + + if IBNotify.handle(data) { +// Logger.log("通知消息") + return + } + } + + func sendDeviceInfo(){ + let mac = UserDefaults.standard.object(forKey: "IOTBletooth.mac") as? [Int] ?? [] + let model = IBPhoneInfo() + model.mac = mac + IBSender() + .ability(IBAbility.sendPhone) + .parameters([.sendPhone(model)]) + .response { result in + Logger.log(result) + if result.code != .success{ + //通知连接失败 + + return + } + guard let value = result.value as? Int else {return} + if value == 2 { + //通知连接成功 + + } + else if value == 1{ + //等待手表同意连接 + Logger.log("等待手表同意连接") + } + else if value == 3{ + //手表已与其他手机绑定 + + } + } + } +} + diff --git a/code/IOTBluetooth/IOTBluetooth/IBSender.swift b/code/IOTBluetooth/IOTBluetooth/IBSender.swift new file mode 100644 index 0000000..3a192b7 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBSender.swift @@ -0,0 +1,140 @@ +// +// IBSender.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/15. +// + +import Foundation + +public typealias IBCompletionHandler = (_ result: IBResponse) -> Void + + +/// 蓝牙消息发送类 +public class IBSender { + public init() { + Self.all.append(self) + } + + var ability: IBAbility! + var parameters: [IBParameter] = [] + var completion: IBCompletionHandler! + + var dataPacket: [[UInt8]] { + var totalData: [UInt8] = [] + if isValue(ability.checker) { + totalData = ability.checker.check(parameters) + } + + let list = splitData(totalData) + if list.count > 1{ + var newList = [[UInt8]]() + /// 分包数 + let packageCount = list.count + /// 数据包总长度 + let dataTotalLength = totalData.count + /// 数据包长度 + let dataLength = list[0].count + /// 数据包索引 + let index = 1 + + var bytes: [UInt8] = [0x73, ability.type.rawValue, ability.id] + bytes = bytes + packageCount.byte4 + dataTotalLength.byte4 + index.byte4 + dataLength.byte2 + bytes = bytes + list[0] + [LRC.calculateLRC(list[0])] + + newList.append(bytes) + newList.append(contentsOf: list[1...list.count - 1]) + + return newList + } + else{ + /// 分包数 + let packageCount = 1 + /// 数据包总长度 + let dataTotalLength = totalData.count + /// 数据包长度 + let dataLength = totalData.count + /// 数据包索引 + let index = 1 + + var bytes: [UInt8] = [0x73, ability.type.rawValue, ability.id] + bytes = bytes + packageCount.byte4 + dataTotalLength.byte4 + index.byte4 + dataLength.byte2 + + if totalData.count > 0 { + bytes = bytes + totalData + bytes = bytes + [LRC.calculateLRC(totalData)] + } else { + bytes = bytes + [0x00] + } + + return [bytes] + } + } + + private func splitData(_ listData:[UInt8])->[[UInt8]]{ + let data = listData.data + var list = [[UInt8]]() + var index = 0 + let seg = 492 + while index < data.count { + if index < data.count - seg { + let sub:[UInt8] = data[index...index + seg - 1].bytes + list.append(sub) + index += sub.count + } + else{ + let sub:[UInt8] = data[index...data.count - 1].bytes + index += sub.count + list.append(sub) + } + } + return list + } + + + func isAnswer(_ data: Data) -> Bool { + return ability.isAnswer(data) + } + + func parse(_ data: Data) -> IBResponse { + return ability.parser.parse(data) + } + + deinit { +// Logger.log("deinit IBSender") + } +} + +public extension IBSender { + + /// 功能接口 + @discardableResult + func ability(_ value: IBAbility) -> Self { + ability = value + return self + } + + /// 参数 + @discardableResult + func parameters(_ value: [IBParameter]) -> Self { + parameters = value + return self + } + + /// 执行与响应处理 + @discardableResult + func response(_ handler: @escaping IBCompletionHandler) -> Self { + completion = handler +// IBBluetooth.shared.send(self) + return self + } +} + +extension IBSender { + static var all: [IBSender] = [] + + /// 从缓存实例集合中移除该实例 + func remove() { + Self.all.removeAll { $0 === self } + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/IBSimulateParser.swift b/code/IOTBluetooth/IOTBluetooth/IBSimulateParser.swift new file mode 100644 index 0000000..4f97eb2 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IBSimulateParser.swift @@ -0,0 +1,24 @@ +// +// IBSimulateParser.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/18. +// + +import Foundation + +/// 通用模拟响应 +//struct IBSingleByteSimulateParser { +// static func parse() -> IBResponse { +// return .response +// } +//} + +///// 闹钟模拟响应 +//struct IBClockSimulateParser { +// static func parse() -> IBResponse { +// let str = #"{"code":1,"alarms":[{"id":0,"repeat":[0],"hour":10,"minute":0,"enable":1},{"id":1,"repeat":[1,2,3,4,5],"hour":7,"minute":45,"enable":1}]}"# +// guard let clock = JSON.decode(IBClock.self, from: str.data!) else { return .response.message("数据解析失败") } +// return .response.success(true).items(clock.alarms) +// } +//} diff --git a/code/IOTBluetooth/IOTBluetooth/IOTBluetooth.h b/code/IOTBluetooth/IOTBluetooth/IOTBluetooth.h new file mode 100644 index 0000000..98258c9 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/IOTBluetooth.h @@ -0,0 +1,18 @@ +// +// IOTBluetooth.h +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/6. +// + +#import + +//! Project version number for IOTBluetooth. +FOUNDATION_EXPORT double IOTBluetoothVersionNumber; + +//! Project version string for IOTBluetooth. +FOUNDATION_EXPORT const unsigned char IOTBluetoothVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBAbout.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBAbout.swift new file mode 100644 index 0000000..2e70140 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBAbout.swift @@ -0,0 +1,24 @@ +// +// IBAbout.swift +// IOTBluetooth +// +// Created by incar on 2024/5/28. +// + +import Foundation +/// 关于本机模型 +public class IBAbout:Codable { + /// 标记码,有数据更新时为1,无数据更新时为0,暂不用 + public var code: Int = 0 + /// 固件主版本号 + public var major: Int = 1 + /// 固件中版本号 + public var mid: Int = 0 + /// 固件次版本号 + public var minor: Int = 0 + /// MAC地址 + public var macAddress: String = "" + + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBAddressBook.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBAddressBook.swift new file mode 100644 index 0000000..c76cad3 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBAddressBook.swift @@ -0,0 +1,71 @@ +// +// IBAddressBook.swift +// IOTBluetooth +// +// Created by incar on 2024/6/3. +// + +import Foundation +/// 通讯录模型 +public class IBAddressBook { + /// 性别 0:未指定,1:男性 ,2:女性 + public var gender: Int = 0 + + /// 姓名,不传手表上显示名字为“未知” + public var name: String? + + /// 电话号码,一个联系人名下可有最少1个最多2个电话号码 + public var phoneNumbers: [IBPhoneNumber] = [] + + + public init() { } + +// public static func test() -> [IBAddressBook]{ +// var list = [IBAddressBook](); +// for i in 0...10{ +// let model = IBAddressBook() +// model.gender = i%2 +// model.name = "å¼ \(i)" +// +// for j in 0...1{ +// let phone = IBPhoneNumber() +// phone.number = "1300000000\(j)" +// phone.location = "深圳市宝安区\(j)" +// phone.operator = "中国移动" +// model.phoneNumbers.append(phone) +// } +// list.append(model) +// } +// +// return list +// } + + public var description: String { + var phoneStr = "[ " + for phone in phoneNumbers { + phoneStr += phone.description + } + phoneStr += " ]" + return "IBAddressBook(gender: \(gender), name:\(name ?? ""), phoneNumbers:\(phoneStr))" + } +} + +/// 电话号码模型 +public class IBPhoneNumber { + /// 电话号码,长度1~255 + public var number: String = "" + + /// 归属地,长度0~255,不传手表上显示归属地为“未知 + public var location: String? + + /// 运营商,长度0~255,不传手表上显示运营商为“未知 + public var `operator`: String? + + public init() { } + + public var description: String { + return "IBPhoneNumber(number: \(number), location:\(location ?? ""), `operator`:\(`operator` ?? ""))" + } +} + + diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBAiResponse.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBAiResponse.swift new file mode 100644 index 0000000..63afd5a --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBAiResponse.swift @@ -0,0 +1,21 @@ +// +// IBAiResponse.swift +// IOTBluetooth +// +// Created by incar on 2024/6/27. +// + +import Foundation +/// Ai回应模型 +public class IBAiResponse { + /// 类型,0:AI回应错误 1:AI录音解析内容 2:AI回应内容(类型为0时,只发类型即可,后面字节均不发) + var type: Int = 0 + /// 内容 + public var value: String = "" + + public init() { } + + public var description: String { + return "IBAiResponse(type: \(type), value:\(value))" + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBAiValid.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBAiValid.swift new file mode 100644 index 0000000..d5ccf80 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBAiValid.swift @@ -0,0 +1,21 @@ +// +// IBAiValid.swift +// IOTBluetooth +// +// Created by incar on 2024/8/2. +// + +import Foundation +/// Ai是否可用模型 +public class IBAiValid { + /// 0:不可用(后面不带key)1:可用 + public var isValid: Bool = true + /// key (ChatGPT后面带key,其他不带) + public var key: String? + + public init() { } + + public var description: String { + return "IBAiValid(isValid: \(isValid), key:\(key ?? ""))" + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBBloodOxygen.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBBloodOxygen.swift new file mode 100644 index 0000000..99f6639 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBBloodOxygen.swift @@ -0,0 +1,42 @@ +// +// IBBloodOxygen.swift +// IOTBluetooth +// +// Created by incar on 2024/5/29. +// + +import Foundation +/// 血氧设置模型 +public class IBBloodOxygen { + /// 监测频率 + public var rate: IBBloodOxygenFrequency = .min30 + /// 血氧提醒 0:关闭 1:开启。 为0时后面区间阈值无需设置。 + public var state: Int = 1 + /// 提醒阈值 90~99 + public var value: Int = 95 + + public init() { } + + init(_ data: Data) { + rate = IBBloodOxygenFrequency(rawValue:data[0].int) ?? .none + state = data[1].int + if state == 1 { + value = data[2].int + } + } + + var description: String { + return "IBBloodOxygen(rate: \(rate), state:\(state), value:\(value))" + } +} +/// 血氧监测频率 +public enum IBBloodOxygenFrequency: Int { + /// 关闭 + case none = 0 + /// 30分钟 + case min30 + /// 1小时 + case hour1 + /// 2小时 + case hour2 +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBClock.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBClock.swift new file mode 100644 index 0000000..600d82b --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBClock.swift @@ -0,0 +1,56 @@ +// +// IBClock.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/21. +// + +import Foundation + +/// 闹钟模型 +public class IBClock: Codable { + /// 闹钟列表 + public var alarms: [IBClockInfo] = [] + + public init() { +// for i in 0...4{ +// let model = IBClockInfo() +// model.id = i +// model.hour = i*3 +// model.minute = i*10 +// model.enable = i%2 +// model.repeat = [1,2,3,4,5,6,7] +// alarms.append(model) +// } + } +} + +/// 闹钟信息模型 +public class IBClockInfo: Codable { + /// 闹钟id 范围为0~4. (即闹钟暂定最多设置为5个) + public var id: Int = 0 + /// 小时 + public var hour: Int = 0 + /// 分钟 + public var minute: Int = 0 + /// 0关闭闹钟,1开启闹钟 + public var enable: Int = 0 + /// 闹钟重复列表 + /// + /// - 0 不重复,响一次 + /// - 1 周一 + /// - 2 周二 + /// - ...... + /// - 7 周日 + public var `repeat`: [Int] = [0] + +// enum CodingKeys: String, CodingKey { +// case id = "id" +// case hour = "hour" +// case minute = "minute" +// case enable = "enable" +// case `repeat` = "repeat" +// } + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBCode.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBCode.swift new file mode 100644 index 0000000..3711401 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBCode.swift @@ -0,0 +1,45 @@ +// +// IBError.swift +// IOTBluetooth +// +// Created by incar on 2024/5/9. +// + +import Foundation + +/// 状态码 +public enum IBCode: Int,CustomStringConvertible { + public var description: String{ + return "IBError(rawValue:\(rawValue), message:\(message))" + } + /// 操作成功 + case success = 1000 + /// 操作失败 + case failure = 1001 + /// 参数错误 + case invalidParam = 1002 + /// 解析错误 + case parseError = 1003 + /// 请求超时 + case timeout = 1004 + /// 未知错误 + case unknow = 1005 + + var message:String{ + switch self { + case .failure: + return "操作失败" + case .success: + return "操作成功" + case .invalidParam: + return "参数错误" + case .parseError: + return "解析错误" + case .timeout: + return "请求超时" + default: + return "未知错误" + } + } + +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBDeleteFile.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBDeleteFile.swift new file mode 100644 index 0000000..a46fd18 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBDeleteFile.swift @@ -0,0 +1,22 @@ +// +// IBDeleteFile.swift +// IOTBluetooth +// +// Created by incar on 2024/6/6. +// + +import Foundation + +/// 删除文件模型 +public class IBDeleteFile:Codable { + /// 文件类型,2:MP3 3:表盘 + public var fileType: IBFile = .mp3 + /// 文件UID,文件唯一编号。一次最多5个 + public var uids: [Int] = [] + + public init() { } + + public var description: String { + return "IBDeleteFile(fileType: \(fileType), uids:\(uids))" + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBDial.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBDial.swift new file mode 100644 index 0000000..6d8060d --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBDial.swift @@ -0,0 +1,104 @@ +// +// IBDail.swift +// IOTBluetooth +// +// Created by incar on 2024/6/6. +// + +import Foundation +/// 表盘文件汇总信息模型(自定义) +//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 IBDial { + /// 自定义表盘:0~4 + /// 表盘市场 : 0x05~0xffff + public var UID: Int = 5 + + /// 主题名称,名称长度,0~255 (仅表盘市场有该项) + public var theme: String? + /// 主题名称长度 + 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 ?? ""))" + } +} + +/// 单个表盘文件信息模型 +public class IBDialFile { + /// 文件路径 + public var filePath: String = "" + /// 文件名称 + public var fileName: String = "" + + /// UID,0x05~0xffff:表盘市场UID + internal var UID: Int = 0 + /// 文件编号,1~255,即该主题表盘中的第几个文件,最大为表盘文件汇总信息中的文件数目 + internal var index: Int = 0 + /// 文件类型,0:表盘jSON文件(压缩后统一命名为local_page.lzma)1:图片文件(除缩略图外的其他图片)(背景图压缩后统一命名为main_bg.lzma)2:缩略图:(压缩后统一命名为thumbnail.lzma) + internal var fileType: Int = 1 + /// 文件名称长度,6~255,不能为0,文件后缀统一为.lzma + internal var nameLength: Int{ + return fileName.count + } + /// 文件大小,0x01~0xffffffff + internal var size: Int = 1 + + + public init() { } + + + public var description: String { + return "IBDialFile(UID: \(UID), index:\(index),fileType: \(fileType), nameLength:\(nameLength), filePath:\(filePath), fileName:\(fileName), size:\(size))" + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBEpo.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBEpo.swift new file mode 100644 index 0000000..a5a2340 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBEpo.swift @@ -0,0 +1,27 @@ +// +// IBEpo.swift +// IOTBluetooth +// +// Created by incar on 2024/6/7. +// + +import Foundation +/// IBEpo信息模型 +public class IBEpo { + /// 文件编号,1~255 + public var UID: Int = 5 + /// 文件名称长度,6~255,不能为0,文件后缀统一为.lzma + public var nameLength: Int = 6 + /// 文件名称,名称字节数为前面名称长度指定,UTF-8编码 + public var fileName: String = "" + /// 文件路径 + public var filePath: String = "" + /// 文件大小,单位byte + 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/code/IOTBluetooth/IOTBluetooth/Models/IBFetchFile.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBFetchFile.swift new file mode 100644 index 0000000..bd7fa42 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBFetchFile.swift @@ -0,0 +1,80 @@ +// +// IBFetchFile.swift +// IOTBluetooth +// +// Created by incar on 2024/6/8. +// + +import Foundation +/// 获取指定类型文件目录模型 +public class IBFetchFile:Codable { + /// 类型 2:MP3, 3:表盘 + var type:IBFile = .mp3 + /// 表盘 + var dialList:[IBFetchDialItem]? + /// mp3 + var mp3List:[IBFetchMp3Item]? + + init?(_ data: Data) { + guard let dict = data.jsonObject() as? [String: Any] else { return nil } + guard let type = dict["type"] as? Int,let fileType = IBFile(rawValue: type) else { return nil } + self.type = fileType + + if let listDict = dict["list"] as? [[String: Any]] { + if self.type == .mp3 { + var list = [IBFetchMp3Item]() + for item in listDict { + let model = IBFetchMp3Item() + model.uid = item["uid"] as? Int ?? 0 + model.name = item["name"] as? String ?? "" + model.size = item["size"] as? Int ?? 0 + model.artist = item["artist"] as? String ?? "" + model.duration = item["duration"] as? Int ?? 0 + list.append(model) + } + mp3List = list + } + else if self.type == .dial{ + var list = [IBFetchDialItem]() + for item in listDict { + let model = IBFetchDialItem() + model.uid = item["uid"] as? Int ?? 0 + model.name = item["name"] as? String ?? "" + model.size = item["size"] as? Int ?? 0 + list.append(model) + } + dialList = list + } + } + } + + public init() { } +} + +/// 获取指定类型文件表盘模型 +public class IBFetchDialItem:Codable { + /// 表盘在手表中唯一编号 + var uid:Int = 0 + /// 表盘名字 + var name:String = "" + /// 文件大小,单位byte + var size:Int = 0 + + public init() { } +} + +/// 获取指定类型文件MP3模型 +public class IBFetchMp3Item:Codable { + /// 歌曲在手表中唯一编号 + var uid:Int = 0 + /// 歌曲名字 + var name:String = "" + /// 文件大小,单位byte + var size:Int = 0 + /// 作者 + var artist:String = "" + /// 歌曲时长,单位毫秒 + var duration:Int = 0 + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHeartRate.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHeartRate.swift new file mode 100644 index 0000000..eee15b4 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHeartRate.swift @@ -0,0 +1,43 @@ +// +// IBHeartRate.swift +// IOTBluetooth +// +// Created by incar on 2024/5/16. +// + +import Foundation +/// 心率设置模型 +public class IBHeartRate { + /// 监测频率 + public var rate: IBHeartRateFrequency = .min1 + /// 心率提醒 0:关闭 1:开启。 为0时后面区间阈值无需设置。 + public var state: Int = 1 + /// 心率区间阈值 高于或低于 0:低于 1:高于 + public var higherOrLower: Int = 0 + /// 心率区间阈值 30~200 + public var value: Int = 100 + + public init() { } + + init(_ data: Data) { + rate = IBHeartRateFrequency(rawValue:data[0].int) ?? .none + state = data[1].int + higherOrLower = data[2].int + value = data[3].int + } + + var description: String { + return "IBHeartRate(rate: \(rate), state:\(state), higherOrLower:\(higherOrLower), value:\(value))" + } +} +/// 提醒频率 +public enum IBHeartRateFrequency: Int { + /// 关闭 + case none = 0 + /// 1分钟 + case min1 + /// 5分钟 + case min5 + /// 10分钟 + case min10 +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBaseTraining.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBaseTraining.swift new file mode 100644 index 0000000..b93cb22 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBaseTraining.swift @@ -0,0 +1,113 @@ +// +// IBHistoryBaseTraining.swift +// IOTBluetooth +// +// Created by incar on 2024/5/21. +// + +import Foundation + +/// 基础训练历史数据模型 +class IBHistoryBaseTraining:Codable { + /// 训练历史记录 + var list:[IBHistoryBaseTrainingItem] = [] + + enum CodingKeys: String, CodingKey { + case list = "recs" + } +} + +/// 基础训练历史数据模型 +public class IBHistoryBaseTrainingItem:Codable { + /// 0:手表开启的训练1:APP开启的训练 + public var source: Int = 0 + + /// (仅W4)0:健身类1:舞蹈类2:休闲运动3:球类4:水上项目5:冰上项目6:极限运动7:其他 + public var otherType: Int? + + /// 训练类型,0:健走1:跑步2:骑行以上适用W2/W3,W4该字段表示各训练大类中的小类,对应训练名称参考 自由训练176.xlsx 表格。 + public var trainingType: Int = 0 + + /// 训练开始时间戳,单位秒 + public var star: Int = 0 + + /// 训练结束时间戳,单位秒 + public var stop: Int = 0 + + /// 训练目标类型,0:路程 1:卡路里 2:时间 3:步数 + public var targetType: Int = 0 + + /// 训练目标 路程:单位米, 卡路里:单位kcal ,时间:单位分钟 ,步数:单位步 + public var target: Int = 0 + + /// 目标完成度,单位% + public var progress: Int = 0 + + /// 训练总时间,单位秒 + public var duration: Int = 0 + + /// 卡路里,为该数值乘以0.1,单位kcal + public var calorie: Int? + + /// 距离,单位米 + public var distance: Int? + + /// 平均运动心率 + public var heartRate: Int = 0 + + /// 运动步数 + public var step: Int? + + /// 训练强度(仅W4), 0 = 较低强度,1 = 中等强度,2 = 高等强度,3 = 超高强度 + public var trainingLevel:Int? + + /// 圈数(仅限跑步训练) + public var circle: Int? + + /// 最高海拔高度(仅限登山训练) + public var elevation: Int? + + + enum CodingKeys: String, CodingKey { + case source = "src" + case otherType = "bt" + case trainingType = "ty" + case star = "st" + case stop = "sp" + case targetType = "tgt" + case target = "tg" + case progress = "pg" + case duration = "du" + case calorie = "ca" + case distance = "dl" + case heartRate = "hr" + case step = "stp" + case trainingLevel = "tl" + case circle = "cir" + case elevation = "alt" + } +} + +///// 训练目标类型 +//public enum IBTrainingTargetType: Int,Codable { +// /// 路程 +// case distance = 0 +// /// 卡路里 +// case calorie +// /// 时间 +// case time +// /// 步数 +// case step +//} + +///// 训练强度 +//public enum IBTrainingLevel: Int,Codable { +// /// 较低强度 +// case low = 0 +// /// 中等强度 +// case medium +// /// 高等强度 +// case high +// /// 超高强度 +// case superHigh +//} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBloodOxygen.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBloodOxygen.swift new file mode 100644 index 0000000..e922be8 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBloodOxygen.swift @@ -0,0 +1,36 @@ +// +// IBHistoryBloodOxygen.swift +// IOTBluetooth +// +// Created by incar on 2024/5/28. +// + +import Foundation +/// 血氧历史模型 +class IBHistoryBloodOxygen { + /// 记录 + var list:[IBHistoryBloodOxygenItem] = [] + + init(_ data: Data) { + var historyList:[IBHistoryBloodOxygenItem] = [] + for i in stride(from: 0, to: data.count, by: 5){ + if data.count > i + 4 { + let model = IBHistoryBloodOxygenItem() + model.time = data[i...i+3].int + model.value = data[i+4].int + historyList.append(model) + } + } + list = historyList + } +} + +/// 血氧历史记录 +public class IBHistoryBloodOxygenItem { + /// 时间戳 + public var time: Int = 0 + /// 血氧,单位% + public var value: Int = 0 + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBurning.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBurning.swift new file mode 100644 index 0000000..33f1466 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBurning.swift @@ -0,0 +1,36 @@ +// +// IBHistoryBurning.swift +// IOTBluetooth +// +// Created by incar on 2024/5/28. +// + +import Foundation +/// 燃脂历史模型 + class IBHistoryBurning { + /// 记录 + var list:[IBHistoryBurningItem] = [] + + init(_ data: Data) { + var historyList:[IBHistoryBurningItem] = [] + for i in stride(from: 0, to: data.count, by: 6){ + if data.count > i + 5 { + let model = IBHistoryBurningItem() + model.time = data[i...i+3].int + model.value = data[i+4...i+5].int + historyList.append(model) + } + } + list = historyList + } +} + +/// 燃脂历史记录 +public class IBHistoryBurningItem { + /// 时间戳 + public var time: Int = 0 + /// 单位分钟 + public var value: Int = 0 + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift new file mode 100644 index 0000000..c5f7ed8 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift @@ -0,0 +1,36 @@ +// +// IBHistoryCalorie.swift +// IOTBluetooth +// +// Created by incar on 2024/5/28. +// + +import Foundation +/// 卡路里历史模型 + class IBHistoryCalorie { + /// 记录 + var list:[IBHistoryCalorieItem] = [] + + init(_ data: Data) { + var historyList:[IBHistoryCalorieItem] = [] + for i in stride(from: 0, to: data.count, by: 8){ + if data.count > i + 7 { + let model = IBHistoryCalorieItem() + model.time = data[i...i+3].int + model.value = data[i+4...i+7].float + historyList.append(model) + } + } + list = historyList + } +} + +/// 卡路里历史记录 +public class IBHistoryCalorieItem { + /// 时间戳 + public var time: Int = 0 + /// 单位kcal + public var value: Float = 0 + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift new file mode 100644 index 0000000..ef8d97d --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift @@ -0,0 +1,36 @@ +// +// IBHistoryDistance.swift +// IOTBluetooth +// +// Created by incar on 2024/5/28. +// + +import Foundation +/// 卡路里历史模型 + class IBHistoryDistance { + /// 历史记录 + var list:[IBHistoryDistanceItem] = [] + + init(_ data: Data) { + var historyList:[IBHistoryDistanceItem] = [] + for i in stride(from: 0, to: data.count, by: 8){ + if data.count > i + 7 { + let model = IBHistoryDistanceItem() + model.time = data[i...i+3].int + model.value = data[i+4...i+7].float + historyList.append(model) + } + } + list = historyList + } +} + +/// 距离历史记录 +public class IBHistoryDistanceItem { + /// 时间戳 + public var time: Int = 0 + /// 单位km + public var value: Float = 0 + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryGps.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryGps.swift new file mode 100644 index 0000000..78eb6fc --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryGps.swift @@ -0,0 +1,40 @@ +// +// IBHistoryGps.swift +// IOTBluetooth +// +// Created by incar on 2024/5/30. +// + +import Foundation + +/// 同步路径坐标历史数据模型 +public class IBHistoryGps { + /// 记录 + var list:[IBHistoryGpsItem] = [] + + init(_ data: Data) { + var historyList:[IBHistoryGpsItem] = [] + for i in stride(from: 0, to: data.count, by: 12){ + if data.count > i + 11 { + let model = IBHistoryGpsItem() + model.time = data[i...i+3].int + model.lon = data[i+4...i+7].int + model.lat = data[i+8...i+11].int + historyList.append(model) + } + } + list = historyList + } +} +/// 同步路径坐标历史数据模型 +public class IBHistoryGpsItem { + /// unix时间戳 + public var time: Int = 0 + /// 纬度,该值除以10000000得到实际纬度值 + public var lat: Int = 0 + /// 经度,该值除以10000000得到实际经度值 + public var lon: Int = 0 + + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryHeartRate.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryHeartRate.swift new file mode 100644 index 0000000..ad3248f --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryHeartRate.swift @@ -0,0 +1,36 @@ +// +// IBHistoryHeartRate.swift +// IOTBluetooth +// +// Created by incar on 2024/5/21. +// + +import Foundation +/// 心率历史模型 + class IBHistoryHeartRate { + /// 记录 + var list:[IBHistoryHeartRateItem] = [] + + init(_ data: Data) { + var historyList:[IBHistoryHeartRateItem] = [] + for i in stride(from: 0, to: data.count, by: 5){ + if data.count > i + 4 { + let model = IBHistoryHeartRateItem() + model.time = data[i...i+3].int + model.value = data[i+4].int + historyList.append(model) + } + } + list = historyList + } +} + +/// 心率历史记录 +public class IBHistoryHeartRateItem { + /// 时间戳 + public var time: Int = 0 + /// 心率 + public var value: Int = 0 + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift new file mode 100644 index 0000000..ef75792 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift @@ -0,0 +1,113 @@ +// +// IBHistorySleep.swift +// IOTBluetooth +// +// Created by incar on 2024/5/30. +// + +import Foundation +/// 睡眠历史模型 + class IBHistorySleep { + /// 记录 + var list:[IBHistorySleepItem] = [] + + init(_ data: Data) { + if(data.count <= 0){ + return + } + var historyList:[IBHistorySleepItem] = [] + let times = data[0].int + let contentData:Data = data.suffix(from: 1).map({ $0 }).data + var index = 0 + for _ in 0.. index + 1 { + let len = contentData[index...index+1].int + if contentData.count > index + len { + let itemData = contentData[index+2 ... index+1+len].map({ $0 }).data + let item = IBHistorySleepItem() + item.sleepId = itemData[0].int + item.type = itemData[1].int + item.startTime = itemData[2...5].int + item.endTime = itemData[6...9].int + item.totalSleep = IBSleepTime(hour: itemData[10].int, minute: itemData[11].int) + if(item.type == 1){ + item.deepSleep = IBSleepTime(hour: itemData[12].int, minute: itemData[13].int) + item.lightSleep = IBSleepTime(hour: itemData[14].int, minute: itemData[15].int) + item.remSleep = IBSleepTime(hour: itemData[16].int, minute: itemData[17].int) + item.awake = IBSleepTime(hour: itemData[18].int, minute: itemData[19].int) + + var subIndex = 20 + var list:[IBSleepState] = [] + while itemData.count >= subIndex + 5{ + let state = IBSleepState(time: itemData[subIndex...subIndex+3].int, state: itemData[subIndex+4].int) + list.append(state) + subIndex = subIndex + 5 + } + item.states = list + } + historyList.append(item) + } + index += 2 + len + } + } + list = historyList + } +} + +/// 睡眠历史记录 +public class IBHistorySleepItem { + /// 睡眠ID,0~255,表示第几次睡眠 + public var sleepId: Int = 0 + /// 睡眠类型,0:小睡(无总时长之后的所有项目)1:睡眠 + public var type: Int = 0 + /// 入睡时间戳,Unix时间戳 + public var startTime: Int = 0 + /// 出睡时间戳,Unix时间戳 + public var endTime: Int = 0 + /// 总时长 + public var totalSleep: IBSleepTime = IBSleepTime(hour: 0, minute: 0) + /// 深睡时长 + public var deepSleep: IBSleepTime? + /// 浅睡时长 + public var lightSleep: IBSleepTime? + /// 快速动眼时长 + public var remSleep: IBSleepTime? + /// 清醒时长 + public var awake: IBSleepTime? + /// 睡眠状态 + public var states:[IBSleepState]? + + + public init() { } +} + +/// 时间 +public struct IBSleepTime { + /// 时 + public var hour: Int + /// 分 0~59 + public var minute: Int + + public init(hour: Int, minute: Int) { + self.hour = hour + self.minute = minute + } + + var description: String { + return "IBSleepTime(hour: \(hour), minute:\(minute)" + } +} + + +/// 睡眠状态 +public struct IBSleepState { + /// Unix时间戳 + public var time: Int = 0 + /// 状态,0:进入睡眠 1:浅层睡眠 2:深层睡眠 3:清醒 4:结束睡眠 7:未出睡最后一笔睡眠数据(资料上报结束标志) 9:出睡最后一笔睡眠数据(资料上报结束标志) 12:快速眼动 13:取消入睡通知(睡眠不足 60 分钟出睡上报状态) 14:结束小睡 + public var state: Int = 0 + + public init(time: Int, state: Int) { + self.time = time + self.state = state + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryStand.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryStand.swift new file mode 100644 index 0000000..918a8f8 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryStand.swift @@ -0,0 +1,33 @@ +// +// IBHistoryStand.swift +// IOTBluetooth +// +// Created by incar on 2024/5/28. +// + +import Foundation +/// 站立历史模型 + class IBHistoryStand { + /// 记录 + var list:[IBHistoryStandItem] = [] + + init(_ data: Data) { + var historyList:[IBHistoryStandItem] = [] + for i in stride(from: 0, to: data.count, by: 4){ + if data.count > i + 3 { + let model = IBHistoryStandItem() + model.time = data[i...i+3].int + historyList.append(model) + } + } + list = historyList + } +} + +/// 站立历史记录 +public class IBHistoryStandItem { + /// 时间戳 + public var time: Int = 0 + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryStep.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryStep.swift new file mode 100644 index 0000000..538bc4b --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBHistoryStep.swift @@ -0,0 +1,36 @@ +// +// IBHistoryStep.swift +// IOTBluetooth +// +// Created by incar on 2024/5/28. +// + +import Foundation +/// 步数历史模型 + class IBHistoryStep { + /// 记录 + var list:[IBHistoryStepItem] = [] + + init(_ data: Data) { + var historyList:[IBHistoryStepItem] = [] + for i in stride(from: 0, to: data.count, by: 8){ + if data.count > i + 7 { + let model = IBHistoryStepItem() + model.time = data[i...i+3].int + model.value = data[i+4...i+7].int + historyList.append(model) + } + } + list = historyList + } +} + +/// 步数历史记录 +public class IBHistoryStepItem { + /// 时间戳 + public var time: Int = 0 + /// 单位步 + public var value: Int = 0 + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBLanguage.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBLanguage.swift new file mode 100644 index 0000000..722f730 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBLanguage.swift @@ -0,0 +1,58 @@ +// +// IBLanguage.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/20. +// + +import Foundation + +/// 语言原始值从 0 开始递增 +public enum IBLanguage: Int { + /// 中文 + case chinese = 0 + /// 英语 + case english + /// 日本语 + case japanese + /// 韩语 + case korean + /// 西班牙语 + case spanish + /// 俄语 + case russian + /// 克罗地亚语 + case croatian + /// 德语 + case german + /// 葡萄牙语 + case portuguese + /// 法语 + case french + /// 荷兰语 + case dutch + /// 意大利语 + case italian + /// 捷克语 + case czech + /// 希腊语 + case greek + /// 印度语 + case hindi + /// 印度尼西亚语 + case indonesian + /// 马来西亚语 + case malaysian + /// 波兰语 + case polish + /// 泰语 + case thai + /// 越南语 + case vietnamese + /// 芬兰语 + case finnish + /// 罗马尼亚语 + case romanian + /// 土耳其语 + case turkish +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBManufacturer.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBManufacturer.swift new file mode 100644 index 0000000..6300d84 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBManufacturer.swift @@ -0,0 +1,37 @@ +// +// IBManufacturer.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/14. +// + +import Foundation + +/// 广播数据包解析 +struct IBManufacturer { + init?(_ data: Data) { + guard data.count == 14 else { return nil } + + code = data[0...1].string ?? "" + guard code == "JH" else { return nil } + + isBind = data[2].int + macAddress = data[3...8].reversedJoined(":") + customerID = data[9].int + productID = data[10].int + softwareVersion = data[11..<14].joined(format: "%d", ".") + } + + /// 识别符 + let code: String + /// 是否已绑定(1=已绑定) + let isBind: Int + /// mac 地址 + let macAddress: String + /// 客户 ID + let customerID: Int + /// 产品 ID + let productID: Int + /// 软件版本 + let softwareVersion: String +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBMp3.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBMp3.swift new file mode 100644 index 0000000..3b10293 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBMp3.swift @@ -0,0 +1,38 @@ +// +// IBMp3.swift +// IOTBluetooth +// +// Created by incar on 2024/6/6. +// + +import Foundation + +/// 发送mp3文件模型 +public class IBSendMp3:Codable { + /// 文件路径 + public var filePath: String = "" + /// 歌曲名字 + public var name: String = "" + /// 文件大小,单位byte + internal var size: Int = 0 + + + public init() { } +} + + +/// mp3文件模型 +public class IBMp3File:Codable { + /// 歌曲名字 + var name: String = "" + /// 文件类型,2:MP3 + var type: Int = 2 + /// 文件大小,单位byte + var size: Int = 0 + + internal init(data:IBSendMp3) { + self.name = data.name + self.size = data.size + } +} + diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBOta.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBOta.swift new file mode 100644 index 0000000..5f8aa00 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBOta.swift @@ -0,0 +1,48 @@ +// +// IBOta.swift +// IOTBluetooth +// +// Created by incar on 2024/6/6. +// + +import Foundation +/// 发送OTA文件模型 +public class IBSendOta:Codable { + /// 文件路径 + public var filePath: String = "" + /// 文件大小,单位byte + internal var size: Int = 0 + /// major是主版本号,mid是中间版本号,minor是次版本号,用于与手表当前版本比较,新于当前版本才可升级 + public var major: Int = 0 + /// mid是中间版本号 + public var mid: Int = 0 + /// minor是次版本号 + public var minor: Int = 0 + /// 是否强制升级,0为非强制,1为强制 + public var isForced: Int = 0 + + public init() { } +} +/// OTA文件模型 +public class IBOtaFile:Codable { + /// 文件类型,1:OTA + internal var type: Int = 1 + /// 文件大小,单位byte + internal var size: Int = 0 + /// major是主版本号,mid是中间版本号,minor是次版本号,用于与手表当前版本比较,新于当前版本才可升级 + internal var major: Int = 0 + /// mid是中间版本号 + internal var mid: Int = 0 + /// minor是次版本号 + internal var minor: Int = 0 + /// 是否强制升级,0为非强制,1为强制 + internal var isForced: Int = 0 + + internal init(data:IBSendOta) { + self.size = data.size + self.major = data.major + self.mid = data.mid + self.minor = data.minor + self.isForced = data.isForced + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBOtherTarget.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBOtherTarget.swift new file mode 100644 index 0000000..fd0aaf7 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBOtherTarget.swift @@ -0,0 +1,41 @@ +// +// IBUserInfo.swift +// IOTBluetooth +// +// Created by incar on 2024/5/14. +// + +import Foundation +/// 其他目标设置模型 +public class IBOtherTarget { + /// 目标卡路里,单位千卡 + public var calorie: Int = 0 + /// 目标里程,单位公里 + public var mileage: Int = 1 + /// 目标站立次数,单位次 + public var stand: Int = 0 + /// 目标燃脂时间,单位分钟 + public var burning: Int = 0 + /// 铃声提醒 0:关闭 1:开启 + public var ringsState: Int = 0 + /// 震动提醒 0:关闭 1:开启 + public var vibrateState: Int = 0 + /// 通知提醒 0:关闭 1:开启 + public var noticeState: Int = 0 + + public init() { } + + init(_ data: Data) { + calorie = data[0...1].int + mileage = data[2].int + stand = data[3].int + burning = data[4].int + ringsState = data[5].int + vibrateState = data[6].int + noticeState = data[7].int + } + + var description: String { + return "IBOtherTarget(calorie: \(calorie), mileage:\(mileage), stand:\(stand), burning:\(burning), ringsState:\(ringsState), vibrateState:\(vibrateState), noticeState:\(noticeState))" + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBPhoneInfo.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBPhoneInfo.swift new file mode 100644 index 0000000..0387b03 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBPhoneInfo.swift @@ -0,0 +1,25 @@ +// +// IBDeviceInfo.swift +// IOTBluetooth +// +// Created by incar on 2024/5/23. +// + +import Foundation +/// 手机信息模型 +public class IBPhoneInfo:Codable { + /// 手机名称 + public var name: String = "iphone" + /// 手机类型 0:安卓 1:苹果 + public var type: Int = 1 + /// 手机MAC地址数组(仅IOS,配对成功从手表获取,未获取时为空) + public var mac: [Int]? + + enum CodingKeys: String, CodingKey { + case name = "pn" + case type = "tp" + case mac = "mac" + } + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBResponse.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBResponse.swift new file mode 100644 index 0000000..27891f4 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBResponse.swift @@ -0,0 +1,77 @@ +// +// IBResponse.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/15. +// + +import Foundation + +public class IBResponseModel { + var data:Data! + required init(_ data: Data) { + self.data = data + } + + var pkgData:Data{ + if(data.count > 18){ + return data[17...data.count - 2] + } + return Data() + } +} + + +public class IBResponse:CustomStringConvertible { + /// 状态码 + public private(set) var code: IBCode = .success + /// 反馈信息 + public private(set) var message: String = "操作成功" + /// 当成功时为 true + public private(set) var isSuccess: Bool = false + /// 当结果为集合时,应该从此字段读取 + public private(set) var items: [Any] = [] + /// 当结果为非集合,应该从此字段读取 + public private(set) var value: Any? + + static var response: IBResponse { IBResponse() } + + public var description: String { + var des = "IBResponse(code: \(code.rawValue),isSuccess: \(isSuccess), massage:\(message), " + if let v1 = value { + des.append("value:\(v1))") + } + else{ + des.append("items:\(items))") + } + return des + } +} + +extension IBResponse { + @discardableResult + func code(_ value: IBCode) -> Self { + code = value + isSuccess = code == .success + message = code.message + return self + } + + @discardableResult + func message(_ value: String) -> Self { + message = value + return self + } + + @discardableResult + func items(_ value: [Any]) -> Self { + items = value + return self + } + + @discardableResult + func value(_ value: Any) -> Self { + self.value = value + return self + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBScreen.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBScreen.swift new file mode 100644 index 0000000..9a1a017 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBScreen.swift @@ -0,0 +1,77 @@ +// +// IBScreen.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/21. +// + +import Foundation + +/// 设置屏幕参数 +public class IBScreenConfig { + /// 类型 0,亮度调节; 1,亮屏时长,抬腕亮屏 + public var type: Int = 0 + + /// 亮度 [51, 255], type为0必须设置 + public var brightness: Int? + + /// 亮屏时长, type为1必须设置 + public var duration: IBDuration? + + /// 抬腕亮屏(0=关闭, 1=开启), type为1必须设置 + public var wrist: Int? + + public init() { } + + var description: String { + var str = "IBScreenConfig(type: \(type)," + if let brightness = brightness{ + str += "brightness: \(brightness ))" + } + if let duration = duration { + str += "duration: \(duration )" + } + if let wrist = wrist { + str += "wrist: \(wrist ))" + } + return str + } +} + +/// 屏幕参数 +public class IBScreen { + /// 亮度 [0, 255] + public var brightness: Int = 10 + + /// 亮屏时长 + public var duration: IBDuration = .second10 + + /// 抬腕亮屏(0=关闭, 1=开启) + public var wrist: Int = 1 + + public init() { } + + init(_ data: Data) { + brightness = data[0].int + duration = IBDuration(rawValue: data[1].int) ?? .second5 + wrist = data[2].int + } + var description: String { + return "IBScreen(brightness: \(brightness), duration:\(duration), wrist:\(wrist))" + } +} + +public enum IBDuration: Int { + /// 5 秒 + case second5 = 0 + /// 10 秒 + case second10 + /// 15 秒 + case second15 + /// 30 秒 + case second30 + /// 1 分钟 + case minute + /// 5 分钟 + case minute5 +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBSendTime.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBSendTime.swift new file mode 100644 index 0000000..5cb39eb --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBSendTime.swift @@ -0,0 +1,30 @@ +// +// IBSendTime.swift +// IOTBluetooth +// +// Created by incar on 2024/5/23. +// + +import Foundation +/// 发送时间模型 +public class IBSendTime:Codable { + /// unix时间戳 + public var time: Int = 0 + /// 0-6:周日-周六 + public var week: Int = 0 + /// 0-127,年份减去2000 + public var year: Int = 0 + /// 1-12 + public var month: Int = 1 + /// 1-31 + public var day: Int = 1 + /// 0-24 + public var hour: Int = 0 + /// 0-60 + public var minute: Int = 0 + /// 0-60 + public var second: Int = 0 + + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBSleep.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBSleep.swift new file mode 100644 index 0000000..04e703a --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBSleep.swift @@ -0,0 +1,56 @@ +// +// IBSleep.swift +// IOTBluetooth +// +// Created by incar on 2024/5/15. +// + +import Foundation +/// 睡眠设置模型 +public class IBSleep { + /// 0:关闭 1:开启 为0时后面所有项目都无需设置。 + public var sleepTimeSet: Int = 0 + /// 就寝时间 + public var sleepTime: IBTimeItem? + /// 起床时间 + public var getUpTime: IBTimeItem? + /// 就寝前20分钟提醒,0:关闭1:开启 + public var alertBeforeMin20: Int = 0 + + init(_ data: Data) { + sleepTimeSet = data[0].int + if sleepTimeSet == 1{ + sleepTime = IBTimeItem(hour: data[1].int, minute: data[2].int) + getUpTime = IBTimeItem(hour: data[3].int, minute: data[4].int) + alertBeforeMin20 = data[5].int + } + } + public init() { +// sleepTimeSet = 1 +// sleepTime = IBTimeItem(hour: 7, minute: 30) +// getUpTime = IBTimeItem(hour: 15, minute: 50) +// alertBeforeMin20 = 0 + } + + var description: String { + return "IBSleep(sleepTimeSet: \(sleepTimeSet), sleepTime:\(sleepTime?.description ?? ""), getUpTime:\(getUpTime?.description ?? ""), alertBeforeMin20:\(alertBeforeMin20))" + } + +} + +/// 时间 +public struct IBTimeItem { + /// 0~23 + public var hour: Int + /// 0~59 + public var minute: Int + + public init(hour: Int, minute: Int) { + self.hour = hour + self.minute = minute + } + + var description: String { + return "IBTimeItem(hour: \(hour), minute:\(minute)" + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBTargetStep.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBTargetStep.swift new file mode 100644 index 0000000..5666ca8 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBTargetStep.swift @@ -0,0 +1,37 @@ +// +// IBTargetStep.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/21. +// + +import Foundation +/// 目标步数模型 +public class IBTargetStep { + + /// 步数 + public var value: Int = 5000 + + /// 铃声提醒(0=关闭,1=开启) + public var ring: Int = 0 + + /// 震动提醒(0=关闭,1=开启) + public var vibrate: Int = 0 + + /// 通知提醒(0=关闭,1=开启) + public var notice: Int = 0 + + init?(_ data: Data) { + guard data.count == 7 else { return nil } + value = data[0...3].int + ring = data[4].int + vibrate = data[5].int + notice = data[6].int + } + + var description: String { + return "IBTargetStep(value: \(value), ring:\(ring), vibrate: \(vibrate), notice:\(notice))" + } + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBTarinState.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBTarinState.swift new file mode 100644 index 0000000..9e048fe --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBTarinState.swift @@ -0,0 +1,26 @@ +// +// IBTarinState.swift +// IOTBluetooth +// +// Created by incar on 2024/5/28. +// + +import Foundation +/// 推送训练状态模型 +public class IBTarinState { + /// 状态 0:结束 1:开始 2:暂停 3:恢复 + public var state: Int = 1 + /// 训练时长,单位秒 + public var duration: Int = 0 + + public init() { } + + init(_ data: Data) { + state = data[0].int + duration = data[1...2].int + } + + public var description: String { + return "IBTarinState(state: \(state), duration:\(duration))" + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBTrainTarget.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBTrainTarget.swift new file mode 100644 index 0000000..bbc1946 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBTrainTarget.swift @@ -0,0 +1,46 @@ +// +// IBaa.swift +// IOTBluetooth +// +// Created by incar on 2024/5/16. +// + +import Foundation +/// 训练目标设置模型 +public class IBTrainTarget:Codable { + /// 目标类型,对应的类型,设置对应的值 + public var type: IBTrainTargetType = .calorie + /// 目标距离,单位米 + public var distance: Int? + /// 目标卡路里,单位kcal + public var calorie: Int? + /// 目标时间,单位分钟 + public var time: Int? + /// 目标步数,单位步 + public var step: Int? + + public init() { } + + enum CodingKeys: String, CodingKey { + case type = "ty" + case distance = "di" + case calorie = "ca" + case time = "tm" + case step = "st" + } + var description: String { + return "IBTrainTarget(type: \(type), distance:\(distance ?? 0), calorie:\(calorie ?? 0), time:\(time ?? 0), step:\(step ?? 0))" + } +} + +/// 提醒频率 +public enum IBTrainTargetType: Int,Codable { + /// 距离 + case distance = 0 + /// 卡路里 + case calorie = 1 + /// 时间 + case time = 2 + /// 步数 + case step = 4 +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBTrainingData.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBTrainingData.swift new file mode 100644 index 0000000..270c59e --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBTrainingData.swift @@ -0,0 +1,48 @@ +// +// IBTrainingData.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/20. +// + +import Foundation + +/// 训练数据模型 +public struct IBTrainingData { + /// 训练开始时间(时间戳) + public let startTime: Int + + /// 实时训练心率,单位bpm + public let heartRate: Int + + /// 实时训练卡路里,单位kcal + public let calorie: Int + + /// 实时训练距离,单位米 + public let distance: Int + + /// 实时训练步数,单位步 + public let stepCount: Int + + /// 实时训练强度 + /// + /// - 0:较低强度 + /// - 1:中等强度 + /// - 2:高等强度 + /// - 3:超高强度 + public let intensity: Int + + init?(_ data: Data) { + guard let dict = data.jsonObject() as? [String: Int] else { return nil } + self.startTime = dict["st"]! + self.heartRate = dict["hr"]! + self.calorie = dict["ca"]! + self.distance = dict["di"]! + self.stepCount = dict["stp"]! + self.intensity = dict["ti"]! + } + + public var description: String { + return "IBTrainingData(startTime: \(startTime), heartRate:\(heartRate), calorie:\(calorie), distance:\(distance), stepCount:\(stepCount), intensity:\(intensity))" + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBUserInfo.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBUserInfo.swift new file mode 100644 index 0000000..34e8192 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBUserInfo.swift @@ -0,0 +1,45 @@ +// +// IBUserInfo.swift +// IOTBluetooth +// +// Created by incar on 2024/5/30. +// + +import Foundation +/// 用户数据模型 +public class IBUserInfo { + /// 年龄 + public var age: Int = 0 + /// 性别 0:男性 1:女性 + public var gender: Int = 0 + /// 身高, 单位cm + public var height: Int = 0 + /// 体重, 单位kg + public var weight: Int = 0 + /// 目标步数 + public var step: Int = 0 + /// 目标燃脂时间,单位分钟 + public var burning: Int = 0 + /// 目标站立次数 + public var stand: Int = 0 + /// 时制 0:24小时制 1:12小时制 + public var hourSystem: Int = 0 + + + public init() { } + + init(_ data: Data) { + age = data[0].int + gender = data[1].int + height = data[2].int + weight = data[3...4].int + step = data[5...8].int + burning = data[9...10].int + stand = data[11].int + hourSystem = data[12].int + } + + public var description: String { + return "IBUserInfo(age: \(age), gender:\(gender), height:\(height), weight:\(weight), step:\(step), burning:\(burning), stand:\(stand), hourSystem:\(hourSystem))" + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBVibrateSound.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBVibrateSound.swift new file mode 100644 index 0000000..88e1ccd --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBVibrateSound.swift @@ -0,0 +1,63 @@ +//// +//// IBVibrateSound.swift +//// IOTBluetooth +//// +//// Created by incar on 2024/5/9. +//// +// + +/// 声音震动设置模型 +public class IBVibrateConfig { + /// 类型 0系统音量调节,1铃声音量调节,3铃声开关,震动开关 + public var type: Int = 0 + /// 系统音量调节0~100(type为0设置) + public var systemValue: Int? + + /// 铃声音量调节0~100(type为1设置) + public var ringsValue: Int? + + /// 静音开关0:关闭 1:开启(type为3设置) + public var ringsState: Int? + + /// 震动设置 0:关闭 1:弱 2:中 3:强 (type为3设置) + public var vibrateState: Int? + + public init() { } + + var description: String { + return "IBVibrateConfig(type: \(type),systemValue: \(systemValue), ringsValue:\(ringsValue), ringsState:\(ringsState), vibrateState:\(vibrateState))" + } +} + +/// 声音震动模型 +public class IBVibrateSound { + /// 系统音量调节0~100 + public var systemValue: Int = 0 + + /// 铃声音量调节0~100 + public var ringsValue: Int = 0 + + /// 媒体音量调节0~100 + public var mediaValue: Int = 0 + + /// 静音开关0:关闭 1:开启 + public var ringsState: Int = 0 + + /// 震动设置 0:关闭 1:弱 2:中 3:强 + public var vibrateState: Int = 0 + + public init() { } + + init(_ data: Data) { + systemValue = data[0].int + ringsValue = data[1].int + mediaValue = data[2].int + ringsState = data[3].int + vibrateState = data[4].int + } + + var description: String { + return "IBVibrateSound(systemValue: \(systemValue), ringsValue:\(ringsValue), mediaValue:\(mediaValue), ringsState:\(ringsState), vibrateState:\(vibrateState))" + } +} + diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBWatchInfo.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBWatchInfo.swift new file mode 100644 index 0000000..2a7c0c1 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBWatchInfo.swift @@ -0,0 +1,46 @@ +// +// IBWatchInfo.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/14. +// + +import Foundation + +/// 手表信息 +struct IBWatchInfo { + + init(_ data: Data) { + battery = data[0].int + dialID = data[1...2].int + firmwareVersion = data[3...5].joined(format: "%d", ".") + macAddress = data[6...11].joined(":") + messagePush = data[12].int + media = data[13].int + customerID = data[14].int + productID = data[15].int + hardwareVersion = data[16...17].joined(format: "%d", ".") + bluetoothVersion = data[18...19].joined(format: "%d", ".") + } + + /// 电量 + let battery: Int + /// 表盘ID + let dialID: Int + /// 固件版本 + let firmwareVersion: String + /// mac 地址 + let macAddress: String + /// 消息推送 1=开启 + let messagePush: Int + /// 多媒体 1=开启 + let media: Int + /// 硬件版本 + let hardwareVersion: String + /// 蓝牙版本 + let bluetoothVersion: String + /// 客户 ID + let customerID: Int + /// 产品 ID + let productID: Int +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBWater.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBWater.swift new file mode 100644 index 0000000..1da5ebd --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBWater.swift @@ -0,0 +1,45 @@ +// +// IBWater.swift +// IOTBluetooth +// +// Created by incar on 2024/5/15. +// + + +import Foundation +/// 喝水提醒设置模型 +public class IBWater { + /// 0:关闭 1:开启 为0时后面所有项目都无需设置。 + public var alertState: Int = 0 + /// 提醒方式0:每天提醒一次 1:间隔提醒 + public var alertType: Int = 0 + /// 饮水目标 500~5000ml + public var drinkingValue: Int = 0 + /// 提醒开始时间 + public var start:IBTimeItem = IBTimeItem(hour: 0, minute: 0) + /// 提醒结束时间。仅间隔提醒需要,其他情况不填 + public var end:IBTimeItem = IBTimeItem(hour: 0, minute: 0) + /// 提醒频率。仅间隔提醒需要,其他情况不填 + public var frequency: IBFrequency = IBFrequency.min30 + + public init() { } + + var description: String { + return "IBWater(alertState: \(alertState), alertType:\(alertType), drinkingValue:\(drinkingValue), start:\(start.description), end:\(end.description), frequency:\(frequency.rawValue))" + } +} + + +/// 提醒频率 +public enum IBFrequency: Int { + /// 30分钟 + case min30 = 0 + /// 1小时 + case hour1 + /// 2小时 + case hour2 + /// 3小时 + case hour3 + /// 其他情况 + case none +} diff --git a/code/IOTBluetooth/IOTBluetooth/Models/IBWeather.swift b/code/IOTBluetooth/IOTBluetooth/Models/IBWeather.swift new file mode 100644 index 0000000..5409798 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Models/IBWeather.swift @@ -0,0 +1,70 @@ +// +// IBWeather.swift +// IOTBluetooth +// +// Created by incar on 2024/5/30. +// + +import Foundation +/// 天气模型 +public class IBWeather:Codable { + /// 当前定位地址 + public var address: IBAddress = IBAddress() + /// 当前天气信息 + public var now: IBWeatherNow = IBWeatherNow() + /// 七天天气信息 + public var daily: [IBWeatherInfo] = [] + + + public init() { } +} + + +/// 地址模型 +public class IBAddress:Codable { + /// 城市 + public var city: String = "" + /// 区 + public var subLocality: String = "" + /// 纬度,该值除以10000000得 + public var lat: Int = 0 + /// 经度,该值除以10000000得到实际纬度值 + public var lon: Int = 0 + /// 气压,单位hPa + public var atm: Int = 0 + + public init() { } +} + +/// 天气信息模型 +public class IBWeatherInfo:Codable { + /// 预报日期,时间戳 + public var fxDate: Int = 1654012800 + /// 预报白天天气状况的图标代码 + public var iconDay: Int = 0 + /// 预报白天天气状况文字描述,包括阴晴雨雪等天气状态的描述 + public var textDay: String = "" + /// 预报当天最高温度 + public var tempMax: Int = 0 + /// 预报当天最低温度 + public var tempMin: Int = 0 + + public init() { } +} +/// 现在天气信息模型 +public class IBWeatherNow:Codable { + /// 当前温度 + public var temp: Int = 0 + /// 夜晚天气icon图标 + public var iconNight: Int = 0 + /// 夜晚天气情况 + public var textNight: String = "" + /// 风力 + public var windScale: String = "" + /// 湿度% + public var humidity: Int = 0 + /// 紫外线强度指数 + public var uvIndex: Int = 0 + + public init() { } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Tools/Any.swift b/code/IOTBluetooth/IOTBluetooth/Tools/Any.swift new file mode 100644 index 0000000..662e7e1 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Tools/Any.swift @@ -0,0 +1,196 @@ +// +// Any.swift +// MyBase +// +// Created by Harvey on 2021/6/2. +// + +import Foundation + +/// 判断可选类型是否为 nil +func isNil(_ any: Any?) -> Bool { + switch any { + case .none: return true + case .some: return false + } +} + +/// 判断可选类型是否有值 +func isValue(_ any: Any?) -> Bool { + return isNil(any) == false +} + +/// for Array +func isEmpty(_ value: [Any]?) -> Bool { + return isNil(value) || value!.count == 0 +} + +/// for Array +func isNotEmpty(_ value: [Any]?) -> Bool { + return isEmpty(value) == false +} + +/// for Dictionary +func isEmpty(_ value: [AnyHashable: Any]?) -> Bool { + return isNil(value) || value!.count == 0 +} + +/// for Dictionary +func isNotEmpty(_ value: [AnyHashable: Any]?) -> Bool { + return isEmpty(value) == false +} + +/// for String +func isEmpty(_ value: String?) -> Bool { + return isNil(value) || value!.count == 0 +} + +/// for String +func isNotEmpty(_ value: String?) -> Bool { + return isEmpty(value) == false +} + +public extension String { + + /// 补足8位(长度为8的整数倍) + var binary: String { + let bytes = (Double(count) / 8.0).upInt + let offset = bytes * 8 - count + let prefix = Array(repeating: "0", count: offset).joined() + return prefix + self + } +} + +public extension Double { + var upInt: Int { Int(ceil(self)) } +} + +public extension Int { + private static let binaryPack = 0b11111111 + + /// convert to data with byte (max is 4294967295) + /// + /// 低位 --> 高位 + var bytes: [UInt8] { + var mutatingSelf = self + var bytes = [UInt8(mutatingSelf & Self.binaryPack)] + + var count = 1 + mutatingSelf = mutatingSelf >> (count * 8) + while mutatingSelf > 0 { + bytes.append(UInt8(mutatingSelf & Self.binaryPack)) + mutatingSelf = mutatingSelf >> (count * 8) + count = count + 1 + } + return bytes + } + + /// convert to data with 1 byte + var byte: UInt8 { + UInt8(self & Self.binaryPack) + } + + /// convert to data with 2 byte + /// + /// 低位 --> 高位 + var byte2: [UInt8] { + [ + UInt8(self & Self.binaryPack), + UInt8(self >> 8 & Self.binaryPack) + ] + } + + /// convert to data with 3 byte + /// + /// 低位 --> 高位 + var byte3: [UInt8] { + [ + UInt8(self & Self.binaryPack), + UInt8(self >> 8 & Self.binaryPack), + UInt8(self >> 16 & Self.binaryPack) + ] + } + + /// convert to data with 4 byte + /// + /// 低位 --> 高位 + var byte4: [UInt8] { + [ + UInt8(self & Self.binaryPack), + UInt8(self >> 8 & Self.binaryPack), + UInt8(self >> 16 & Self.binaryPack), + UInt8(self >> 24 & Self.binaryPack) + ] + } +} + +public extension BinaryInteger { + + /// e.g. 0000100110010101 + var binaryString: String { + .init(self, radix: 2).binary + } +} + +public extension Array where Element == UInt8 { + + var int: Int { + self + .map { Int($0) } + .enumerated() + .map { $1 << ($0 * 8) } + .reduce(0, { partialResult, value in + partialResult | value + }) + } + + var data: Data { Data(self) } +} + +public extension Data { + var bytes: [UInt8] { map { $0 } } + + func joined(format: String = "%02x", _ separator: String = "") -> String { + self + .map { String(format: format, $0) } + .joined(separator: separator) + } + + func reversedJoined(format: String = "%02x", _ separator: String = "") -> String { + self + .map { String(format: format, $0) } + .reversed() + .joined(separator: separator) + } + + var int: Int { + self + .map { Int($0) } + .enumerated() + .map { $1 << ($0 * 8) } + .reduce(0, { partialResult, value in + partialResult | value + }) + } + + var lrc: UInt8 { LRC.calculateLRC(bytes) } +} + +public extension UInt8 { + var int: Int { Int(self) } +} + + +extension Data{ + var float: Float { + var floatValue: Float = 0.0 + let size = MemoryLayout.size + guard self.count >= size else { return 0 } + + self.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in + _ = memcpy(&floatValue, buffer.baseAddress!, size) + } + + return floatValue + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Tools/CRC16.swift b/code/IOTBluetooth/IOTBluetooth/Tools/CRC16.swift new file mode 100644 index 0000000..0025438 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Tools/CRC16.swift @@ -0,0 +1,44 @@ +// +// CRC16.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/12. +// + +import Foundation + +/// CRC-16/MODBUS +class CRC16 { + static let shared = CRC16() + private init() { } + + func result(_ bytes: [UInt8]) -> [UInt8] { + var crcValue = crc(bytes) + var crcBytes: [UInt8] = [0, 0] + for index in (0..<2).reversed() { + crcBytes[index] = UInt8(crcValue % 256) + crcValue >>= 8 + } + return crcBytes + } + + private func crc(_ bytes: [UInt8]) -> Int { + + var crcSum: Int = 0xffff + let polynomial: Int = 0xa001 + let length = bytes.count + + for index in 0..>= 1 + crcSum ^= polynomial + }else{ + crcSum >>= 1 + } + } + } + return crcSum + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Tools/Date.swift b/code/IOTBluetooth/IOTBluetooth/Tools/Date.swift new file mode 100644 index 0000000..6f0e492 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Tools/Date.swift @@ -0,0 +1,148 @@ +// +// Date.swift +// MyBase +// +// Created by Harvey on 2021/5/18. +// + +import Foundation + +// MARK: - Extension Date +extension Date { + + static let formatter = DateFormatter() + + struct Format { + + let rawValue: String + init(_ rawValue: String) { + self.rawValue = rawValue + } + + /// e.g. 01:01:01 + static let time: Format = .init("HH:mm:ss") + + /// e.g. 01:01 + static let shortTime: Format = .init("HH:mm") + + /// e.g. 01-01 + static let date: Format = .init("MM-dd") + + /// e.g. 01月01日 + static let date1: Format = .init("MM月dd日") + + /// e.g. 2020-01-01 + static let short: Format = .init("yyyy-MM-dd") + + /// e.g. 2020å¹´01月01日 + static let short1: Format = .init("yyyå¹´MM月dd日") + + /// e.g. 2020-01-01 01:01 + static let middle: Format = .init("yyyy-MM-dd HH:mm") + + /// e.g. 2020å¹´01月01日 01:01 + static let middle1: Format = .init("yyyå¹´MM月dd日 HH:mm") + + /// e.g. 2020-01-01 01:01:01 + static let long: Format = .init("yyyy-MM-dd HH:mm:ss") + + /// e.g. 2020å¹´01月01日 01:01:01 + static let long1: Format = .init("yyyyå¹´MM月dd日 HH:mm:ss") + + /// e.g. 2020-01-01 010101 + static let long2: Format = .init("yyyy-MM-dd HHmmss") + + /// e.g. 2020-01-01 01:01:01.088 + static let full: Format = .init("yyyy-MM-dd HH:mm:ss.SSS") + + /// e.g. 2020å¹´01月01日 01:01:01.088 + static let full1: Format = .init("yyyyå¹´MM月dd日 HH:mm:ss.SSS") + } + + /// 格式化成本地时间 + func format(_ format: Date.Format = .middle) -> String { + Self.formatter.dateFormat = format.rawValue + return Self.formatter.string(from: self) + } + + enum Unit { + case minute(Int) + case hour(Int) + case day(Int) + + /// 转换成秒 + var second: TimeInterval { + switch self { + case .minute(let minute): return 60.0 * Double(minute) + case .hour(let hour): return 60.0 * 60.0 * Double(hour) + case .day(let day): return 60.0 * 60.0 * 24.0 * Double(day) + } + } + } +} + +let today: Date = .init() + +/// 本地化 +extension Date { + /// 星期几 + enum Week: Int { + /// 星期日 + case sun = 1 + /// 星期一 + case mon + /// 星期二 + case tue + /// 星期三 + case wed + /// 星期四 + case thur + /// 星期五 + case fri + /// 星期六 + case sat + } + +// /// 当天的第一秒 +// var fromTimeInterval: TimeInterval { +// let today = format(.short) +// return today.timeIntervalSince1970(.short) +// } +// +// /// 当天最后一秒 +// var toTimeInterval: TimeInterval { fromTimeInterval + Unit.day(1).second } + + /// 哪一年 + var year: Int { component(.year) } + + /// 哪个月 + var month: Int { component(.month) } + + /// 当月的第几天 + var day: Int { component(.day) } + + /// 当月的第几天 + var hour: Int { component(.hour)} + + /// 当月的第几天 + var minute: Int { component(.minute)} + + /// 当月的第几天 + var second: Int { component(.second)} + + /// 当月的第几周 + var weekOfMonth: Int { component(.weekOfMonth) } + + /// 今年的第几周 + var weekOfYear: Int { component(.weekOfYear) } + + /// 星期几 + var weekDay: Week { Week(rawValue: component(.weekday))! } + + /// 假设当天是周五,表示本月的第几个周五 + var weekdayOrdinal: Int { component(.weekdayOrdinal) } + + private func component(_ component: Calendar.Component) -> Int { + return Calendar.autoupdatingCurrent.component(component, from: self) + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Tools/IBPeripheralUUID.swift b/code/IOTBluetooth/IOTBluetooth/Tools/IBPeripheralUUID.swift new file mode 100644 index 0000000..285800b --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Tools/IBPeripheralUUID.swift @@ -0,0 +1,23 @@ +// +// IBPeripheralUUID.swift +// IOTBluetooth +// +// Created by Harvey on 2024/2/5. +// + +import CoreBluetooth + +public protocol IBPeripheralUUID { + /// CBPeripheral.identifier + var uuid: UUID { get } +} + +extension UUID: IBPeripheralUUID { + + public var uuid: UUID { self } +} + +public func == (lhs: IBPeripheralUUID?, rhs: IBPeripheralUUID?) -> Bool { + guard let lhs = lhs, let rhs = rhs else { return false } + return lhs.uuid == rhs.uuid +} diff --git a/code/IOTBluetooth/IOTBluetooth/Tools/IBTools.swift b/code/IOTBluetooth/IOTBluetooth/Tools/IBTools.swift new file mode 100644 index 0000000..0965990 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Tools/IBTools.swift @@ -0,0 +1,35 @@ +// +// IBTools.swift +// IOTBluetooth +// +// Created by incar on 2024/7/17. +// + +import Foundation + +class IBTools{ + static func saveToFile(data:Data)->String?{ + let filePath = NSTemporaryDirectory() + "temp.pcm" + print(filePath) + + let url = URL(fileURLWithPath: filePath) + do { + try data.write(to: url, options: .atomic) + return filePath + } + catch{ + print(error) + return nil + } + } + + static func readFile(from path:String) ->Data?{ + let fileURL = URL(fileURLWithPath: path) + do { + let data = try Data(contentsOf:fileURL) + return data + } catch { + return nil + } + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Tools/JSON.swift b/code/IOTBluetooth/IOTBluetooth/Tools/JSON.swift new file mode 100644 index 0000000..9c94ed9 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Tools/JSON.swift @@ -0,0 +1,115 @@ +// +// JSON.swift +// MyBase +// +// Created by Harvey on 2024/1/7. +// + +import Foundation + + struct JSON { } + + extension Dictionary { + + func jsonString(options: JSONSerialization.WritingOptions = [.fragmentsAllowed]) -> String? { + guard JSONSerialization.isValidJSONObject(self) else { + Logger.log("无效的 JSONObject") + return nil + } + guard let data = try? JSONSerialization.data(withJSONObject: self, options: options) else { return nil } + return data.string + } +} + + extension Array { + + func jsonString(options: JSONSerialization.WritingOptions = [.fragmentsAllowed]) -> String? { + guard JSONSerialization.isValidJSONObject(self) else { + Logger.log("无效的 JSONObject") + return nil + } + guard let data = try? JSONSerialization.data(withJSONObject: self, options: options) else { return nil } + return data.string + } +} + + extension String { + + func jsonObject(options: JSONSerialization.ReadingOptions = [.mutableContainers]) -> Any? { + guard let data = self.data else { return nil } + return data.jsonObject(options: options) + } +} + + extension Data { + + func jsonObject(options: JSONSerialization.ReadingOptions = [.mutableContainers]) -> Any? { + do { return try JSONSerialization.jsonObject(with: self, options: options) } + catch { Logger.log(error) } + return nil + } +} + +// MARK: - Extension Codable + + extension Encodable { + + /// JSON encode + var encode: Data? { + do { + return try JSONEncoder().encode(self) + } catch { Logger.log(error) } + return nil + } + + var jsonString: String? { + encode?.string + } +} + + extension Decodable { + + /// JSON decode + static func decode(_ from: Data) -> Self? { + do { + return try JSONDecoder().decode(Self.self, from: from) + } catch { Logger.log(error) } + return nil + } + + /// JSON decode + static func decode(_ from: String) -> Self? { + guard let data = from.data else { return nil } + return decode(data) + } +} + + extension JSON { + /// JSON decode + static func decode(_ decodable: T.Type, from data: Data) -> T? { + do { + return try JSONDecoder().decode(decodable.self, from: data) + } catch { Logger.log(error) } + return nil + } + + /// JSON decode + static func decode(_ decodable: T.Type, from jsonString: String) -> T? { + guard let data = jsonString.data else { return nil } + return decode(decodable, from: data) + } +} + + extension Array where Element: Encodable { + /// JSON encode + var jsonData: Data? { + do { + return try JSONEncoder().encode(self) + } catch { Logger.log(error) } + return nil + } + + var jsonString: String? { + encode?.string + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Tools/LRC.swift b/code/IOTBluetooth/IOTBluetooth/Tools/LRC.swift new file mode 100644 index 0000000..8270a6a --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Tools/LRC.swift @@ -0,0 +1,29 @@ +// +// LRC.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/13. +// + +import Foundation + +public struct LRC { + + public static func calculateLRC(_ bytes: [UInt8]) -> UInt8 { + let sum = bytes.map({ Int($0) }).reduce(0) { partialResult, value in + partialResult + value + } + + let lrc = sum % 256 + return UInt8(lrc == 0 ? 0 : 256 - lrc) + } + + public static func calculate(_ bytes: [UInt8]) -> UInt8 { + let sum = bytes.map({ Int($0) }).reduce(into: 0) { partialResult, value in + partialResult ^= value + } + return sum.byte + } +} + + diff --git a/code/IOTBluetooth/IOTBluetooth/Tools/Logger.swift b/code/IOTBluetooth/IOTBluetooth/Tools/Logger.swift new file mode 100644 index 0000000..e3c23bc --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Tools/Logger.swift @@ -0,0 +1,69 @@ +// +// Logger.swift +// +// +// Created by Harvey on 2021/4/27. +// + +import Foundation + +/// 日记管理 +public class Logger { + static var isDebug: Bool = false + + fileprivate static let shared = Logger() + private var fileHandle:FileHandle? + private init() { + let path = getPath() + fileHandle = FileHandle(forWritingAtPath: path)! +// fileHandle?.seekToEndOfFile() + } + + fileprivate var dateString: String { Date().format(.full) } + + static var logs: [String] = [] + + + private func getPath()->String{ +// let path = NSHomeDirectory() + "/Documents/IOTBluetooth/" + "\(Date().format(.middle)).log" + let foldPath = NSHomeDirectory() + "/Documents/IOTBluetooth/" + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: foldPath) { + do { + try FileManager.default.createDirectory(atPath: foldPath, withIntermediateDirectories: true, attributes: nil) + } catch { + return "" + } + } + let path = foldPath + "\(Date().format(.long2)).log" + if !FileManager.default.fileExists(atPath: path) { + FileManager.default.createFile(atPath: path, contents: nil, attributes: nil) + } + return path + } +} + +public extension Logger { + static func log(_ logs: Any..., line: Int = #line, separator: String = " ", terminator: String = "\n") { + objc_sync_enter(Logger.shared) + let logString = Logger.shared.dateString + " [IOTBluetooth-\(String(format: "%03d", line))]: " + logs.map({ "\($0)" }).joined(separator: separator) + terminator + if Logger.isDebug { + print(Logger.shared.dateString, terminator: " [IOTBluetooth-\(String(format: "%03d", line))]: ") + print(logs, separator: separator, terminator: terminator) + Self.logs.append(logString) + } + Logger.shared.saveLog(log: logString) + objc_sync_exit(Logger.shared) + + + } + + private func saveLog(log:String){ + fileHandle?.write(log.data(using: .utf8)!) +// if #available(iOS 13.0, *) { +// try? fileHandle.close() +// } else { +// // Fallback on earlier versions +// } + } +} diff --git a/code/IOTBluetooth/IOTBluetooth/Tools/UTF8.swift b/code/IOTBluetooth/IOTBluetooth/Tools/UTF8.swift new file mode 100644 index 0000000..a88e787 --- /dev/null +++ b/code/IOTBluetooth/IOTBluetooth/Tools/UTF8.swift @@ -0,0 +1,16 @@ +// +// UTF8.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/6. +// + +import Foundation + +public extension Data { + var string: String? { String(data: self, encoding: .utf8) } +} + +public extension String { + var data: Data? { self.data(using: .utf8) } +} diff --git a/document/2024.10.17 2ae6.docx b/document/2024.10.17 2ae6.docx new file mode 100644 index 0000000..31fed24 Binary files /dev/null and b/document/2024.10.17 2ae6.docx differ diff --git "a/document/\346\227\245\345\277\227/crashlog.crash" "b/document/\346\227\245\345\277\227/crashlog.crash" new file mode 100644 index 0000000..7ef0126 --- /dev/null +++ "b/document/\346\227\245\345\277\227/crashlog.crash" @@ -0,0 +1,359 @@ +Incident Identifier: 3411013E-3426-431E-96C5-F1BE8887879B +Distributor ID: com.apple.TestFlight +Hardware Model: iPhone15,4 +Process: f002 [785] +Path: /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/f002 +Identifier: com.fengeek.f002 +Version: 3.4.27 (50) +AppStoreTools: 16B39 +AppVariant: 1:iPhone15,4:18 +Beta: YES +Code Type: ARM-64 (Native) +Role: Foreground +Parent Process: launchd [1] +Coalition: com.fengeek.f002 [677] + +Date/Time: 2024-11-04 16:05:00.8204 +0800 +Launch Time: 2024-11-04 15:36:23.8518 +0800 +OS Version: iPhone OS 18.0.1 (22A3370) +Release Type: User +Baseband Version: 2.16.06 +Report Version: 104 + +Exception Type: EXC_BREAKPOINT (SIGTRAP) +Exception Codes: 0x0000000000000001, 0x000000010a5abaa0 +Triggered by Thread: 16 + + +Thread 0 name: +Thread 0: +0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1) +1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201) +2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0) +3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323) +4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617) +5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001) +6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414) +7 GraphicsServices 0x00000001d605e1c4 GSEventRunModal + 164 (GSEvent.c:2196) +8 UIKitCore 0x000000018d11e2c0 -[UIApplication _run] + 816 (UIApplication.m:3825) +9 UIKitCore 0x000000018d1ccddc UIApplicationMain + 340 (UIApplication.m:5477) +10 f002 0x00000001058fc5e4 main + 64 (AppDelegate.swift:20) +11 dyld 0x00000001afd9cd34 start + 2724 (dyldMain.cpp:1334) + +Thread 1 name: +Thread 1: +0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1) +1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201) +2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0) +3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323) +4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617) +5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001) +6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414) +7 Foundation 0x000000018925f000 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:373) +8 Foundation 0x000000018925ee50 -[NSRunLoop(NSRunLoop) runUntilDate:] + 64 (NSRunLoop.m:420) +9 UIKitCore 0x000000018d131714 -[UIEventFetcher threadMain] + 420 (UIEventFetcher.m:1241) +10 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991) +11 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +12 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 2 name: +Thread 2: +0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1) +1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201) +2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0) +3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323) +4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617) +5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001) +6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414) +7 Foundation 0x000000018925f000 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:373) +8 Foundation 0x000000018925eed4 -[NSRunLoop(NSRunLoop) run] + 64 (NSRunLoop.m:398) +9 f002 0x0000000106329568 +[UMSocialTaskOperation networkRequestThreadEntryPoint:] + 184 +10 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991) +11 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +12 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 3 name: +Thread 3: +0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1) +1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201) +2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0) +3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323) +4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617) +5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001) +6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414) +7 CoreFoundation 0x000000018a634850 CFRunLoopRun + 64 (CFRunLoop.c:3440) +8 f002 0x0000000106108214 +[JCORECacheThread run] + 180 (JCORECacheThread.m:31) +9 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991) +10 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +11 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 4 name: +Thread 4: +0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1) +1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201) +2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0) +3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323) +4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617) +5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001) +6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414) +7 Foundation 0x000000018925f000 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:373) +8 Foundation 0x000000018925eed4 -[NSRunLoop(NSRunLoop) run] + 64 (NSRunLoop.m:398) +9 f002 0x00000001060914c4 +[JCOREQueueManager threadEntryPoint:] + 216 (JCOREQueueManager.m:32) +10 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991) +11 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +12 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 5 name: +Thread 5: +0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1) +1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201) +2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0) +3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323) +4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617) +5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001) +6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414) +7 CoreFoundation 0x000000018a634850 CFRunLoopRun + 64 (CFRunLoop.c:3440) +8 f002 0x00000001060a2590 +[JCORENetworkThread run] + 180 (JCORENetworkController.m:40) +9 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991) +10 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +11 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 6: +0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1) +1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862) +2 f002 0x000000010735f084 native_event_wait + 208 +3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228 +4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228 +5 f002 0x00000001073757c0 socket_main + 224 +6 f002 0x00000001073605d0 ThreadEntryHook + 548 +7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 7: +0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1) +1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862) +2 f002 0x000000010735f084 native_event_wait + 208 +3 f002 0x00000001073254ec dns_main + 108 +4 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +5 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 8: +0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1) +1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862) +2 f002 0x000000010735f084 native_event_wait + 208 +3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228 +4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228 +5 f002 0x000000010738d27c iFlylua_engine_main + 2832 +6 f002 0x00000001073605d0 ThreadEntryHook + 548 +7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 9: +0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1) +1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862) +2 f002 0x000000010735f084 native_event_wait + 208 +3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228 +4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228 +5 f002 0x000000010738d27c iFlylua_engine_main + 2832 +6 f002 0x00000001073605d0 ThreadEntryHook + 548 +7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 10: +0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1) +1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862) +2 f002 0x000000010735f084 native_event_wait + 208 +3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228 +4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228 +5 f002 0x000000010738d27c iFlylua_engine_main + 2832 +6 f002 0x00000001073605d0 ThreadEntryHook + 548 +7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 11: +0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1) +1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862) +2 f002 0x000000010735f084 native_event_wait + 208 +3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228 +4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228 +5 f002 0x000000010738d27c iFlylua_engine_main + 2832 +6 f002 0x00000001073605d0 ThreadEntryHook + 548 +7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 12: +0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1) +1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862) +2 f002 0x000000010735f084 native_event_wait + 208 +3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228 +4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228 +5 f002 0x000000010738d27c iFlylua_engine_main + 2832 +6 f002 0x00000001073605d0 ThreadEntryHook + 548 +7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 13 name: +Thread 13: +0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1) +1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201) +2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0) +3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323) +4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617) +5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001) +6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414) +7 CFNetwork 0x000000018bb40f44 +[__CFN_CoreSchedulingSetRunnable _run:] + 416 (CoreSchedulingSet.mm:1473) +8 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991) +9 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +10 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 14 name: +Thread 14: +0 libsystem_kernel.dylib 0x00000001da4142d4 __select + 8 (:-1) +1 CoreFoundation 0x000000018a6324c0 __CFSocketManager + 704 (CFSocket.c:1341) +2 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +3 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 15: +0 libsystem_kernel.dylib 0x00000001da412250 __semwait_signal + 8 (:-1) +1 libsystem_c.dylib 0x00000001922cba78 nanosleep + 220 (nanosleep.c:104) +2 libsystem_c.dylib 0x0000000192329550 sleep + 52 (sleep.c:62) +3 StarburstConnectKit 0x000000010a63705c _mqtt_process_thread + 56 +4 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +5 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 16 Crashed: +0 StarburstConnectKit 0x000000010a5abaa0 MqttFileManager.outputProgress() + 980 +1 StarburstConnectKit 0x000000010a5a9110 MqttFileManager.onMessageArrival(topic:payload:) + 1180 +2 StarburstConnectKit 0x000000010a520824 AliCloudIotWatchManager_C.onMessageArrival(topic:payload:) + 440 +3 StarburstConnectKit 0x000000010a559d94 closure #2 in IotMqttManager_C.setupSdkWrapperCallback() + 416 +4 StarburstConnectKit 0x000000010a559e4c thunk for @escaping @callee_guaranteed (@guaranteed String, @guaranteed String) -> () + 88 +5 StarburstConnectKit 0x000000010a49d718 __30-[MqttManager_C setupCallback]_block_invoke_2 + 568 +6 StarburstConnectKit 0x000000010a636c94 _mqtt_default_recv_handler + 324 +7 StarburstConnectKit 0x000000010a63f158 _core_mqtt_pub_handler + 1464 +8 StarburstConnectKit 0x000000010a63e1b4 aiot_mqtt_recv + 1288 +9 StarburstConnectKit 0x000000010a63709c _mqtt_recv_thread + 40 +10 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +11 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 17: +0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1) +1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201) +2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0) +3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323) +4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617) +5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001) +6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414) +7 ASIHTTPRequest 0x00000001091f5670 +[ASIHTTPRequest runRequests] + 140 (ASIHTTPRequest.m:4797) +8 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991) +9 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +10 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 18 name: +Thread 18: +0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1) +1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201) +2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0) +3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323) +4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617) +5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001) +6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414) +7 Foundation 0x000000018925f000 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:373) +8 StarburstSdk 0x000000010bf22c34 -[SRRunLoopThread main] + 216 +9 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991) +10 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931) +11 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1) + +Thread 19: +0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1) + +Thread 20: +0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1) + +Thread 21 name: +Thread 21: +0 libsystem_kernel.dylib 0x00000001da412250 __semwait_signal + 8 (:-1) +1 libsystem_c.dylib 0x00000001922cba78 nanosleep + 220 (nanosleep.c:104) +2 libsystem_c.dylib 0x0000000192329550 sleep + 52 (sleep.c:62) +3 f002 0x00000001062c7c40 __48-[BLYLogicManager abortAfterSendingReportIfNeed]_block_invoke + 36 +4 libdispatch.dylib 0x0000000192270370 _dispatch_call_block_and_release + 32 (init.c:1549) +5 libdispatch.dylib 0x00000001922720d0 _dispatch_client_callout + 20 (object.m:576) +6 libdispatch.dylib 0x0000000192283f60 _dispatch_root_queue_drain + 860 (queue.c:7331) +7 libdispatch.dylib 0x0000000192284590 _dispatch_worker_thread2 + 156 (queue.c:7399) +8 libsystem_pthread.dylib 0x0000000211963c40 _pthread_wqthread + 228 (pthread.c:2709) +9 libsystem_pthread.dylib 0x0000000211960488 start_wqthread + 8 (:-1) + +Thread 22: +0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1) + +Thread 23: +0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1) + +Thread 24: +0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1) + +Thread 25: +0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1) + + +Thread 16 crashed with ARM Thread State (64-bit): + x0: 0xe000000000000000 x1: 0x0000000000000000 x2: 0x00000001edacf940 x3: 0x0000000188faad4c + x4: 0x00000001edaec480 x5: 0x000000016b40e688 x6: 0x000000016b40e680 x7: 0x0000000000000001 + x8: 0x000000007fc00000 x9: 0x000000007f800000 x10: 0x0000000000000000 x11: 0xc000000000000000 + x12: 0x0000000000000003 x13: 0x000000010a5abad8 x14: 0x000000010a5ab750 x15: 0x0000000000000038 + x16: 0x000000018900926c x17: 0x871080018a57b310 x18: 0x0000000000000000 x19: 0x00000003035ea520 + x20: 0x00000003035ea520 x21: 0x0000000000000018 x22: 0x40000003004f73c0 x23: 0x00000003035ea550 + x24: 0x000000016b40e6d0 x25: 0x40000003035fd260 x26: 0xc00000000000003f x27: 0x0000000303aaf3a0 + x28: 0x000000010a709650 fp: 0x000000016b40e6c0 lr: 0x000000010a5a9110 + sp: 0x000000016b40e620 pc: 0x000000010a5abaa0 cpsr: 0x40001000 + esr: 0x56000080 Address size fault + + +Binary Images: + 0x104ca8000 - 0x107e3bfff f002 arm64 <0320a73500ea3635a097cc991f7d6a36> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/f002 + 0x10907c000 - 0x109087fff libobjc-trampolines.dylib arm64e /private/preboot/Cryptexes/OS/usr/lib/libobjc-trampolines.dylib + 0x109168000 - 0x10916ffff DeviceUtil arm64 <5b7002a6e4ae393898153b49c2f30a09> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/DeviceUtil.framework/DeviceUtil + 0x109180000 - 0x109187fff KeychainItemWrapper_hicool arm64 <0aecce381ae03c38a0d9259834776667> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/KeychainItemWrapper_hicool.framework/KeychainItemWrapper_hicool + 0x109194000 - 0x1091affff CocoaLumberjack arm64 <210f1a7f805d396b806eeebf33043c82> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/CocoaLumberjack.framework/CocoaLumberjack + 0x1091dc000 - 0x10920ffff ASIHTTPRequest arm64 <19c5ad39057336ea8628b1bd076e7edc> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/ASIHTTPRequest.framework/ASIHTTPRequest + 0x109268000 - 0x10926ffff Reachability arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/Reachability.framework/Reachability + 0x109290000 - 0x10929ffff FMDB arm64 <0a4574e0c56939f8a3e37d31638c7f8b> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/FMDB.framework/FMDB + 0x1092b8000 - 0x1092c7fff MBProgressHUD arm64 <1b35240fbb303e9ebe22109e2e663b29> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/MBProgressHUD.framework/MBProgressHUD + 0x1092dc000 - 0x1092ebfff Masonry arm64 <4323d49838413f8586cd40ba8186f27d> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/Masonry.framework/Masonry + 0x1093b8000 - 0x1093dffff HandyJSON arm64 <14b18b15e89e3d93a186a51d7560c99a> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/HandyJSON.framework/HandyJSON + 0x109428000 - 0x10943ffff MJRefresh arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/MJRefresh.framework/MJRefresh + 0x109464000 - 0x10946bfff RxRelay arm64 <7fa90ff5197b3f7ebdc082a2ce376a9b> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/RxRelay.framework/RxRelay + 0x109480000 - 0x10948bfff WebViewJavascriptBridge arm64 <56de5e334eb230ff8b976207dc0cf3e1> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/WebViewJavascriptBridge.framework/WebViewJavascriptBridge + 0x1094bc000 - 0x109517fff Alamofire arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/Alamofire.framework/Alamofire + 0x1095c0000 - 0x1095cffff FOTA arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/FOTA.framework/FOTA + 0x109608000 - 0x109623fff SDWebImage arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/SDWebImage.framework/SDWebImage + 0x10966c000 - 0x1096d3fff Logan arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/Logan.framework/Logan + 0x109718000 - 0x10976bfff OpusKit arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/OpusKit.framework/OpusKit + 0x1097c4000 - 0x1097c7fff iCloudDriveFileProviderOverride arm64e /System/Library/Frameworks/FileProvider.framework/OverrideBundles/iCloudDriveFileProviderOverride.bundle/iCloudDriveFileProviderOverride + 0x1097d8000 - 0x1097effff DequeModule arm64 <1ee643420f0e35c2bcc4d7f17ba3246d> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/DequeModule.framework/DequeModule + 0x109890000 - 0x1098c7fff DeviceManager arm64 <01f9ebcc6b43309dbfcf690e05e759ce> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/DeviceManager.framework/DeviceManager + 0x1098f8000 - 0x10996bfff RxCocoa arm64 <700265132423300abd58e67fec50fec4> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/RxCocoa.framework/RxCocoa + 0x109a6c000 - 0x109ae3fff SQLite arm64 <213c6061424036b7a3ae409de50876b0> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/SQLite.framework/SQLite + 0x109c6c000 - 0x109d1bfff RxSwift arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/RxSwift.framework/RxSwift + 0x109e74000 - 0x109e9ffff CocoaAsyncSocket arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/CocoaAsyncSocket.framework/CocoaAsyncSocket + 0x109ff8000 - 0x109ffffff FileProviderOverride arm64e <39396754b75931aab410d42869570a7f> /System/Library/Frameworks/FileProvider.framework/OverrideBundles/FileProviderOverride.bundle/FileProviderOverride + 0x10a010000 - 0x10a093fff FIILBluetoothSDK arm64 <57216117281d3a8097dc3b77cd08f8c1> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/FIILBluetoothSDK.framework/FIILBluetoothSDK + 0x10a488000 - 0x10a6bffff StarburstConnectKit arm64 <4d919c9bb7ef35b89df0c74d01609e6c> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/StarburstConnectKit.framework/StarburstConnectKit + 0x10aaac000 - 0x10ab9bfff Airoha1530 arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/Airoha1530.framework/Airoha1530 + 0x10bf0c000 - 0x10c08bfff StarburstSdk arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/StarburstSdk.framework/StarburstSdk + 0x10c654000 - 0x10c86ffff SpeechSDK arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/SpeechSDK.framework/SpeechSDK + 0x10ca00000 - 0x10ca0bfff NativePeq arm64 <19f476b9c5af33bdb0d97bf148547821> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/NativePeq.framework/NativePeq + 0x110660000 - 0x110687fff NativeAnc arm64 <572dbe6843623f2e8138893fbba3528b> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/NativeAnc.framework/NativeAnc + 0x116708000 - 0x116e5ffff AirohaUnifyLibrary arm64 /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/AirohaUnifyLibrary.framework/AirohaUnifyLibrary + 0x188bd0000 - 0x18916ffff libswiftCore.dylib arm64e <24ec482a274c32f2aa6da809fabfdc58> /usr/lib/swift/libswiftCore.dylib + 0x1891a8000 - 0x189ec6fff Foundation arm64e <6d058a2d05cf3a298bebb1030ae97282> /System/Library/Frameworks/Foundation.framework/Foundation + 0x18a575000 - 0x18aabafff CoreFoundation arm64e <4250d45c51a93ac58c3d69de936fad46> /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation + 0x18ba43000 - 0x18be05fff CFNetwork arm64e <7d799a941e3c3a4fb1a0d9414e8eaca5> /System/Library/Frameworks/CFNetwork.framework/CFNetwork + 0x18cd40000 - 0x18ec00fff UIKitCore arm64e <187c890e8e4c32f2a97efa905a55f094> /System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore + 0x19226e000 - 0x1922b3fff libdispatch.dylib arm64e /usr/lib/system/libdispatch.dylib + 0x1922b4000 - 0x192333ff3 libsystem_c.dylib arm64e <1ad666a001a831c188b9808915fb97b4> /usr/lib/system/libsystem_c.dylib + 0x1afd69000 - 0x1afdec693 dyld arm64e <5db839882ee63756bd07b8d67b1133a5> /usr/lib/dyld + 0x1d605d000 - 0x1d6065fff GraphicsServices arm64e <227122eb72fd3ecb9f66986addd8e048> /System/Library/PrivateFrameworks/GraphicsServices.framework/GraphicsServices + 0x1da40b000 - 0x1da444ffb libsystem_kernel.dylib arm64e <8d929b3bd4d639bfadbbbe59e928b332> /usr/lib/system/libsystem_kernel.dylib + 0x21195f000 - 0x21196bff3 libsystem_pthread.dylib arm64e /usr/lib/system/libsystem_pthread.dylib + +EOF