重构文件传输
authorliangzhenying <867174124@qq.com>
Tue, 5 Nov 2024 03:37:15 +0000 (11:37 +0800)
committerliangzhenying <867174124@qq.com>
Tue, 5 Nov 2024 03:37:15 +0000 (11:37 +0800)
94 files changed:
code/IOTBluetooth/Example/AbilityViewController.swift [new file with mode: 0644]
code/IOTBluetooth/Example/AppDelegate.swift [new file with mode: 0644]
code/IOTBluetooth/Example/Assets.xcassets/AccentColor.colorset/Contents.json [new file with mode: 0644]
code/IOTBluetooth/Example/Assets.xcassets/AppIcon.appiconset/Contents.json [new file with mode: 0644]
code/IOTBluetooth/Example/Assets.xcassets/Contents.json [new file with mode: 0644]
code/IOTBluetooth/Example/Base.lproj/LaunchScreen.storyboard [new file with mode: 0644]
code/IOTBluetooth/Example/Base.lproj/Main.storyboard [new file with mode: 0644]
code/IOTBluetooth/Example/BaseViewController.swift [new file with mode: 0644]
code/IOTBluetooth/Example/Datas.swift [new file with mode: 0644]
code/IOTBluetooth/Example/Info.plist [new file with mode: 0644]
code/IOTBluetooth/Example/LogViewController.swift [new file with mode: 0644]
code/IOTBluetooth/Example/Resource/20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg [new file with mode: 0644]
code/IOTBluetooth/Example/Resource/AIAudio.sbc [new file with mode: 0644]
code/IOTBluetooth/Example/Resource/galaxy.lzma [new file with mode: 0644]
code/IOTBluetooth/Example/Resource/大约在冬季.mp3 [new file with mode: 0644]
code/IOTBluetooth/Example/Resource/黄沙-名决.mp3 [new file with mode: 0644]
code/IOTBluetooth/Example/ViewController.swift [new file with mode: 0644]
code/IOTBluetooth/Example/ble/IBBluetoothManager.swift [new file with mode: 0644]
code/IOTBluetooth/Example/ble/IBManager.swift [new file with mode: 0644]
code/IOTBluetooth/Example/ble/Logger.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/Example.xcscheme [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/IOTBluetooth.xcscheme [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/HsFileAgentSdk.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBAbility.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBAiManager.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBChecker.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBFileSender+Dial.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBFileSender+Mp3.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBFileSender+Ota.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBFileSender.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBKeeper.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBNotify.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBPairling.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBParameter.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBParser.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBReceiveManager.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBSender.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IBSimulateParser.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/IOTBluetooth.h [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBAbout.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBAddressBook.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBAiResponse.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBAiValid.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBBloodOxygen.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBClock.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBCode.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBDeleteFile.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBDial.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBEpo.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBFetchFile.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHeartRate.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBaseTraining.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBloodOxygen.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHistoryBurning.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHistoryGps.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHistoryHeartRate.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHistoryStand.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBHistoryStep.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBLanguage.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBManufacturer.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBMp3.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBOta.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBOtherTarget.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBPhoneInfo.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBResponse.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBScreen.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBSendTime.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBSleep.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBTargetStep.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBTarinState.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBTrainTarget.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBTrainingData.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBUserInfo.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBVibrateSound.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBWatchInfo.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBWater.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Models/IBWeather.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Tools/Any.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Tools/CRC16.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Tools/Date.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Tools/IBPeripheralUUID.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Tools/IBTools.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Tools/JSON.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Tools/LRC.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Tools/Logger.swift [new file with mode: 0644]
code/IOTBluetooth/IOTBluetooth/Tools/UTF8.swift [new file with mode: 0644]
document/2024.10.17 2ae6.docx [new file with mode: 0644]
document/日志/crashlog.crash [new file with mode: 0644]

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