From: liangzhenying <867174124@qq.com> Date: Mon, 3 Jun 2024 08:59:24 +0000 (+0800) Subject: init X-Git-Url: http://git.snicker.com.cn/Users/apple/Desktop/static/git-logo.png?a=commitdiff_plain;ds=sidebyside;p=mirrors%2FIOTBluetooth-iOS.git init --- c094088b5f04ae85934dd102f76a91a9344e865f diff --git "a/Doc/230\350\223\235\347\211\231\346\211\213\350\241\250\351\200\232\350\256\257\345\215\217\350\256\256.docx" "b/Doc/230\350\223\235\347\211\231\346\211\213\350\241\250\351\200\232\350\256\257\345\215\217\350\256\256.docx" new file mode 100644 index 0000000..0ab76ca Binary files /dev/null and "b/Doc/230\350\223\235\347\211\231\346\211\213\350\241\250\351\200\232\350\256\257\345\215\217\350\256\256.docx" differ diff --git "a/Doc/\346\265\267\346\200\235\350\223\235\347\211\231SDK\345\274\200\345\217\221\350\256\241\345\210\222.xlsx" "b/Doc/\346\265\267\346\200\235\350\223\235\347\211\231SDK\345\274\200\345\217\221\350\256\241\345\210\222.xlsx" new file mode 100644 index 0000000..517995f Binary files /dev/null and "b/Doc/\346\265\267\346\200\235\350\223\235\347\211\231SDK\345\274\200\345\217\221\350\256\241\345\210\222.xlsx" differ diff --git a/IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift b/IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift new file mode 100644 index 0000000..f390f9d --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift @@ -0,0 +1,57 @@ +// +// 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() + title = "已连接" + IBManager.currentConnected!.name + } + + deinit { + IBManager.remove(self) + } +} + +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) { + activityIndicatorView.startAnimating() + let data = datas[indexPath.row] + IBSender() + .ability(data.ability) + .parameters(data.parameters) + .response {[weak self] result in + guard let self = self else {return} + if result.isSuccess{ + //请求成功 + } + else{ + //请求失败 + } + print(result) + activityIndicatorView.stopAnimating() + } + } +} diff --git a/IOTBluetooth/IOTBluetooth/Example/AppDelegate.swift b/IOTBluetooth/IOTBluetooth/Example/AppDelegate.swift new file mode 100644 index 0000000..983bdde --- /dev/null +++ b/IOTBluetooth/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. + IBManager.isDebug = true + IBManager.initializationSDK() + + return true + } +} + diff --git a/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AccentColor.colorset/Contents.json b/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/Contents.json b/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/IOTBluetooth/IOTBluetooth/Example/Base.lproj/LaunchScreen.storyboard b/IOTBluetooth/IOTBluetooth/Example/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/Example/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IOTBluetooth/IOTBluetooth/Example/Base.lproj/Main.storyboard b/IOTBluetooth/IOTBluetooth/Example/Base.lproj/Main.storyboard new file mode 100644 index 0000000..5a59854 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/Example/Base.lproj/Main.storyboard @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IOTBluetooth/IOTBluetooth/Example/BaseViewController.swift b/IOTBluetooth/IOTBluetooth/Example/BaseViewController.swift new file mode 100644 index 0000000..c7ab315 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift b/IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift new file mode 100644 index 0000000..53ec0bb --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift @@ -0,0 +1,127 @@ +// +// ConnectViewController.swift +// Example +// +// Created by yaozuopan on 2024/3/14. +// + +import UIKit +import IOTBluetooth + +class ConnectViewController: BaseViewController { + + @IBOutlet weak var dataView: UITextView! + @IBOutlet weak var dataPacketView: UITextView! + @IBOutlet weak var newLRC: UISwitch! + @IBOutlet weak var oldLRC: UISwitch! + + var peripheral: IBPeripheral! + + var dataPacket: [UInt8] = [] { + didSet { + dataPacketView.text = dataPacket.data.joined(" ") + } + } + + var isNewLRC: Bool = true { + didSet { + if isNewLRC { oldLRC.isOn = false } + if isNewLRC == false { newLRC.isOn = false } + + if isNewLRC == oldValue { return } + makeData() + } + } + + override func viewDidLoad() { + super.viewDidLoad() + title = "已连接 \(peripheral.name)" + + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "查看日记", style: .done, target: self, action: #selector(onLog(_:))) + + newLRC.addTarget(self, action: #selector(newOn(_:)), for: .valueChanged) + oldLRC.addTarget(self, action: #selector(oldOn(_:)), for: .valueChanged) + } + + @objc func newOn(_ sender: UISwitch) { + isNewLRC = true + } + + @objc func oldOn(_ sender: UISwitch) { + isNewLRC = false + } + + @objc func onLog(_ sender: Any?) { + let vc = storyboard!.instantiateViewController(withIdentifier: "LogViewController") + navigationController?.pushViewController(vc, animated: true) + } + + @IBAction func onSend(_ sender: UIButton) { +// guard dataPacketView.text.count > 10 else { return } +// showAlert("将发送以下数据", msg: dataPacketView.text, confirmLabel: "发送") { [unowned self] in +// activityIndicatorView.startAnimating() +// let bytes = dataPacketView.text! +// .split(separator: " ") +// .map { String($0) } +// .map { +// var result: UInt64 = 0 +// Scanner(string: $0).scanHexInt64(&result) +// return UInt8(result) +// } +// IBManager.sendInfo(dataPacket.data) { [unowned self] isSuccess in +// activityIndicatorView.stopAnimating() +// guard isSuccess else { return } +// showAlert(msg: "数据已发送") +// } +// +// IBSender() +// .ability(.battery) +// .completion { response in +// +// } +// .send() + } + + /// 组装信息 + func makeData() { + + let data: [UInt8] = []//dataView.text!.data!.bytes + + /// 分包数 + let packageCount = 1 + /// 数据包总长度 + let dataTotalLength = data.count + /// 数据包长度 + let dataLength = data.count + /// 数据包索引 + let index = 1 + + var bytes = [0x73, 0x01, 0x07] + packageCount.byte4 + dataTotalLength.byte4 + index.byte4 + dataLength.byte2 + + dataPacket = bytes + data + [0x00] + +// if isNewLRC { +// dataPacket = bytes + [LRC.calculateLRC(data)] +// } else { +// dataPacket = bytes + [LRC.calculate(data)] +// } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + makeData() + } +} + +extension ConnectViewController: UITextViewDelegate { + + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + guard text == "\n" else { return true } + textView.resignFirstResponder() + + if textView === dataView { + makeData() + } + return false + } +} diff --git a/IOTBluetooth/IOTBluetooth/Example/Datas.swift b/IOTBluetooth/IOTBluetooth/Example/Datas.swift new file mode 100644 index 0000000..a72c68a --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/Example/Datas.swift @@ -0,0 +1,93 @@ +// +// 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(.english)]), + (.about,"关于本机内容",[]), + (.find, "开始查找手表", [.action(1)]), + (.find, "停止查找手表", [.action(0)]), + (.message, "开启消息推送功能", [.action(1)]), + (.message, "关闭消息推送功能", [.action(0)]), + + (.takePhoto, "开启手势拍照", [.action(1)]), + (.takePhoto, "关闭手势拍照", [.action(0)]), + (.restore, "恢复出厂设置", []), + (.reboot, "重新启动", []), + (.shutdown, "关机", []), + (.fetchScreen, "获取手表屏幕参数", []), + (.screen, "设置手表屏幕参数", [.screen(IBScreen())]), + (.fetchBattery, "获取电量", []), + (.fetchClock, "获取闹钟设置", []), + (.setClock, "发送闹钟设置", [.clock(IBClock())]), + + (.fetchCurrentDialId, "发送当前表盘ID", []), + (.switchDial, "切换表盘", [.dialID(12)]), + (.deleteDial, "删除表盘", [.dialID(12)]), + (.fetchDials, "获取所有表盘UID", []), + (.checkDialID, "获取某表盘ID是否已安装", [.dialID(12)]), + + (.fetchWatchState, "获取当前手表状态", []), + (.fetchVibrateSound, "获取手表声音与震动参数", []), + (.vibrateSound, "设置手表声音与震动参数", [.vibrateSound(IBVibrateSound())]), + (.fetchAuxiliary, "获取手表辅助功能参数", []), + (.auxiliary, "设置手表辅助功能参数", [.auxiliary(.bloodOxygen)]), + (.unit, "设置手表公英制单位", [.value(1)]), + + (.sendWeather, "发送天气数据", [.weather(IBWeather())]), + (.disk, "获取SD卡剩余空间", []), + (.unpair, "发送解除配对消息", []), + (.sendUnpair, "发送取消解除配对消息", []), + + + (.startSport, "开启指定运动模式", [.motionMode(.running)]), + (.trainPauseResume, "训练暂停", [.action(0)]), + (.trainPauseResume, "训练恢复", [.action(1)]), + (.endSport, "结束运动模式", []), + + (.heartRateMode, "开启自动获取实时心率", [.action(1)]), + (.heartRateMode, "关闭自动获取实时心率", [.action(0)]), + (.heartRateHigh, "设置最高心率提醒", [.value(240)]), + (.heartRateHigh, "关闭最高心率提醒", [.value(0)]), + (.fetchHeartRate, "获取手表心率参数", []), + (.heartRate, "设置手表心率参数", [.heartRate(IBHeartRate())]), + (.currentHeartRate, "获取实时心率", []), + + (.fetchBloodOxygen, "获取手表血氧设置", []), + (.bloodOxygen, "设置手表血氧参数", [.bloodOxygen(IBBloodOxygen())]), + (.trainFrequency, "设置训练实时数据采样频率", [.value(30)]), + + (.fetchTargetStep, "获取目标步数设置", []), + (.targetStep, "设置目标步数", [.targetStep(IBTargetStep())]), + (.fetchOtherTarget, "获取其他目标设置", []), + (.targetOther, "设置其他目标", [.otherTarget(IBOtherTarget())]), + (.trainTarget, "设置训练目标", [.trainTarget(IBTrainTarget())]), + (.fetchSleep, "获取睡眠设置", []), + (.sleep, "设置睡眠", [.sleep(IBSleep())]), + (.fetchWater, "获取手表喝水提醒设置", []), + (.water, "设置喝水提醒", [.water(IBWater())]), + + + (.syncHeartRate, "同步心率历史数据", [.value(30)]), + (.syncBloodOxygen, "同步血氧历史数据", [.value(30)]), + (.syncStepCount, "同步历史步数数据", [.value(30)]), + (.syncFatBurning, "同步燃脂时间历史数据", [.value(30)]), + (.syncStand, "同步站立历史数据", [.value(30)]), + (.syncCalorie, "同步卡路里历史数据", [.value(30)]), + (.syncDistance, "同步距离历史数据", [.value(30)]), + (.syncTrain, "同步基础训练历史数据", [.value(30)]), + (.syncGPS, "同步路径坐标集历史数据", [.value(1654012800)]), + (.sendSyncEnd, "发送同步数据结束消息", []), + (.addressBook, "发送通讯录", [.addressBook(IBAddressBook.test())]), + +// (.revokeFileSend, "撤销当前文件传输", [.file(.mp3)]), +// (.fetchFiles, "获取指定类型所有文件", [.file(.mp3)]), +// (.deleteFiles, "删除指定类型所有文件", [.file(.mp3)]), +] diff --git a/IOTBluetooth/IOTBluetooth/Example/Info.plist b/IOTBluetooth/IOTBluetooth/Example/Info.plist new file mode 100644 index 0000000..e999718 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/Example/Info.plist @@ -0,0 +1,10 @@ + + + + + UIBackgroundModes + + bluetooth-central + + + diff --git a/IOTBluetooth/IOTBluetooth/Example/LogViewController.swift b/IOTBluetooth/IOTBluetooth/Example/LogViewController.swift new file mode 100644 index 0000000..246784a --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/Example/LogViewController.swift @@ -0,0 +1,24 @@ +// +// LogViewController.swift +// Example +// +// Created by yaozuopan on 2024/3/14. +// + +import UIKit +import IOTBluetooth + +class LogViewController: BaseViewController { + + @IBOutlet weak var textView: UITextView! + + override func viewDidLoad() { + title = "蓝牙日志" + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + textView.text = IBManager.logs.joined(separator: "\n") + } +} + diff --git a/IOTBluetooth/IOTBluetooth/Example/ViewController.swift b/IOTBluetooth/IOTBluetooth/Example/ViewController.swift new file mode 100644 index 0000000..1d6af06 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/Example/ViewController.swift @@ -0,0 +1,98 @@ +// +// ViewController.swift +// Example +// +// Created by yaozuopan on 2024/3/11. +// + +import UIKit +import IOTBluetooth + +class ViewController: BaseViewController { + + @IBOutlet weak var listView: UITableView! + + private var datas: [IBPeripheral] = [] + override func viewDidLoad() { + super.viewDidLoad() + title = "IOTBluetooth" + + IBManager.subscribe(.connectStateDidChange, observer: self) { object, userInfo in + print("connect notice " + "\(object!)") + } + IBManager.subscribe(.takePhoto, observer: self) { object, userInfo in + print("takePhoto notice " + "\(object!)") + } + IBManager.subscribe(.didFindDevice, observer: self) { object, userInfo in + print("find notice " ) + } + IBManager.subscribe(.trainStateDidChanged, observer: self) { object, userInfo in + let model = object as! IBTarinState + print("trainState notice " + model.description) + } + IBManager.subscribe(.trainingData, observer: self) { object, userInfo in + let model = object as! IBTrainingData + print("trainingData notice " + model.description) + } +// IBManager.subscribe(.weather, observer: self) { object, userInfo in +// print("weather notice ") +// } + } + + deinit { + IBManager.remove(self) + } + + @IBAction func startScan(_ sender: UIButton) { + title = "正在扫描蓝牙设备..." + IBManager.scan { [unowned self] peripheral in + if let _ = datas.filter({ $0.uuid == peripheral.uuid }).last { + return + } + print(peripheral.name) + print(peripheral.macAddress) + print(peripheral.softwareVersion) + print(peripheral.rssi) + + datas.append(peripheral) + listView.reloadData() + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + IBManager.stopScan() + title = "IOTBluetooth" + } + +} + +extension ViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + datas.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 = datas[indexPath.row].name + "(点击连接)" + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + activityIndicatorView.startAnimating() + let device = datas[indexPath.row] + IBManager.connect(device) { [unowned self] isSuccess in + activityIndicatorView.stopAnimating() + if isSuccess{ + let vc = storyboard!.instantiateViewController(identifier: "AbilityViewController") + navigationController?.pushViewController(vc, animated: true) + } + else{ + print("连接失败") + } + } + print(indexPath) + } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5ffed27 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj @@ -0,0 +1,821 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 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 */; }; + 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 */; }; + 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 */; }; + 18EFF59B2C080F14001B1E0E /* IBUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */; }; + 8A0A39BF2B980BEF006A3B18 /* IOTBluetooth.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8A0A39C62B980C88006A3B18 /* IBluetooth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39C52B980C88006A3B18 /* IBluetooth.swift */; }; + 8A0A39C92B980CC2006A3B18 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */; }; + 8A0A39CB2B980CFC006A3B18 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39CA2B980CFC006A3B18 /* Logger.swift */; }; + 8A0A39CD2B980D53006A3B18 /* IBPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39CC2B980D53006A3B18 /* IBPeripheral.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 */; }; + 8A34B9272BA2AB66000F66BD /* ConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B9262BA2AB66000F66BD /* ConnectViewController.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 */; }; + 8A9F6AC12BA04BE100DA520F /* IBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9F6AC02BA04BE100DA520F /* IBManager.swift */; }; + 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 */ + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; + 18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBUserInfo.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 /* IBluetooth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBluetooth.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 = ""; }; + 8A0A39CC2B980D53006A3B18 /* IBPeripheral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBPeripheral.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 = ""; }; + 8A34B9262BA2AB66000F66BD /* ConnectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewController.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 = ( + 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 */ + 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 */, + 8A0A39C52B980C88006A3B18 /* IBluetooth.swift */, + 8A5822CE2BA1A67700590AB6 /* IBAbility.swift */, + 8A878FE32BA94D09008C7F26 /* IBChecker.swift */, + 8A76B60A2BA851730016F2AB /* IBParser.swift */, + 8A65F9602BAC0810005C1EA4 /* IBSimulateParser.swift */, + 8A878FE12BA9444C008C7F26 /* IBParameter.swift */, + 8A79D2242BA4278E0096A98C /* IBSender.swift */, + 8A79D2262BA433330096A98C /* IBNotify.swift */, + 8A9F6AC02BA04BE100DA520F /* IBManager.swift */, + 8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */, + ); + path = IOTBluetooth; + sourceTree = ""; + }; + 8A0A39C72B980CC2006A3B18 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 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 */, + ); + path = Tools; + sourceTree = ""; + }; + 8A1DFF502B9EB4F800B735FF /* Example */ = { + isa = PBXGroup; + children = ( + 8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */, + 8A1DFF552B9EB4F800B735FF /* ViewController.swift */, + 8A34B9262BA2AB66000F66BD /* ConnectViewController.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 = ( + 8A0A39CC2B980D53006A3B18 /* IBPeripheral.swift */, + 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 */, + 1849DC902BECD131002C2D2D /* IBCode.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 = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8A1DFF4D2B9EB4F800B735FF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8A1DFF5E2B9EB4F900B735FF /* LaunchScreen.storyboard in Resources */, + 8A1DFF5B2B9EB4F900B735FF /* Assets.xcassets in Resources */, + 8A1DFF592B9EB4F900B735FF /* Main.storyboard 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 */, + 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 */, + 189C15892C05E0CD00676A80 /* IBTarinState.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 */, + 8A34B92D2BA2FA02000F66BD /* IBManufacturer.swift in Sources */, + 184B94CB2BF606CC0042BEB3 /* IBTrainTarget.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 */, + 8A65F9632BAC1018005C1EA4 /* IBTargetStep.swift in Sources */, + 8A34B9252BA2A069000F66BD /* IBWatchInfo.swift in Sources */, + 8A0A39D62B980EC9006A3B18 /* IBPeripheralUUID.swift in Sources */, + 189C15952C05FFC700676A80 /* IBHistoryCalorie.swift in Sources */, + 8A9F6AC12BA04BE100DA520F /* IBManager.swift in Sources */, + 18EC6E282BFF4C6D00757CA8 /* IBPhoneInfo.swift in Sources */, + 18C9589F2C086A9D006A5DE8 /* IBHistorySleep.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 */, + 18E7AC922BF44C6800CB9393 /* IBSleep.swift in Sources */, + 8A0A39C62B980C88006A3B18 /* IBluetooth.swift in Sources */, + 8A0A39CB2B980CFC006A3B18 /* Logger.swift in Sources */, + 189C158F2C05F85200676A80 /* IBHistoryStep.swift in Sources */, + 8A5822D12BA1C80700590AB6 /* LRC.swift in Sources */, + 8A0A39DA2B981037006A3B18 /* Date.swift in Sources */, + 18993AA22C073FED006742D2 /* IBBloodOxygen.swift in Sources */, + 8A0A39CD2B980D53006A3B18 /* IBPeripheral.swift in Sources */, + 8A87FBB32BAA7F3900096761 /* IBLanguage.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 */, + 8A34B9272BA2AB66000F66BD /* ConnectViewController.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 = { + 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; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + 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 = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 8A0A39C42B980BEF006A3B18 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + 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; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + 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 = 5.0; + 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; + 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; + 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/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/IDEFindNavigatorScopes.plist b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..943a344 Binary files /dev/null and b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/IOTBluetooth.xcscheme b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/IOTBluetooth.xcscheme new file mode 100644 index 0000000..7630f1c --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/IOTBluetooth.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..c296514 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..d0682b2 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + Example.xcscheme_^#shared#^_ + + orderHint + 1 + + IOTBluetooth.xcscheme_^#shared#^_ + + orderHint + 0 + + + SuppressBuildableAutocreation + + 8A0A39BA2B980BEF006A3B18 + + primary + + + + + diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/williamtrillgates.xcuserdatad/xcschemes/xcschememanagement.plist b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/williamtrillgates.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..8d574d1 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/williamtrillgates.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + Example.xcscheme_^#shared#^_ + + orderHint + 1 + + IOTBluetooth.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift new file mode 100644 index 0000000..655ac55 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift @@ -0,0 +1,505 @@ +// +// IBAbility.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/13. +// + +import Foundation + +/// 功能指令 +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) } + + 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 + } +} + +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 + /// 0 = 未安装 \ + /// 1 = 已安装 \ + /// 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) + + /// APP发送通讯录 + /// - Parameter addressBook: see ``IBAddressBook`` + /// 0~20 + static let addressBook = IBAbility.long.id(0x0B).parser(IBGeneralParser.self).checker(IBAddressBookChecker.self) + +// /// 撤销当前文件传输 +// static let revokeFileSend = IBAbility.short.id(0x20).parser(IBGeneralParser.self).checker(IBFileChecker.self) +// /// 获取指定类型所有文件(当前只支持 MP3 和 表盘) +// static let fetchFiles = IBAbility.short.id(0x21).parser(IBGeneralParser.self).checker(IBFileChecker.self) +// /// 删除指定类型所有文件(当前只支持 MP3 和 表盘) +// static let deleteFiles = IBAbility.short.id(0x22).parser(IBGeneralParser.self).checker(IBFileChecker.self) +// +// +} + +extension IBAbility { + + +// +// // MARK: - Fetch 获取数据 + +// /// 撤销当前文件传输 +// static let revokeFileTransfer = IBAbility.short.id(0x20) + +// /// 删除指定文件(数据包为文件索引号) +// static let deleteFile = IBAbility.long.id(0x22) + + +// +// +// /// 发送OTA文件信息 +// static let ota = IBAbility.file.id(0x20) +// /// 发送MP3文件信息 +// static let mp3 = IBAbility.file.id(0x21) +// /// 发送表盘文件信息 +// static let dial = IBAbility.file.id(0x22) +// /// APP发送文件校验码 +// static let fileCheckCode = IBAbility.file.id(0x23) +// ///  发送单个表盘文件信息 +// static let dialInfo = IBAbility.file.id(0x24) +// /// 发送EPO文件信息(04,25) +// static let epo = IBAbility.file.id(0x28) +// +// +// // 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/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift new file mode 100644 index 0000000..43f0f55 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift @@ -0,0 +1,291 @@ +// +// 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 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 + } +} + +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 IBScreenChecker: IBChecker { + static func check(_ parameters: [IBParameter]) -> [UInt8] { + guard case let .screen(value) = parameters[.screen] else { fatalError("缺少参数: screen") } + Logger.log(value.description) + let data = [value.brightness.byte] + [value.duration.rawValue.byte] + [value.wrist.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("缺少参数: vibrateSound") } + Logger.log(value.description) + let data = [value.systemValue.byte] + [value.ringsValue.byte] + [value.mediaValue.byte] + [value.ringsState.byte] + [value.vibrateState.byte] + return data + } +} +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 + } +} + diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBKeeper.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBKeeper.swift new file mode 100644 index 0000000..53b8a37 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift new file mode 100644 index 0000000..765644a --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift @@ -0,0 +1,75 @@ +// +// 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() +// } + } + + public static var logs: [String] { Logger.logs } +} + +public extension IBManager { + /// SDK 版本号 + static let version: String = "1.0.7" + /// 默认为 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/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift new file mode 100644 index 0000000..a7c5a57 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift @@ -0,0 +1,268 @@ +// +// 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") +} + +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] == 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 data.bytes[1] == 0x05 && data.bytes[2] == 0x03 { + post(.weather) + return true + } + + //配对 + 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) + //发送时间 + IBNotify.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/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift new file mode 100644 index 0000000..23dfa2e --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift @@ -0,0 +1,180 @@ +// +// 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" +} + +/// 参数 +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(IBScreen) + /// 设置声音和震动 + case vibrateSound(IBVibrateSound) + /// 设置目标设置 + case otherTarget(IBOtherTarget) + /// 设置训练目标 + case trainTarget(IBTrainTarget) + /// 发送手机信息 + case sendPhone(IBPhoneInfo) + /// 发送当前时间 + case sendTime(IBSendTime) + /// 设置辅助功能 + case auxiliary(IBAuxiliary) + /// 设置用户数据 + case userInfo(IBUserInfo) + /// 发送天气数据 + case weather(IBWeather) + /// 发送通讯录 + case addressBook([IBAddressBook]) + + 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 + } + } +} + +public enum IBMotionMode: Int { + /// 健走 + case walking = 0 + /// 跑步 + case running + /// 骑行 + case ride +} + +public enum IBFile: Int { + /// OTA 文件 + case ota = 1 + /// MP3 文件 + case mp3 + /// 表盘信息 + case dial + /// 天气 + case weather + /// 闹钟 + case clock +} +//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/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift new file mode 100644 index 0000000..97e3ea5 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift @@ -0,0 +1,329 @@ +// +// 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 { + var bytes = data.bytes + bytes.removeLast() + let code = bytes.last!.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 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) + } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift new file mode 100644 index 0000000..d9527d8 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift @@ -0,0 +1,111 @@ +// +// IBSender.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/15. +// + +import Foundation + +public typealias IBCompletionHandler = (_ result: IBResponse) -> Void + +//public typealias IBCmdSuccess = (_ result: IBResponse) -> Void +//public typealias IBCmdError = (_ error: IBError) -> Void + +/// 蓝牙消息发送类 +public class IBSender { + public init() { + Self.all.append(self) + } + + var ability: IBAbility! + var parameters: [IBParameter] = [] + var completion: IBCompletionHandler! + +// var cmdSuccess: IBCmdSuccess? +// var cmdFailure: IBCmdError? + + var data: [UInt8] = [] + + var dataPacket: [UInt8] { + if isValue(ability.checker) { + data = ability.checker.check(parameters) + } + +// let data: [UInt8] = data?.bytes ?? [] + /// 分包数 + let packageCount = 1 + /// 数据包总长度 + let dataTotalLength = data.count + /// 数据包长度 + let dataLength = data.count + /// 数据包索引 + let index = 1 + + var bytes: [UInt8] = [0x73, ability.type.rawValue, ability.id] + bytes = bytes + packageCount.byte4 + dataTotalLength.byte4 + index.byte4 + dataLength.byte2 + + if data.count > 0 { + bytes = bytes + data + bytes = bytes + [LRC.calculateLRC(data)] + } else { + bytes = bytes + [0x00] + } + + return bytes + } + + 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 + } +// /// 执行与响应处理 +// @discardableResult +// func response(success: @escaping IBCmdSuccess,failure:@escaping IBCmdError) -> Self { +// cmdSuccess = success +// cmdFailure = failure +// IBBluetooth.shared.send(self) +// return self +// } +} + +extension IBSender { + static var all: [IBSender] = [] + + /// 从缓存实例集合中移除该实例 + func remove() { + Self.all.removeAll { $0 === self } + } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSimulateParser.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSimulateParser.swift new file mode 100644 index 0000000..4f97eb2 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift new file mode 100644 index 0000000..2ba56a0 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift @@ -0,0 +1,389 @@ +// +// IBluetooth.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/2/4. +// + +import Foundation +import CoreBluetooth + +/// Max is 482 +let dataPacketMaxBytes = 450 + +public class IBScanOption { + var values: [String : Any] = [:] + var serviceUUIDs: [String]? = nil + + /// 允许重复扫描 + @discardableResult + public func allowDuplicates(_ value: Bool) -> Self { + values[CBCentralManagerScanOptionAllowDuplicatesKey] = value + return self + } + + /// 扫描指定服务 + @discardableResult + public func solicited(_ serviceUUIDs: [String]) -> Self { + self.serviceUUIDs = serviceUUIDs + values[CBCentralManagerScanOptionSolicitedServiceUUIDsKey] = serviceUUIDs.map({ CBUUID(string: $0) }) + return self + } + + public static let `default` = IBScanOption() + public static var custom: IBScanOption { IBScanOption() } +} + +/// 主服务 +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" + +class IBBluetooth: NSObject { + static let shared = IBBluetooth() + + private override init() { + super.init() + centralManager = CBCentralManager(delegate: self, queue: nil) + } + + /// 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 devices: [IBPeripheral] = [] + + private var option: IBScanOption = .default + func scan(_ option: IBScanOption = .default) { + self.option = option + startScan() + } + + /// 重连接设备 + func reconnected() { + guard let connectedPeripheral = centralManager.retrieveConnectedPeripherals(withServices: [CBUUID(string: serviceUUID)]).first else { return } + currentConnected = IBPeripheral() + currentConnected.peripheral = connectedPeripheral + centralManager.connect(connectedPeripheral) + } + + /// 当前连接设备 + var currentConnected: IBPeripheral! + /// 最后消息发送实例 + private weak var lastSender: IBSender? + + /// 开始扫描 + private func startScan() { + if centralManager.state == .poweredOn { + Logger.log("开始扫描蓝牙设备...") + } + centralManager.scanForPeripherals(withServices: nil, options: option.values) + } + + /// 停止扫描 + func stopScan(){ + Logger.log("停止扫描蓝牙设备...") + centralManager.stopScan() + } + + /// 连接蓝牙设备 + /// + /// - Parameter peripheral: 蓝牙设备对像 + func connect(_ peripheral: IBPeripheral) { + currentConnected = peripheral + centralManager.connect(peripheral.peripheral) + } + + /// 断开与蓝牙设备的连接 + /// + /// - Parameter peripheral: 蓝牙设备对像 + func cancel(_ peripheral: IBPeripheral) { + centralManager.cancelPeripheralConnection(peripheral.peripheral) + } + + /// 发送数据 + /// - Parameters: + /// - sender: 消息类 + func send(_ sender: IBSender) { + if let _ = lastSender { + sender.remove() + Logger.log("有命令正在执行中...") + return + } + lastSender = sender + startTimer() + + let dataPacket = sender.dataPacket + 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 + } + + currentConnected.peripheral.writeValue(dataPacket.data, for: writeCharacteristic, type: .withResponse) + } + + func sendDeviceInfo(){ +// IBNotify.post(.authStateDidChange, userInfo: ["state": 0]) + + 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) + guard let value = result.value as? Int else {return} + if value == 2 { + //通知连接成功 + IBManager.completeHandler?(true) + IBManager.completeHandler = nil + } + else if value == 1{ + //等待手表同意连接 + Logger.log("等待手表同意连接") + } + else if value == 3{ + //手表已与其他手机绑定 + IBManager.completeHandler?(false) + IBManager.completeHandler = nil + } + } + } +} + +extension IBBluetooth: CBCentralManagerDelegate { + + /// 手机蓝牙状态更新 + /// - Parameter central: 中央管理类 + 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) { + + guard let manufacturerData = advertisementData["kCBAdvDataManufacturerData"] as? Data else { return } + guard let manufacturer = IBManufacturer(manufacturerData) else { return } + + let device = IBPeripheral() + device.code = manufacturer.code + device.isBind = manufacturer.isBind + device.rssi = RSSI.intValue + device.peripheral = peripheral + device.productID = manufacturer.productID + device.customerID = manufacturer.productID + device.advertisementData = advertisementData + device.macAddress = manufacturer.macAddress + device.softwareVersion = manufacturer.softwareVersion + IBManager.discoverHandler?(device) + Logger.log("发现设备", device.name, device.macAddress) + } + + /// 连接上设备 + func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { + peripheral.delegate = self + peripheral.discoverServices([CBUUID(string: serviceUUID)]) +// IBNotify.post(.connectedDeviceDidChange) + Logger.log("已连接", currentConnected.name) + IBNotify.post(.connectStateDidChange, object: 1) + } + + /// 连接失败 + func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { + Logger.log("连接失败",error?.localizedDescription ?? "" ) + currentConnected = nil + IBManager.completeHandler?(false) + IBManager.completeHandler = nil + } + + /// 断开连接 + func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { + Logger.log("断开连接") + + deinitTimer() + lastSender?.completion?(.response.code(.failure)) + lastSender?.remove() + + IBManager.completeHandler?(false) + IBManager.completeHandler = nil + IBNotify.post(.connectStateDidChange,object: 0) + } +} + +extension IBBluetooth: 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) + } + + guard let data = characteristic.value else { return } + if(data.count == 2 && data.int == 0){ + Logger.log("收到0000指令异常") + return + } + + let mark = characteristic.isNotifying ? "Notify Read" : "Read" + let uuid = characteristic.uuid.uuidString + Logger.log(mark, uuid, "->", data.joined(" "), "Bytes(\(data.count))") + + if lastSender?.isAnswer(data) == true { + deinitTimer() + + let result = lastSender!.parse(data) + lastSender?.completion?(result) + lastSender?.remove() + return + } + + if characteristic.uuid.uuidString == readCharacteristicUUID { + let info = IBWatchInfo(data) + currentConnected.battery = info.battery + currentConnected.macAddress = info.macAddress + currentConnected.productID = info.productID + currentConnected.customerID = info.customerID + sendDeviceInfo() + return + } + +// if characteristic.uuid.uuidString == notifyCharacteristicUUID { +// Logger.log("Notify == ") +// return +// } + if IBNotify.handle(data) { +// Logger.log("通知消息") + return + } + + Logger.log("Other handle") + } + + /// 写入完成(发送指令完成) + func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { + if let error = error { + Logger.log(error) + return + } + + Logger.log(characteristic.uuid.uuidString, "write success") + } +} + +extension IBBluetooth { + + static var timer: Timer? + func startTimer() { + deinitTimer() + + Self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false, block: { [unowned self] _ in + deinitTimer() + lastSender?.completion?(.response.code(.timeout)) + lastSender?.remove() + }) + Logger.log("创建定时器") + } + + func deinitTimer() { + guard let _ = Self.timer else { return } + Self.timer?.invalidate() + Self.timer = nil + + Logger.log("销毁定时器") + } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IOTBluetooth.h b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IOTBluetooth.h new file mode 100644 index 0000000..98258c9 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBAbout.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBAbout.swift new file mode 100644 index 0000000..2e70140 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBAddressBook.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBAddressBook.swift new file mode 100644 index 0000000..fef7b43 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBBloodOxygen.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBBloodOxygen.swift new file mode 100644 index 0000000..99f6639 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBClock.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBClock.swift new file mode 100644 index 0000000..7b12f33 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBClock.swift @@ -0,0 +1,46 @@ +// +// IBClock.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/21. +// + +import Foundation + +/// 闹钟模型 +public class IBClock: Codable { + /// 闹钟列表 + public var alarms: [IBClockInfo] = [] + + public init() { } +} + +/// 闹钟信息模型 +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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBCode.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBCode.swift new file mode 100644 index 0000000..3711401 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHeartRate.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHeartRate.swift new file mode 100644 index 0000000..554758d --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHeartRate.swift @@ -0,0 +1,45 @@ +// +// IBHeartRate.swift +// IOTBluetooth +// +// Created by incar on 2024/5/16. +// + +import Foundation +/// 心率设置模型 +public class IBHeartRate { + /// 监测频率 + public var rate: IBHeartRateFrequency = .none + /// 心率提醒 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 + if state == 1 { + 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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBaseTraining.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBaseTraining.swift new file mode 100644 index 0000000..b93cb22 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBloodOxygen.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBloodOxygen.swift new file mode 100644 index 0000000..e922be8 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBurning.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBurning.swift new file mode 100644 index 0000000..33f1466 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift new file mode 100644 index 0000000..b6e2e87 --- /dev/null +++ b/IOTBluetooth/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[0...3].int + model.value = data[i+4...i+7].int + historyList.append(model) + } + } + list = historyList + } +} + +/// 卡路里历史记录 +public class IBHistoryCalorieItem { + /// 时间戳 + public var time: Int = 0 + /// 单位kcal + public var value: Int = 0 + + public init() { } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift new file mode 100644 index 0000000..0c876bd --- /dev/null +++ b/IOTBluetooth/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].int + historyList.append(model) + } + } + list = historyList + } +} + +/// 距离历史记录 +public class IBHistoryDistanceItem { + /// 时间戳 + public var time: Int = 0 + /// 单位km + public var value: Int = 0 + + public init() { } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryGps.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryGps.swift new file mode 100644 index 0000000..78eb6fc --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryHeartRate.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryHeartRate.swift new file mode 100644 index 0000000..ad3248f --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift new file mode 100644 index 0000000..ebf1904 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift @@ -0,0 +1,86 @@ +// +// 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 number = data[0].int + + + for i in stride(from: 0, to: data.count, by: 8){ + if data.count > i + 7 { + let model = IBHistorySleepItem() +// model.time = data[i...i+3].int +// model.value = data[i+4...i+7].int +// historyList.append(model) + } + } + list = historyList + } +} + +/// 睡眠历史记录 +public class IBHistorySleepItem { + /// 睡眠ID,0~255,表示第几次睡眠 + public var sleepId: UInt8 = 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 = IBSleepTime(hour: 0, minute: 0) + /// 浅睡时长 + public var lightSleep: IBSleepTime = IBSleepTime(hour: 0, minute: 0) + /// 快速动眼时长 + public var remSleep: IBSleepTime = IBSleepTime(hour: 0, minute: 0) + /// 清醒时长 + public var awake: IBSleepTime = IBSleepTime(hour: 0, minute: 0) + /// 睡眠状态 + public var states:[IBSleepState] = [] + + + public init() { } +} + +/// 时间 +public struct IBSleepTime { + /// 时 + public var hour: UInt8 + /// 分 0~59 + public var minute: UInt8 + + public init(hour: UInt8, minute: UInt8) { + self.hour = hour + self.minute = minute + } + + var description: String { + return "IBSleepTime(hour: \(hour), minute:\(minute)" + } +} + + +/// 睡眠状态 +public class IBSleepState { + /// Unix时间戳 + public var time: Int32 = 0 + /// 状态,0:进入睡眠 1:浅层睡眠 2:深层睡眠 3:清醒 4:结束睡眠 7:未出睡最后一笔睡眠数据(资料上报结束标志) 9:出睡最后一笔睡眠数据(资料上报结束标志) 12:快速眼动 13:取消入睡通知(睡眠不足 60 分钟出睡上报状态) 14:结束小睡 + public var state: Int8 = 0 +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryStand.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryStand.swift new file mode 100644 index 0000000..918a8f8 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryStep.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryStep.swift new file mode 100644 index 0000000..538bc4b --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBLanguage.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBLanguage.swift new file mode 100644 index 0000000..722f730 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBManufacturer.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBManufacturer.swift new file mode 100644 index 0000000..6300d84 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBOtherTarget.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBOtherTarget.swift new file mode 100644 index 0000000..fd0aaf7 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPeripheral.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPeripheral.swift new file mode 100644 index 0000000..02fd72c --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPeripheral.swift @@ -0,0 +1,55 @@ +// +// IBPeripheral.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/6. +// + +import Foundation +import CoreBluetooth + +public class IBPeripheral { + + /// 信号强度(分贝) + public internal(set) var rssi: Int = 0 + + /// 外围设备实例 + public internal(set) var peripheral: CBPeripheral! { + didSet { + if let name = peripheral.name { + self.name = name + } + uuid = peripheral.identifier + } + } + + /// mac 地址 + public internal(set) var macAddress: String = "" + + /// 广播数据 + public internal(set) var advertisementData: [String: Any] = [:] + + /// CBPeripheral.identifier + public private(set) var uuid: UUID! + + /// 名称 + public private(set) var name: String = "Unknown" + + /// 软件版本 + public internal(set) var softwareVersion: String = "" + + /// 电量,连接设备后可用,默认为 0 + public internal(set) var battery: Int = 0 + + /// 识别符 + var code: String = "" + + /// 是否绑定(1=绑定) + var isBind: Int = 0 + + /// 客户 ID + var customerID: Int = 0 + + /// 产品ID + var productID: Int = 0 +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPhoneInfo.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPhoneInfo.swift new file mode 100644 index 0000000..0387b03 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBResponse.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBResponse.swift new file mode 100644 index 0000000..27891f4 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBScreen.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBScreen.swift new file mode 100644 index 0000000..26d3cbd --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBScreen.swift @@ -0,0 +1,46 @@ +// +// IBScreen.swift +// IOTBluetooth +// +// Created by yaozuopan on 2024/3/21. +// + +import Foundation + +/// 屏幕参数 +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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBSendTime.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBSendTime.swift new file mode 100644 index 0000000..5cb39eb --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBSleep.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBSleep.swift new file mode 100644 index 0000000..04e703a --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTargetStep.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTargetStep.swift new file mode 100644 index 0000000..5666ca8 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTarinState.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTarinState.swift new file mode 100644 index 0000000..9e048fe --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTrainTarget.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTrainTarget.swift new file mode 100644 index 0000000..bbc1946 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTrainingData.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTrainingData.swift new file mode 100644 index 0000000..270c59e --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBUserInfo.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBUserInfo.swift new file mode 100644 index 0000000..34e8192 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBVibrateSound.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBVibrateSound.swift new file mode 100644 index 0000000..802fdc8 --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBVibrateSound.swift @@ -0,0 +1,39 @@ +//// +//// IBVibrateSound.swift +//// IOTBluetooth +//// +//// Created by incar on 2024/5/9. +//// +// +/// 声音震动模型 +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:开启 + 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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWatchInfo.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWatchInfo.swift new file mode 100644 index 0000000..2a7c0c1 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWater.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWater.swift new file mode 100644 index 0000000..1da5ebd --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWeather.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWeather.swift new file mode 100644 index 0000000..5409798 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift new file mode 100644 index 0000000..64e712f --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift @@ -0,0 +1,181 @@ +// +// 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) } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/CRC16.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/CRC16.swift new file mode 100644 index 0000000..0025438 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Date.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Date.swift new file mode 100644 index 0000000..9fc933d --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Date.swift @@ -0,0 +1,145 @@ +// +// 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 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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/IBPeripheralUUID.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/IBPeripheralUUID.swift new file mode 100644 index 0000000..285800b --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/JSON.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/JSON.swift new file mode 100644 index 0000000..9c94ed9 --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/LRC.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/LRC.swift new file mode 100644 index 0000000..8270a6a --- /dev/null +++ b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Logger.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Logger.swift new file mode 100644 index 0000000..1236c7f --- /dev/null +++ b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Logger.swift @@ -0,0 +1,33 @@ +// +// Logger.swift +// +// +// Created by Harvey on 2021/4/27. +// + +import Foundation + +/// 日记管理 +class Logger { + static var isDebug: Bool = false + + fileprivate static let shared = Logger() + private init() { } + + fileprivate var dateString: String { Date().format(.full) } + + static var logs: [String] = [] +} + +extension Logger { + static func log(_ logs: Any..., line: Int = #line, separator: String = " ", terminator: String = "\n") { + objc_sync_enter(Logger.shared) + if Logger.isDebug { + print(Logger.shared.dateString, terminator: " [IOTBluetooth-\(String(format: "%03d", line))]: ") + print(logs, separator: separator, terminator: terminator) + let logString = Logger.shared.dateString + " [IOTBluetooth-\(String(format: "%03d", line))]: " + logs.map({ "\($0)" }).joined(separator: separator) + terminator + Self.logs.append(logString) + } + objc_sync_exit(Logger.shared) + } +} diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/UTF8.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/UTF8.swift new file mode 100644 index 0000000..a88e787 --- /dev/null +++ b/IOTBluetooth/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) } +}