init master
authorliangzhenying <867174124@qq.com>
Mon, 3 Jun 2024 08:59:24 +0000 (16:59 +0800)
committerliangzhenying <867174124@qq.com>
Mon, 3 Jun 2024 08:59:24 +0000 (16:59 +0800)
77 files changed:
Doc/230蓝牙手表通讯协议.docx [new file with mode: 0644]
Doc/海思蓝牙SDK开发计划.xlsx [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/AppDelegate.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AccentColor.colorset/Contents.json [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AppIcon.appiconset/Contents.json [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/Contents.json [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/Base.lproj/LaunchScreen.storyboard [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/Base.lproj/Main.storyboard [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/BaseViewController.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/Datas.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/Info.plist [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/LogViewController.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/Example/ViewController.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/IDEFindNavigatorScopes.plist [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/IOTBluetooth.xcscheme [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/williamtrillgates.xcuserdatad/xcschemes/xcschememanagement.plist [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBKeeper.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSimulateParser.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/IOTBluetooth.h [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBAbout.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBAddressBook.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBBloodOxygen.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBClock.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBCode.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHeartRate.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBaseTraining.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBloodOxygen.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBurning.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryGps.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryHeartRate.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryStand.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryStep.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBLanguage.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBManufacturer.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBOtherTarget.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPeripheral.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPhoneInfo.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBResponse.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBScreen.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBSendTime.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBSleep.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTargetStep.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTarinState.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTrainTarget.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTrainingData.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBUserInfo.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBVibrateSound.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWatchInfo.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWater.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWeather.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/CRC16.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Date.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/IBPeripheralUUID.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/JSON.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/LRC.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Logger.swift [new file with mode: 0644]
IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/UTF8.swift [new file with mode: 0644]

diff --git a/Doc/230蓝牙手表通讯协议.docx b/Doc/230蓝牙手表通讯协议.docx
new file mode 100644 (file)
index 0000000..0ab76ca
Binary files /dev/null and "b/Doc/230\350\223\235\347\211\231\346\211\213\350\241\250\351\200\232\350\256\257\345\215\217\350\256\256.docx" differ
diff --git a/Doc/海思蓝牙SDK开发计划.xlsx b/Doc/海思蓝牙SDK开发计划.xlsx
new file mode 100644 (file)
index 0000000..517995f
Binary files /dev/null and "b/Doc/\346\265\267\346\200\235\350\223\235\347\211\231SDK\345\274\200\345\217\221\350\256\241\345\210\222.xlsx" differ
diff --git a/IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift b/IOTBluetooth/IOTBluetooth/Example/AbilityViewController.swift
new file mode 100644 (file)
index 0000000..f390f9d
--- /dev/null
@@ -0,0 +1,57 @@
+//
+//  AbilityViewController.swift
+//  Example
+//
+//  Created by yaozuopan on 2024/3/11.
+//
+
+import UIKit
+import IOTBluetooth
+
+class AbilityViewController: BaseViewController {
+
+    @IBOutlet weak var listView: UITableView!
+       
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "已连接" + IBManager.currentConnected!.name
+    }
+    
+    deinit {
+        IBManager.remove(self)
+    }
+}
+
+extension AbilityViewController: UITableViewDelegate, UITableViewDataSource {
+    
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        datas.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let data = datas[indexPath.row]
+        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+        let label = cell.viewWithTag(10) as! UILabel
+        label.text = "\(indexPath.row) " + data.ability.description + " \(data.label)"
+        return cell
+    }
+    
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        activityIndicatorView.startAnimating()
+        let data = datas[indexPath.row]
+        IBSender()
+            .ability(data.ability)
+            .parameters(data.parameters)
+            .response {[weak self]  result in
+                guard let self = self else {return}
+                if result.isSuccess{
+                    //请求成功
+                }
+                else{
+                    //请求失败
+                }
+                print(result)
+                activityIndicatorView.stopAnimating()
+            }
+    }
+}
diff --git a/IOTBluetooth/IOTBluetooth/Example/AppDelegate.swift b/IOTBluetooth/IOTBluetooth/Example/AppDelegate.swift
new file mode 100644 (file)
index 0000000..983bdde
--- /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.
+        IBManager.isDebug = true
+        IBManager.initializationSDK()
+        
+        return true
+    }
+}
+
diff --git a/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AccentColor.colorset/Contents.json b/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644 (file)
index 0000000..eb87897
--- /dev/null
@@ -0,0 +1,11 @@
+{
+  "colors" : [
+    {
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/Example/Assets.xcassets/Contents.json b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/Example/Base.lproj/LaunchScreen.storyboard b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/Example/Base.lproj/Main.storyboard b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/Example/BaseViewController.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift b/IOTBluetooth/IOTBluetooth/Example/ConnectViewController.swift
new file mode 100644 (file)
index 0000000..53ec0bb
--- /dev/null
@@ -0,0 +1,127 @@
+//
+//  ConnectViewController.swift
+//  Example
+//
+//  Created by yaozuopan on 2024/3/14.
+//
+
+import UIKit
+import IOTBluetooth
+
+class ConnectViewController: BaseViewController {
+    
+    @IBOutlet weak var dataView: UITextView!
+    @IBOutlet weak var dataPacketView: UITextView!
+    @IBOutlet weak var newLRC: UISwitch!
+    @IBOutlet weak var oldLRC: UISwitch!
+    
+    var peripheral: IBPeripheral!
+    
+    var dataPacket: [UInt8] = [] {
+        didSet {
+            dataPacketView.text = dataPacket.data.joined(" ")
+        }
+    }
+    
+    var isNewLRC: Bool = true {
+        didSet {
+            if isNewLRC { oldLRC.isOn = false }
+            if isNewLRC == false { newLRC.isOn = false }
+            
+            if isNewLRC == oldValue { return }
+            makeData()
+        }
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "已连接 \(peripheral.name)"
+        
+        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "查看日记", style: .done, target: self, action: #selector(onLog(_:)))
+        
+        newLRC.addTarget(self, action: #selector(newOn(_:)), for: .valueChanged)
+        oldLRC.addTarget(self, action: #selector(oldOn(_:)), for: .valueChanged)
+    }
+    
+    @objc func newOn(_ sender: UISwitch) {
+        isNewLRC = true
+    }
+    
+    @objc func oldOn(_ sender: UISwitch) {
+        isNewLRC = false
+    }
+    
+    @objc func onLog(_ sender: Any?) {
+        let vc = storyboard!.instantiateViewController(withIdentifier: "LogViewController")
+        navigationController?.pushViewController(vc, animated: true)
+    }
+    
+    @IBAction func onSend(_ sender: UIButton) {
+//        guard dataPacketView.text.count > 10 else { return }
+//        showAlert("将发送以下数据", msg: dataPacketView.text, confirmLabel: "发送") { [unowned self] in
+//            activityIndicatorView.startAnimating()
+//            let bytes = dataPacketView.text!
+//                .split(separator: " ")
+//                .map { String($0) }
+//                .map {
+//                    var result: UInt64 = 0
+//                    Scanner(string: $0).scanHexInt64(&result)
+//                    return UInt8(result)
+//                }
+//        IBManager.sendInfo(dataPacket.data) { [unowned self] isSuccess in
+//                activityIndicatorView.stopAnimating()
+//                guard isSuccess else { return }
+//                showAlert(msg: "数据已发送")
+//            }
+//        
+//        IBSender()
+//            .ability(.battery)
+//            .completion { response in
+//                
+//            }
+//            .send()
+    }
+    
+    /// 组装信息
+    func makeData() {
+        
+        let data: [UInt8] = []//dataView.text!.data!.bytes
+       
+        /// 分包数
+        let packageCount = 1
+        /// 数据包总长度
+        let dataTotalLength = data.count
+        /// 数据包长度
+        let dataLength = data.count
+        /// 数据包索引
+        let index = 1
+        
+        var bytes = [0x73, 0x01, 0x07] + packageCount.byte4 + dataTotalLength.byte4 + index.byte4 + dataLength.byte2
+        
+        dataPacket = bytes + data + [0x00]
+        
+//        if isNewLRC {
+//            dataPacket = bytes + [LRC.calculateLRC(data)]
+//        } else {
+//            dataPacket = bytes + [LRC.calculate(data)]
+//        }
+    }
+    
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        makeData()
+    }
+}
+
+extension ConnectViewController: UITextViewDelegate {
+    
+    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
+        guard text == "\n" else { return true }
+        textView.resignFirstResponder()
+        
+        if textView === dataView {
+            makeData()
+        }
+        return false
+    }
+}
diff --git a/IOTBluetooth/IOTBluetooth/Example/Datas.swift b/IOTBluetooth/IOTBluetooth/Example/Datas.swift
new file mode 100644 (file)
index 0000000..a72c68a
--- /dev/null
@@ -0,0 +1,93 @@
+//
+//  Datas.swift
+//  Example
+//
+//  Created by yaozuopan on 2024/3/19.
+//
+
+import Foundation
+import IOTBluetooth
+
+let datas: [(ability: IBAbility, label: String, parameters: [IBParameter])] = [
+    (.userData,"设置用户数据",[.userInfo(IBUserInfo())]),
+    (.language, "设置语言", [.language(.english)]),
+    (.about,"关于本机内容",[]),
+    (.find, "开始查找手表", [.action(1)]),
+    (.find, "停止查找手表", [.action(0)]),
+    (.message, "开启消息推送功能", [.action(1)]),
+    (.message, "关闭消息推送功能", [.action(0)]),
+    
+    (.takePhoto, "开启手势拍照", [.action(1)]),
+    (.takePhoto, "关闭手势拍照", [.action(0)]),
+    (.restore, "恢复出厂设置", []),
+    (.reboot, "重新启动", []),
+    (.shutdown, "关机", []),
+    (.fetchScreen, "获取手表屏幕参数", []),
+    (.screen, "设置手表屏幕参数", [.screen(IBScreen())]),
+    (.fetchBattery, "获取电量", []),
+    (.fetchClock, "获取闹钟设置", []),
+    (.setClock, "发送闹钟设置", [.clock(IBClock())]),
+    
+    (.fetchCurrentDialId, "发送当前表盘ID", []),
+    (.switchDial, "切换表盘", [.dialID(12)]),
+    (.deleteDial, "删除表盘", [.dialID(12)]),
+    (.fetchDials, "获取所有表盘UID", []),
+    (.checkDialID, "获取某表盘ID是否已安装", [.dialID(12)]),
+    
+    (.fetchWatchState, "获取当前手表状态", []),
+    (.fetchVibrateSound, "获取手表声音与震动参数", []),
+    (.vibrateSound, "设置手表声音与震动参数", [.vibrateSound(IBVibrateSound())]),
+    (.fetchAuxiliary, "获取手表辅助功能参数", []),
+    (.auxiliary, "设置手表辅助功能参数", [.auxiliary(.bloodOxygen)]),
+    (.unit, "设置手表公英制单位", [.value(1)]),
+    
+    (.sendWeather, "发送天气数据", [.weather(IBWeather())]),
+    (.disk, "获取SD卡剩余空间", []),
+    (.unpair, "发送解除配对消息", []),
+    (.sendUnpair, "发送取消解除配对消息", []),
+    
+    
+    (.startSport, "开启指定运动模式", [.motionMode(.running)]),
+    (.trainPauseResume, "训练暂停", [.action(0)]),
+    (.trainPauseResume, "训练恢复", [.action(1)]),
+    (.endSport, "结束运动模式", []),
+    
+    (.heartRateMode, "开启自动获取实时心率", [.action(1)]),
+    (.heartRateMode, "关闭自动获取实时心率", [.action(0)]),
+    (.heartRateHigh, "设置最高心率提醒", [.value(240)]),
+    (.heartRateHigh, "关闭最高心率提醒", [.value(0)]),
+    (.fetchHeartRate, "获取手表心率参数", []),
+    (.heartRate, "设置手表心率参数", [.heartRate(IBHeartRate())]),
+    (.currentHeartRate, "获取实时心率", []),
+    
+    (.fetchBloodOxygen, "获取手表血氧设置", []),
+    (.bloodOxygen, "设置手表血氧参数", [.bloodOxygen(IBBloodOxygen())]),
+    (.trainFrequency, "设置训练实时数据采样频率", [.value(30)]),
+    
+    (.fetchTargetStep, "获取目标步数设置", []),
+    (.targetStep, "设置目标步数", [.targetStep(IBTargetStep())]),
+    (.fetchOtherTarget, "获取其他目标设置", []),
+    (.targetOther, "设置其他目标", [.otherTarget(IBOtherTarget())]),
+    (.trainTarget, "设置训练目标", [.trainTarget(IBTrainTarget())]),
+    (.fetchSleep, "获取睡眠设置", []),
+    (.sleep, "设置睡眠", [.sleep(IBSleep())]),
+    (.fetchWater, "获取手表喝水提醒设置", []),
+    (.water, "设置喝水提醒", [.water(IBWater())]),
+    
+    
+    (.syncHeartRate, "同步心率历史数据", [.value(30)]),
+    (.syncBloodOxygen, "同步血氧历史数据", [.value(30)]),
+    (.syncStepCount, "同步历史步数数据", [.value(30)]),
+    (.syncFatBurning, "同步燃脂时间历史数据", [.value(30)]),
+    (.syncStand, "同步站立历史数据", [.value(30)]),
+    (.syncCalorie, "同步卡路里历史数据", [.value(30)]),
+    (.syncDistance, "同步距离历史数据", [.value(30)]),
+    (.syncTrain, "同步基础训练历史数据", [.value(30)]),
+    (.syncGPS, "同步路径坐标集历史数据", [.value(1654012800)]),
+    (.sendSyncEnd, "发送同步数据结束消息", []),
+    (.addressBook, "发送通讯录", [.addressBook(IBAddressBook.test())]),
+
+//        (.revokeFileSend, "撤销当前文件传输", [.file(.mp3)]),
+//        (.fetchFiles, "获取指定类型所有文件", [.file(.mp3)]),
+//        (.deleteFiles, "删除指定类型所有文件", [.file(.mp3)]),
+]
diff --git a/IOTBluetooth/IOTBluetooth/Example/Info.plist b/IOTBluetooth/IOTBluetooth/Example/Info.plist
new file mode 100644 (file)
index 0000000..e999718
--- /dev/null
@@ -0,0 +1,10 @@
+<?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>
+</dict>
+</plist>
diff --git a/IOTBluetooth/IOTBluetooth/Example/LogViewController.swift b/IOTBluetooth/IOTBluetooth/Example/LogViewController.swift
new file mode 100644 (file)
index 0000000..246784a
--- /dev/null
@@ -0,0 +1,24 @@
+//
+//  LogViewController.swift
+//  Example
+//
+//  Created by yaozuopan on 2024/3/14.
+//
+
+import UIKit
+import IOTBluetooth
+
+class LogViewController: BaseViewController {
+    
+    @IBOutlet weak var textView: UITextView!
+
+    override func viewDidLoad() {
+        title = "蓝牙日志"
+    }
+    
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        textView.text = IBManager.logs.joined(separator: "\n")
+    }
+}
+
diff --git a/IOTBluetooth/IOTBluetooth/Example/ViewController.swift b/IOTBluetooth/IOTBluetooth/Example/ViewController.swift
new file mode 100644 (file)
index 0000000..1d6af06
--- /dev/null
@@ -0,0 +1,98 @@
+//
+//  ViewController.swift
+//  Example
+//
+//  Created by yaozuopan on 2024/3/11.
+//
+
+import UIKit
+import IOTBluetooth
+
+class ViewController: BaseViewController {
+
+    @IBOutlet weak var listView: UITableView!
+    
+    private var datas: [IBPeripheral] = []
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "IOTBluetooth"
+        
+        IBManager.subscribe(.connectStateDidChange, observer: self) {  object, userInfo in
+            print("connect notice " + "\(object!)")
+        }
+        IBManager.subscribe(.takePhoto, observer: self) {  object, userInfo in
+            print("takePhoto notice " + "\(object!)")
+        }
+        IBManager.subscribe(.didFindDevice, observer: self) {  object, userInfo in
+            print("find notice " )
+        }
+        IBManager.subscribe(.trainStateDidChanged, observer: self) {  object, userInfo in
+            let model = object as! IBTarinState
+            print("trainState notice " +  model.description)
+        }
+        IBManager.subscribe(.trainingData, observer: self) {  object, userInfo in
+            let model = object as! IBTrainingData
+            print("trainingData notice " +  model.description)
+        }
+//        IBManager.subscribe(.weather, observer: self) {  object, userInfo in
+//            print("weather notice ")
+//        }
+    }
+    
+    deinit {
+        IBManager.remove(self)
+    }
+    
+    @IBAction func startScan(_ sender:  UIButton) {
+        title = "正在扫描蓝牙设备..."
+        IBManager.scan { [unowned self] peripheral in
+            if let _ = datas.filter({ $0.uuid == peripheral.uuid }).last {
+                return
+            }
+            print(peripheral.name)
+            print(peripheral.macAddress)
+            print(peripheral.softwareVersion)
+            print(peripheral.rssi)
+            
+            datas.append(peripheral)
+            listView.reloadData()
+        }
+    }
+    
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        IBManager.stopScan()
+        title = "IOTBluetooth"
+    }
+    
+}
+
+extension ViewController: UITableViewDelegate, UITableViewDataSource {
+    
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        datas.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+        let label = cell.viewWithTag(10) as! UILabel
+        label.text = datas[indexPath.row].name + "(点击连接)"
+        return cell
+    }
+    
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        activityIndicatorView.startAnimating()
+        let device = datas[indexPath.row]
+        IBManager.connect(device) { [unowned self] isSuccess in
+            activityIndicatorView.stopAnimating()
+            if isSuccess{
+                let vc = storyboard!.instantiateViewController(identifier: "AbilityViewController")
+                navigationController?.pushViewController(vc, animated: true)
+            }
+            else{
+                print("连接失败")
+            }
+        }
+        print(indexPath)
+    }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.pbxproj
new file mode 100644 (file)
index 0000000..5ffed27
--- /dev/null
@@ -0,0 +1,821 @@
+// !$*UTF8*$!
+{
+       archiveVersion = 1;
+       classes = {
+       };
+       objectVersion = 56;
+       objects = {
+
+/* Begin PBXBuildFile section */
+               180B01982BFC831300524729 /* IBHistoryHeartRate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180B01972BFC831300524729 /* IBHistoryHeartRate.swift */; };
+               180B019E2BFC9CF400524729 /* IBHistoryBaseTraining.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180B019D2BFC9CF400524729 /* IBHistoryBaseTraining.swift */; };
+               180CE2652C0D728F00A7006E /* IBAddressBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180CE2642C0D728F00A7006E /* IBAddressBook.swift */; };
+               184431322BF3720E00546E4E /* IBOtherTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184431312BF3720E00546E4E /* IBOtherTarget.swift */; };
+               1849DC8F2BEC8007002C2D2D /* IBVibrateSound.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1849DC8E2BEC8007002C2D2D /* IBVibrateSound.swift */; };
+               1849DC912BECD131002C2D2D /* IBCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1849DC902BECD131002C2D2D /* IBCode.swift */; };
+               184B94CB2BF606CC0042BEB3 /* IBTrainTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184B94CA2BF606CC0042BEB3 /* IBTrainTarget.swift */; };
+               1859DFA02BFF5A2700112E8C /* IBSendTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1859DF9F2BFF5A2700112E8C /* IBSendTime.swift */; };
+               18993AA22C073FED006742D2 /* IBBloodOxygen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18993AA12C073FED006742D2 /* IBBloodOxygen.swift */; };
+               189C15892C05E0CD00676A80 /* IBTarinState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15882C05E0CD00676A80 /* IBTarinState.swift */; };
+               189C158B2C05F27C00676A80 /* IBAbout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C158A2C05F27C00676A80 /* IBAbout.swift */; };
+               189C158D2C05F58A00676A80 /* IBHistoryBloodOxygen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C158C2C05F58A00676A80 /* IBHistoryBloodOxygen.swift */; };
+               189C158F2C05F85200676A80 /* IBHistoryStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C158E2C05F85200676A80 /* IBHistoryStep.swift */; };
+               189C15912C05FBA100676A80 /* IBHistoryBurning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15902C05FBA100676A80 /* IBHistoryBurning.swift */; };
+               189C15932C05FFA600676A80 /* IBHistoryStand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15922C05FFA600676A80 /* IBHistoryStand.swift */; };
+               189C15952C05FFC700676A80 /* IBHistoryCalorie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15942C05FFC700676A80 /* IBHistoryCalorie.swift */; };
+               189C15972C05FFEE00676A80 /* IBHistoryDistance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15962C05FFEE00676A80 /* IBHistoryDistance.swift */; };
+               18C9589B2C082FA7006A5DE8 /* IBWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C9589A2C082FA7006A5DE8 /* IBWeather.swift */; };
+               18C9589D2C08600C006A5DE8 /* IBHistoryGps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C9589C2C08600C006A5DE8 /* IBHistoryGps.swift */; };
+               18C9589F2C086A9D006A5DE8 /* IBHistorySleep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C9589E2C086A9D006A5DE8 /* IBHistorySleep.swift */; };
+               18E7AC922BF44C6800CB9393 /* IBSleep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E7AC912BF44C6800CB9393 /* IBSleep.swift */; };
+               18E7AC942BF4B31000CB9393 /* IBWater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E7AC932BF4B31000CB9393 /* IBWater.swift */; };
+               18E7AC962BF5E39900CB9393 /* IBHeartRate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E7AC952BF5E39900CB9393 /* IBHeartRate.swift */; };
+               18EC6E282BFF4C6D00757CA8 /* IBPhoneInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EC6E272BFF4C6D00757CA8 /* IBPhoneInfo.swift */; };
+               18EFF59B2C080F14001B1E0E /* IBUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */; };
+               8A0A39BF2B980BEF006A3B18 /* IOTBluetooth.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */; settings = {ATTRIBUTES = (Public, ); }; };
+               8A0A39C62B980C88006A3B18 /* IBluetooth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39C52B980C88006A3B18 /* IBluetooth.swift */; };
+               8A0A39C92B980CC2006A3B18 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */; };
+               8A0A39CB2B980CFC006A3B18 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39CA2B980CFC006A3B18 /* Logger.swift */; };
+               8A0A39CD2B980D53006A3B18 /* IBPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39CC2B980D53006A3B18 /* IBPeripheral.swift */; };
+               8A0A39D22B980E4C006A3B18 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D12B980E4C006A3B18 /* JSON.swift */; };
+               8A0A39D42B980E93006A3B18 /* Any.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D32B980E93006A3B18 /* Any.swift */; };
+               8A0A39D62B980EC9006A3B18 /* IBPeripheralUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D52B980EC9006A3B18 /* IBPeripheralUUID.swift */; };
+               8A0A39D82B980F58006A3B18 /* UTF8.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D72B980F58006A3B18 /* UTF8.swift */; };
+               8A0A39DA2B981037006A3B18 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D92B981037006A3B18 /* Date.swift */; };
+               8A1DFF522B9EB4F800B735FF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */; };
+               8A1DFF562B9EB4F800B735FF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1DFF552B9EB4F800B735FF /* ViewController.swift */; };
+               8A1DFF592B9EB4F900B735FF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8A1DFF572B9EB4F900B735FF /* Main.storyboard */; };
+               8A1DFF5B2B9EB4F900B735FF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8A1DFF5A2B9EB4F900B735FF /* Assets.xcassets */; };
+               8A1DFF5E2B9EB4F900B735FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8A1DFF5C2B9EB4F900B735FF /* LaunchScreen.storyboard */; };
+               8A34B9252BA2A069000F66BD /* IBWatchInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B9242BA2A069000F66BD /* IBWatchInfo.swift */; };
+               8A34B9272BA2AB66000F66BD /* ConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B9262BA2AB66000F66BD /* ConnectViewController.swift */; };
+               8A34B9292BA2AB86000F66BD /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B9282BA2AB86000F66BD /* LogViewController.swift */; };
+               8A34B92B2BA2D446000F66BD /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B92A2BA2D446000F66BD /* BaseViewController.swift */; };
+               8A34B92D2BA2FA02000F66BD /* IBManufacturer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B92C2BA2FA02000F66BD /* IBManufacturer.swift */; };
+               8A5822CF2BA1A67700590AB6 /* IBAbility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A5822CE2BA1A67700590AB6 /* IBAbility.swift */; };
+               8A5822D12BA1C80700590AB6 /* LRC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A5822D02BA1C80700590AB6 /* LRC.swift */; };
+               8A65F95F2BABEC4D005C1EA4 /* IBClock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F95E2BABEC4D005C1EA4 /* IBClock.swift */; };
+               8A65F9612BAC0810005C1EA4 /* IBSimulateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F9602BAC0810005C1EA4 /* IBSimulateParser.swift */; };
+               8A65F9632BAC1018005C1EA4 /* IBTargetStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F9622BAC1018005C1EA4 /* IBTargetStep.swift */; };
+               8A65F9652BAC271B005C1EA4 /* IBScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F9642BAC271B005C1EA4 /* IBScreen.swift */; };
+               8A76B6092BA84C2B0016F2AB /* AbilityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A76B6082BA84C2B0016F2AB /* AbilityViewController.swift */; };
+               8A76B60B2BA851730016F2AB /* IBParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A76B60A2BA851730016F2AB /* IBParser.swift */; };
+               8A79D2252BA4278E0096A98C /* IBSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A79D2242BA4278E0096A98C /* IBSender.swift */; };
+               8A79D2272BA433330096A98C /* IBNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A79D2262BA433330096A98C /* IBNotify.swift */; };
+               8A79D2292BA43F5A0096A98C /* IBResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A79D2282BA43F5A0096A98C /* IBResponse.swift */; };
+               8A878FE02BA93CF1008C7F26 /* Datas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A878FDF2BA93CF1008C7F26 /* Datas.swift */; };
+               8A878FE22BA9444C008C7F26 /* IBParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A878FE12BA9444C008C7F26 /* IBParameter.swift */; };
+               8A878FE42BA94D09008C7F26 /* IBChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A878FE32BA94D09008C7F26 /* IBChecker.swift */; };
+               8A87FBB32BAA7F3900096761 /* IBLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A87FBB22BAA7F3900096761 /* IBLanguage.swift */; };
+               8A87FBB52BAA85FB00096761 /* IBTrainingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A87FBB42BAA85FB00096761 /* IBTrainingData.swift */; };
+               8A9F6ABD2BA0463D00DA520F /* CRC16.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9F6ABC2BA0463D00DA520F /* CRC16.swift */; };
+               8A9F6ABE2BA0475100DA520F /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */; };
+               8A9F6AC12BA04BE100DA520F /* IBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9F6AC02BA04BE100DA520F /* IBManager.swift */; };
+               8A9F6AC22BA05CB500DA520F /* IOTBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */; };
+               8A9F6AC32BA05CB500DA520F /* IOTBluetooth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+               8A9F6AC42BA05CB500DA520F /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 8A0A39B22B980BEF006A3B18 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 8A0A39BA2B980BEF006A3B18;
+                       remoteInfo = IOTBluetooth;
+               };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+               8A9F6AC62BA05CB500DA520F /* Embed Frameworks */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 2147483647;
+                       dstPath = "";
+                       dstSubfolderSpec = 10;
+                       files = (
+                               8A9F6AC32BA05CB500DA520F /* IOTBluetooth.framework in Embed Frameworks */,
+                       );
+                       name = "Embed Frameworks";
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+               180B01972BFC831300524729 /* IBHistoryHeartRate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryHeartRate.swift; sourceTree = "<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>"; };
+               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>"; };
+               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>"; };
+               18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBUserInfo.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 /* IBluetooth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBluetooth.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>"; };
+               8A0A39CC2B980D53006A3B18 /* IBPeripheral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBPeripheral.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>"; };
+               8A34B9262BA2AB66000F66BD /* ConnectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectViewController.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 = (
+                               8A0A39C92B980CC2006A3B18 /* CoreBluetooth.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               8A1DFF4C2B9EB4F800B735FF /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               8A9F6AC22BA05CB500DA520F /* IOTBluetooth.framework in Frameworks */,
+                               8A9F6ABE2BA0475100DA520F /* CoreBluetooth.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+               8A0A39B12B980BEF006A3B18 = {
+                       isa = PBXGroup;
+                       children = (
+                               8A0A39BD2B980BEF006A3B18 /* IOTBluetooth */,
+                               8A1DFF502B9EB4F800B735FF /* Example */,
+                               8A0A39BC2B980BEF006A3B18 /* Products */,
+                               8A0A39C72B980CC2006A3B18 /* Frameworks */,
+                       );
+                       sourceTree = "<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 */,
+                               8A0A39C52B980C88006A3B18 /* IBluetooth.swift */,
+                               8A5822CE2BA1A67700590AB6 /* IBAbility.swift */,
+                               8A878FE32BA94D09008C7F26 /* IBChecker.swift */,
+                               8A76B60A2BA851730016F2AB /* IBParser.swift */,
+                               8A65F9602BAC0810005C1EA4 /* IBSimulateParser.swift */,
+                               8A878FE12BA9444C008C7F26 /* IBParameter.swift */,
+                               8A79D2242BA4278E0096A98C /* IBSender.swift */,
+                               8A79D2262BA433330096A98C /* IBNotify.swift */,
+                               8A9F6AC02BA04BE100DA520F /* IBManager.swift */,
+                               8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */,
+                       );
+                       path = IOTBluetooth;
+                       sourceTree = "<group>";
+               };
+               8A0A39C72B980CC2006A3B18 /* Frameworks */ = {
+                       isa = PBXGroup;
+                       children = (
+                               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 */,
+                       );
+                       path = Tools;
+                       sourceTree = "<group>";
+               };
+               8A1DFF502B9EB4F800B735FF /* Example */ = {
+                       isa = PBXGroup;
+                       children = (
+                               8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */,
+                               8A1DFF552B9EB4F800B735FF /* ViewController.swift */,
+                               8A34B9262BA2AB66000F66BD /* ConnectViewController.swift */,
+                               8A76B6082BA84C2B0016F2AB /* AbilityViewController.swift */,
+                               8A878FDF2BA93CF1008C7F26 /* Datas.swift */,
+                               8A34B92A2BA2D446000F66BD /* BaseViewController.swift */,
+                               8A34B9282BA2AB86000F66BD /* LogViewController.swift */,
+                               8A1DFF572B9EB4F900B735FF /* Main.storyboard */,
+                               8A1DFF5A2B9EB4F900B735FF /* Assets.xcassets */,
+                               8A1DFF5C2B9EB4F900B735FF /* LaunchScreen.storyboard */,
+                               8A1DFF5F2B9EB4F900B735FF /* Info.plist */,
+                       );
+                       path = Example;
+                       sourceTree = "<group>";
+               };
+               8A9F6ABF2BA04B5100DA520F /* Models */ = {
+                       isa = PBXGroup;
+                       children = (
+                               8A0A39CC2B980D53006A3B18 /* IBPeripheral.swift */,
+                               8A87FBB22BAA7F3900096761 /* IBLanguage.swift */,
+                               8A79D2282BA43F5A0096A98C /* IBResponse.swift */,
+                               8A65F95E2BABEC4D005C1EA4 /* IBClock.swift */,
+                               18E7AC912BF44C6800CB9393 /* IBSleep.swift */,
+                               8A65F9642BAC271B005C1EA4 /* IBScreen.swift */,
+                               18E7AC932BF4B31000CB9393 /* IBWater.swift */,
+                               8A65F9622BAC1018005C1EA4 /* IBTargetStep.swift */,
+                               8A87FBB42BAA85FB00096761 /* IBTrainingData.swift */,
+                               8A34B92C2BA2FA02000F66BD /* IBManufacturer.swift */,
+                               8A34B9242BA2A069000F66BD /* IBWatchInfo.swift */,
+                               184431312BF3720E00546E4E /* IBOtherTarget.swift */,
+                               184B94CA2BF606CC0042BEB3 /* IBTrainTarget.swift */,
+                               18E7AC952BF5E39900CB9393 /* IBHeartRate.swift */,
+                               18993AA12C073FED006742D2 /* IBBloodOxygen.swift */,
+                               180B01972BFC831300524729 /* IBHistoryHeartRate.swift */,
+                               189C158C2C05F58A00676A80 /* IBHistoryBloodOxygen.swift */,
+                               189C158E2C05F85200676A80 /* IBHistoryStep.swift */,
+                               189C15902C05FBA100676A80 /* IBHistoryBurning.swift */,
+                               189C15922C05FFA600676A80 /* IBHistoryStand.swift */,
+                               189C15942C05FFC700676A80 /* IBHistoryCalorie.swift */,
+                               189C15962C05FFEE00676A80 /* IBHistoryDistance.swift */,
+                               180B019D2BFC9CF400524729 /* IBHistoryBaseTraining.swift */,
+                               18C9589E2C086A9D006A5DE8 /* IBHistorySleep.swift */,
+                               18C9589C2C08600C006A5DE8 /* IBHistoryGps.swift */,
+                               1849DC8E2BEC8007002C2D2D /* IBVibrateSound.swift */,
+                               18EC6E272BFF4C6D00757CA8 /* IBPhoneInfo.swift */,
+                               1859DF9F2BFF5A2700112E8C /* IBSendTime.swift */,
+                               189C15882C05E0CD00676A80 /* IBTarinState.swift */,
+                               189C158A2C05F27C00676A80 /* IBAbout.swift */,
+                               18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */,
+                               18C9589A2C082FA7006A5DE8 /* IBWeather.swift */,
+                               180CE2642C0D728F00A7006E /* IBAddressBook.swift */,
+                               1849DC902BECD131002C2D2D /* IBCode.swift */,
+                       );
+                       path = Models;
+                       sourceTree = "<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 = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               8A1DFF4D2B9EB4F800B735FF /* Resources */ = {
+                       isa = PBXResourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               8A1DFF5E2B9EB4F900B735FF /* LaunchScreen.storyboard in Resources */,
+                               8A1DFF5B2B9EB4F900B735FF /* Assets.xcassets in Resources */,
+                               8A1DFF592B9EB4F900B735FF /* Main.storyboard in Resources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+               8A0A39B72B980BEF006A3B18 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               8A79D2292BA43F5A0096A98C /* IBResponse.swift in Sources */,
+                               189C15932C05FFA600676A80 /* IBHistoryStand.swift in Sources */,
+                               1849DC8F2BEC8007002C2D2D /* IBVibrateSound.swift in Sources */,
+                               180CE2652C0D728F00A7006E /* IBAddressBook.swift in Sources */,
+                               8A878FE22BA9444C008C7F26 /* IBParameter.swift in Sources */,
+                               184431322BF3720E00546E4E /* IBOtherTarget.swift in Sources */,
+                               189C158B2C05F27C00676A80 /* IBAbout.swift in Sources */,
+                               189C15972C05FFEE00676A80 /* IBHistoryDistance.swift in Sources */,
+                               180B019E2BFC9CF400524729 /* IBHistoryBaseTraining.swift in Sources */,
+                               8A87FBB52BAA85FB00096761 /* IBTrainingData.swift in Sources */,
+                               8A878FE42BA94D09008C7F26 /* IBChecker.swift in Sources */,
+                               189C15892C05E0CD00676A80 /* IBTarinState.swift in Sources */,
+                               8A76B60B2BA851730016F2AB /* IBParser.swift in Sources */,
+                               18EFF59B2C080F14001B1E0E /* IBUserInfo.swift in Sources */,
+                               8A65F9612BAC0810005C1EA4 /* IBSimulateParser.swift in Sources */,
+                               180B01982BFC831300524729 /* IBHistoryHeartRate.swift in Sources */,
+                               189C158D2C05F58A00676A80 /* IBHistoryBloodOxygen.swift in Sources */,
+                               8A79D2272BA433330096A98C /* IBNotify.swift in Sources */,
+                               8A0A39D42B980E93006A3B18 /* Any.swift in Sources */,
+                               1849DC912BECD131002C2D2D /* IBCode.swift in Sources */,
+                               8A34B92D2BA2FA02000F66BD /* IBManufacturer.swift in Sources */,
+                               184B94CB2BF606CC0042BEB3 /* IBTrainTarget.swift in Sources */,
+                               8A79D2252BA4278E0096A98C /* IBSender.swift in Sources */,
+                               18C9589D2C08600C006A5DE8 /* IBHistoryGps.swift in Sources */,
+                               189C15912C05FBA100676A80 /* IBHistoryBurning.swift in Sources */,
+                               8A9F6ABD2BA0463D00DA520F /* CRC16.swift in Sources */,
+                               18E7AC942BF4B31000CB9393 /* IBWater.swift in Sources */,
+                               8A5822CF2BA1A67700590AB6 /* IBAbility.swift in Sources */,
+                               8A65F9652BAC271B005C1EA4 /* IBScreen.swift in Sources */,
+                               1859DFA02BFF5A2700112E8C /* IBSendTime.swift in Sources */,
+                               8A65F9632BAC1018005C1EA4 /* IBTargetStep.swift in Sources */,
+                               8A34B9252BA2A069000F66BD /* IBWatchInfo.swift in Sources */,
+                               8A0A39D62B980EC9006A3B18 /* IBPeripheralUUID.swift in Sources */,
+                               189C15952C05FFC700676A80 /* IBHistoryCalorie.swift in Sources */,
+                               8A9F6AC12BA04BE100DA520F /* IBManager.swift in Sources */,
+                               18EC6E282BFF4C6D00757CA8 /* IBPhoneInfo.swift in Sources */,
+                               18C9589F2C086A9D006A5DE8 /* IBHistorySleep.swift in Sources */,
+                               8A65F95F2BABEC4D005C1EA4 /* IBClock.swift in Sources */,
+                               8A0A39D22B980E4C006A3B18 /* JSON.swift in Sources */,
+                               18E7AC962BF5E39900CB9393 /* IBHeartRate.swift in Sources */,
+                               18C9589B2C082FA7006A5DE8 /* IBWeather.swift in Sources */,
+                               8A0A39D82B980F58006A3B18 /* UTF8.swift in Sources */,
+                               18E7AC922BF44C6800CB9393 /* IBSleep.swift in Sources */,
+                               8A0A39C62B980C88006A3B18 /* IBluetooth.swift in Sources */,
+                               8A0A39CB2B980CFC006A3B18 /* Logger.swift in Sources */,
+                               189C158F2C05F85200676A80 /* IBHistoryStep.swift in Sources */,
+                               8A5822D12BA1C80700590AB6 /* LRC.swift in Sources */,
+                               8A0A39DA2B981037006A3B18 /* Date.swift in Sources */,
+                               18993AA22C073FED006742D2 /* IBBloodOxygen.swift in Sources */,
+                               8A0A39CD2B980D53006A3B18 /* IBPeripheral.swift in Sources */,
+                               8A87FBB32BAA7F3900096761 /* IBLanguage.swift in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               8A1DFF4B2B9EB4F800B735FF /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               8A878FE02BA93CF1008C7F26 /* Datas.swift in Sources */,
+                               8A34B92B2BA2D446000F66BD /* BaseViewController.swift in Sources */,
+                               8A76B6092BA84C2B0016F2AB /* AbilityViewController.swift in Sources */,
+                               8A34B9272BA2AB66000F66BD /* ConnectViewController.swift in Sources */,
+                               8A1DFF562B9EB4F800B735FF /* ViewController.swift in Sources */,
+                               8A1DFF522B9EB4F800B735FF /* AppDelegate.swift in Sources */,
+                               8A34B9292BA2AB86000F66BD /* LogViewController.swift in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+               8A9F6AC52BA05CB500DA520F /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 8A0A39BA2B980BEF006A3B18 /* IOTBluetooth */;
+                       targetProxy = 8A9F6AC42BA05CB500DA520F /* PBXContainerItemProxy */;
+               };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+               8A1DFF572B9EB4F900B735FF /* Main.storyboard */ = {
+                       isa = PBXVariantGroup;
+                       children = (
+                               8A1DFF582B9EB4F900B735FF /* Base */,
+                       );
+                       name = Main.storyboard;
+                       sourceTree = "<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 = {
+                               CLANG_ENABLE_MODULES = YES;
+                               CODE_SIGN_STYLE = Automatic;
+                               CURRENT_PROJECT_VERSION = 1;
+                               DEFINES_MODULE = YES;
+                               DEVELOPMENT_TEAM = M33X987285;
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 1;
+                               DYLIB_INSTALL_NAME_BASE = "@rpath";
+                               ENABLE_MODULE_VERIFIER = YES;
+                               GENERATE_INFOPLIST_FILE = YES;
+                               INFOPLIST_KEY_NSHumanReadableCopyright = "";
+                               INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+                               IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+                               LD_RUNPATH_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "@executable_path/Frameworks",
+                                       "@loader_path/Frameworks",
+                               );
+                               MARKETING_VERSION = 1.0;
+                               MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+                               MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
+                               PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth;
+                               PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+                               SKIP_INSTALL = YES;
+                               SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+                               SUPPORTS_MACCATALYST = NO;
+                               SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+                               SWIFT_EMIT_LOC_STRINGS = YES;
+                               SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+                               SWIFT_VERSION = 5.0;
+                               TARGETED_DEVICE_FAMILY = 1;
+                       };
+                       name = Debug;
+               };
+               8A0A39C42B980BEF006A3B18 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ENABLE_MODULES = YES;
+                               CODE_SIGN_STYLE = Automatic;
+                               CURRENT_PROJECT_VERSION = 1;
+                               DEFINES_MODULE = YES;
+                               DEVELOPMENT_TEAM = M33X987285;
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 1;
+                               DYLIB_INSTALL_NAME_BASE = "@rpath";
+                               ENABLE_MODULE_VERIFIER = YES;
+                               GENERATE_INFOPLIST_FILE = YES;
+                               INFOPLIST_KEY_NSHumanReadableCopyright = "";
+                               INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+                               IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+                               LD_RUNPATH_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "@executable_path/Frameworks",
+                                       "@loader_path/Frameworks",
+                               );
+                               MARKETING_VERSION = 1.0;
+                               MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+                               MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
+                               PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth;
+                               PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+                               SKIP_INSTALL = YES;
+                               SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+                               SUPPORTS_MACCATALYST = NO;
+                               SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+                               SWIFT_EMIT_LOC_STRINGS = YES;
+                               SWIFT_VERSION = 5.0;
+                               TARGETED_DEVICE_FAMILY = 1;
+                       };
+                       name = Release;
+               };
+               8A1DFF602B9EB4F900B735FF /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+                               ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+                               CODE_SIGN_STYLE = Automatic;
+                               CURRENT_PROJECT_VERSION = 1001;
+                               DEVELOPMENT_TEAM = M33X987285;
+                               GENERATE_INFOPLIST_FILE = YES;
+                               INFOPLIST_FILE = Example/Info.plist;
+                               INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "扫描智能手表需要开启蓝牙功能";
+                               INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+                               INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
+                               INFOPLIST_KEY_UIMainStoryboardFile = Main;
+                               INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
+                               INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
+                               INFOPLIST_KEY_UIUserInterfaceStyle = Light;
+                               IPHONEOS_DEPLOYMENT_TARGET = 13.5;
+                               LD_RUNPATH_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "@executable_path/Frameworks",
+                               );
+                               MARKETING_VERSION = 1.0.0;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth.Example;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+                               SUPPORTS_MACCATALYST = NO;
+                               SWIFT_EMIT_LOC_STRINGS = YES;
+                               SWIFT_VERSION = 5.0;
+                               TARGETED_DEVICE_FAMILY = 1;
+                       };
+                       name = Debug;
+               };
+               8A1DFF612B9EB4F900B735FF /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+                               ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+                               CODE_SIGN_STYLE = Automatic;
+                               CURRENT_PROJECT_VERSION = 1001;
+                               DEVELOPMENT_TEAM = M33X987285;
+                               GENERATE_INFOPLIST_FILE = YES;
+                               INFOPLIST_FILE = Example/Info.plist;
+                               INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "扫描智能手表需要开启蓝牙功能";
+                               INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+                               INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
+                               INFOPLIST_KEY_UIMainStoryboardFile = Main;
+                               INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
+                               INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
+                               INFOPLIST_KEY_UIUserInterfaceStyle = Light;
+                               IPHONEOS_DEPLOYMENT_TARGET = 13.5;
+                               LD_RUNPATH_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "@executable_path/Frameworks",
+                               );
+                               MARKETING_VERSION = 1.0.0;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth.Example;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+                               SUPPORTS_MACCATALYST = NO;
+                               SWIFT_EMIT_LOC_STRINGS = YES;
+                               SWIFT_VERSION = 5.0;
+                               TARGETED_DEVICE_FAMILY = 1;
+                       };
+                       name = Release;
+               };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+               8A0A39B52B980BEF006A3B18 /* Build configuration list for PBXProject "IOTBluetooth" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               8A0A39C02B980BEF006A3B18 /* Debug */,
+                               8A0A39C12B980BEF006A3B18 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               8A0A39C22B980BEF006A3B18 /* Build configuration list for PBXNativeTarget "IOTBluetooth" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               8A0A39C32B980BEF006A3B18 /* Debug */,
+                               8A0A39C42B980BEF006A3B18 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               8A1DFF622B9EB4F900B735FF /* Build configuration list for PBXNativeTarget "Example" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               8A1DFF602B9EB4F900B735FF /* Debug */,
+                               8A1DFF612B9EB4F900B735FF /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+/* End XCConfigurationList section */
+       };
+       rootObject = 8A0A39B22B980BEF006A3B18 /* Project object */;
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/IDEFindNavigatorScopes.plist b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/IDEFindNavigatorScopes.plist
new file mode 100644 (file)
index 0000000..5dd5da8
--- /dev/null
@@ -0,0 +1,5 @@
+<?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">
+<array/>
+</plist>
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate
new file mode 100644 (file)
index 0000000..943a344
Binary files /dev/null and b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/IOTBluetooth.xcscheme b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcshareddata/xcschemes/IOTBluetooth.xcscheme
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
new file mode 100644 (file)
index 0000000..c296514
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Bucket
+   uuid = "3E7E940C-0C97-40F4-A990-DDBAB66B37E1"
+   type = "1"
+   version = "2.0">
+</Bucket>
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644 (file)
index 0000000..d0682b2
--- /dev/null
@@ -0,0 +1,27 @@
+<?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>SchemeUserState</key>
+       <dict>
+               <key>Example.xcscheme_^#shared#^_</key>
+               <dict>
+                       <key>orderHint</key>
+                       <integer>1</integer>
+               </dict>
+               <key>IOTBluetooth.xcscheme_^#shared#^_</key>
+               <dict>
+                       <key>orderHint</key>
+                       <integer>0</integer>
+               </dict>
+       </dict>
+       <key>SuppressBuildableAutocreation</key>
+       <dict>
+               <key>8A0A39BA2B980BEF006A3B18</key>
+               <dict>
+                       <key>primary</key>
+                       <true/>
+               </dict>
+       </dict>
+</dict>
+</plist>
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/williamtrillgates.xcuserdatad/xcschemes/xcschememanagement.plist b/IOTBluetooth/IOTBluetooth/IOTBluetooth.xcodeproj/xcuserdata/williamtrillgates.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644 (file)
index 0000000..8d574d1
--- /dev/null
@@ -0,0 +1,19 @@
+<?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>SchemeUserState</key>
+       <dict>
+               <key>Example.xcscheme_^#shared#^_</key>
+               <dict>
+                       <key>orderHint</key>
+                       <integer>1</integer>
+               </dict>
+               <key>IOTBluetooth.xcscheme_^#shared#^_</key>
+               <dict>
+                       <key>orderHint</key>
+                       <integer>0</integer>
+               </dict>
+       </dict>
+</dict>
+</plist>
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAbility.swift
new file mode 100644 (file)
index 0000000..655ac55
--- /dev/null
@@ -0,0 +1,505 @@
+//
+//  IBAbility.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/3/13.
+//
+
+import Foundation
+
+/// 功能指令
+public class IBAbility {
+   
+    /// 命令类型
+    let type: `Type`
+    
+    /// 命令ID
+    private(set) var id: UInt8 = 0x0
+    
+    /// 指定解析器
+    private(set) var parser: IBParser.Type!
+    
+    /// 指定参数检查器
+    private(set) var checker: IBChecker.Type!
+    
+    /// 服务特征
+    private(set) var characteristicUUID: String!
+    
+    private init(_ type: `Type`) {
+        self.type = type
+    }
+    
+    private func id(_ value: UInt8) -> Self {
+        self.id = value
+        return self
+    }
+    
+    private func parser<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) }
+    
+    public var description: String { "[\(String(format: "0x%02X, 0x%02X", type.rawValue, id))] \(characteristicUUID!)" }
+    
+    func isAnswer(_ data: Data) -> Bool {
+        let answerTypeValue = data.bytes[1]
+        let answerID = data.bytes[2]
+        
+        return type.rawValue == answerTypeValue && id == answerID
+    }
+}
+
+extension IBAbility {
+    enum `Type`: UInt8 {
+        /// 短指令
+        case short = 0x01
+        /// 长指令
+        case long = 0x02
+        /// 文件指令(用来发送文件,比如表盘、MP3、OTA等)
+        case file = 0x04
+    }
+}
+
+public extension IBAbility {
+    /// 设置用户数据
+    /// - Parameter userInfo: see ``IBUserInfo``
+    static let userData = IBAbility.long.id(0x01).parser(IBGeneralParser.self).checker(IBUserInfoChecker.self)
+    
+    /// 关于本机内容
+    static let about = IBAbility.short.id(0x03).parser(IBAblutParser.self)
+    
+    /// 切换表盘
+    /// - Parameter dialID:Int
+    /// 表盘id
+    static let switchDial = IBAbility.short.id(0x02).parser(IBSwitchDialParser.self).checker(IBSwitchDialChecker.self)
+    
+    /// APP删除表盘
+    /// - Parameter dialID:Int   \
+    /// 表盘id
+    static let deleteDial = IBAbility.short.id(0x1B).parser(IBGeneralParser.self).checker(IBSwitchDialChecker.self)
+    
+    /// 发送当前表盘ID
+    /// - Returns: Int   \
+    /// Read from ``IBResponse``.value
+    static let fetchCurrentDialId = IBAbility.short.id(0x17).parser(IBFetchCurrentDialParser.self)
+    
+    /// 查找手表
+    /// - Parameter action:
+    ///   0 = 停止查找    \
+    ///   1 = 开始查找
+    static let find = IBAbility.short.id(0x04).parser(IBFindParser.self).checker(IBActionChecker.self)
+    
+    /// 开关消息推送功能
+    /// - Parameter action:
+    ///   0 = 关闭消息    \
+    ///   1 = 开启消息
+    static let message = IBAbility.short.id(0x08).parser(IBGeneralParser.self).checker(IBActionChecker.self)
+    
+    /// 开启指定运动模式
+    /// - Parameter motionMode: see ``IBMotionMode``
+    static let startSport = IBAbility.short.id(0x0A).parser(IBGeneralParser.self).checker(IBSportChecker.self)
+    
+    /// 结束运动模式
+    static let endSport = IBAbility.short.id(0x0B).parser(IBGeneralParser.self)
+    
+    /// 设置训练实时数据采样频率
+    /// - Parameter value:Int   \
+    /// 单位(秒)
+    static let trainFrequency = IBAbility.short.id(0x14).parser(IBGeneralParser.self).checker(IBTrainFrequencyChecker.self)
+    
+    /// 训练暂停/恢复
+    /// - Parameter action:
+    ///   0 = 暂停    \
+    ///   1 = 恢复
+    static let trainPauseResume = IBAbility.short.id(0x16).parser(IBGeneralParser.self).checker(IBActionChecker.self)
+    
+    /// 设置语言
+    /// - Parameter language: see ``IBLanguage``
+    static let language = IBAbility.short.id(0x0E).parser(IBGeneralParser.self).checker(IBLanguageChecker.self)
+    
+    /// 获取手表心率参数
+    /// - Returns: ``IBHeartRate``   \
+    /// Read from ``IBResponse``.value
+    static let fetchHeartRate = IBAbility.short.id(0x1E).parser(IBHeartRateParser.self)
+    
+    /// 设置手表心率参数
+    /// - Parameter heartRate: see ``IBHeartRate``
+    static let heartRate = IBAbility.long.id(0x12).parser(IBGeneralParser.self).checker(IBHeartRateChecker.self)
+    
+    /// 获取手表血氧设置
+    /// - Returns: ``IBBloodOxygen``   \
+    /// Read from ``IBResponse``.value
+    static let fetchBloodOxygen = IBAbility.short.id(0x1F).parser(IBBloodOxygenParser.self)
+    
+    /// 设置手表血氧参数
+    /// - Parameter heartRate: see ``IBBloodOxygen``
+    static let bloodOxygen = IBAbility.long.id(0x13).parser(IBGeneralParser.self).checker(IBBloodOxygenChecker.self)
+    
+    /// 获取实时心率(实时训练数据)
+    /// - Returns: ``IBTrainingData``   \
+    /// Read from ``IBResponse``.value
+    static let currentHeartRate = IBAbility.short.id(0x0F).parser(IBTrainingDataParser.self)
+    
+    /// 开启/关闭自动获取实时心率
+    /// - Parameter action:
+    ///   0 = 关闭自动获取    \
+    ///   1 = 开启自动获取
+    static let heartRateMode = IBAbility.short.id(0x11).parser(IBGeneralParser.self).checker(IBActionChecker.self)
+    
+    /// 设置最高心率提醒
+    /// - Parameter value:Int   \
+    /// 最高不超过 250 ,如果需要关闭最高心率提醒,可将最高心率设为255,开启则设正常值即可。
+    static let heartRateHigh = IBAbility.short.id(0x15).parser(IBGeneralParser.self).checker(IBHeartRateHighChecker.self)
+    
+    /// 手势拍照 
+    /// - Parameter action:
+    ///   0 = 退出手势拍照 \
+    ///   1 = 进入手势拍照
+    static let takePhoto = IBAbility.short.id(0x23).parser(IBTakePhotoParser.self).checker(IBActionChecker.self)
+    
+    /// 恢复出厂设置
+    static let restore = IBAbility.short.id(0x25).parser(IBGeneralParser.self)
+    
+    /// 重新启动
+    static let reboot = IBAbility.short.id(0x26).parser(IBGeneralParser.self)
+    
+    /// 关机
+    static let shutdown = IBAbility.short.id(0x27).parser(IBGeneralParser.self)
+    
+    /// 获取手表电池电量。
+    /// - Returns: Int  \
+    /// Read from ``IBResponse``.value
+    static let fetchBattery = IBAbility.short.id(0x07).parser(IBBatteryParser.self)
+    
+    /// 获取闹钟设置
+    /// - Returns: ``[IBClockInfo]``  \
+    /// Read from ``IBResponse``.items
+    static let fetchClock = IBAbility.short.id(0x01).parser(IBClockParser.self)
+    
+    /// 发送闹钟设置
+    /// - Parameter clock: see ``IBClock``
+    static let setClock = IBAbility.long.id(0x21).parser(IBSetClockParser.self).checker(IBClockChecker.self)
+    
+    /// 获取所有表盘UID
+    /// - Returns: [Int]  \
+    /// Read from ``IBResponse``.items
+    static let fetchDials = IBAbility.short.id(0x2A).parser(IBFetchDialsParser.self)
+    
+    /// APP获取某表盘ID是否已安装
+    /// - Parameter value:Int
+    /// - Returns: Int  
+    ///      0 = 未安装  \
+    ///      1 = 已安装   \
+    ///   Read from ``IBResponse``.value
+    static let checkDialID = IBAbility.long.id(0x0D).parser(IBCheckDialParser.self).checker(IBSwitchDialChecker.self)
+    
+    /// 获取当前手表状态
+    /// - Returns: Int  \
+    ///    0 = 空闲状态  \
+    ///    9 = USB存储状态  \
+    ///    10 = 运动训练状态   \
+    ///   Read from ``IBResponse``.value
+    static let fetchWatchState = IBAbility.short.id(0x19).parser(IBWatchStateParser.self)
+    
+    /// 获取手表屏幕参数
+    /// - Returns: ``IBScreen`` \
+    ///   Read from ``IBResponse``.value
+    static let fetchScreen = IBAbility.short.id(0x1A).parser(IBScreenParser.self)
+    
+    /// 设置手表屏幕参数
+    /// - Parameter screen: see ``screen``
+    static let screen = IBAbility.long.id(0x0E).parser(IBGeneralParser.self).checker(IBScreenChecker.self)
+    
+    /// 获取手表声音与震动参数
+    /// - Returns: ``IBVibrateSound`` \
+    ///   Read from ``IBResponse``.value
+    static let fetchVibrateSound = IBAbility.short.id(0x1C).parser(IBVibrateSoundParser.self)
+    
+    /// APP设置手表声音与震动参数
+    /// - Parameter vibrateSound: see ``IBVibrateSound``
+    static let vibrateSound = IBAbility.long.id(0x0F).parser(IBGeneralParser.self).checker(IBVibrateSoundChecker.self)
+    
+    
+    /// 设置手表辅助功能参数
+    /// - Parameter auxiliary: see ``IBAuxiliary``
+    static let auxiliary = IBAbility.long.id(0x11).parser(IBGeneralParser.self).checker(IBAuxiliaryChecker.self)
+    
+    /// 获取手表辅助功能参数
+    /// - Returns: IBAuxiliary   \
+    /// Read from ``IBResponse``.value
+    static let fetchAuxiliary = IBAbility.short.id(0x1D).parser(IBFetchAuxiliaryParser.self)
+    
+    /// 获取目标步数设置
+    /// - Returns: ``IBTargetStep`` \
+    ///   Read from ``IBResponse``.value
+    static let fetchTargetStep = IBAbility.short.id(0x2B).parser(IBTargetStepParser.self)
+    
+    /// 设置目标步数
+    /// - Parameter targetStep: see ``IBTrainTarget``
+    static let targetStep = IBAbility.long.id(0x16).parser(IBGeneralParser.self).checker(IBTargetStepChecker.self)
+    
+    /// 获取其他目标设置
+    /// - Returns: ``IBOtherTarget`` \
+    ///   Read from ``IBResponse``.value
+    static let fetchOtherTarget = IBAbility.short.id(0x2C).parser(IBOtherTargetParser.self)
+    
+    /// 设置其他目标
+    /// - Parameter targetOther: see ``IBOtherTarget``
+    static let targetOther = IBAbility.long.id(0x17).parser(IBGeneralParser.self).checker(IBOtherTargetChecker.self)
+    
+    /// 获取睡眠设置
+    /// - Returns: ``IBSleep`` \
+    ///   Read from ``IBResponse``.value
+    static let fetchSleep = IBAbility.short.id(0x29).parser(IBSleepParser.self)
+    
+    /// 设置睡眠
+    /// - Parameter sleep: see ``IBSleep``
+    static let sleep = IBAbility.long.id(0x15).parser(IBGeneralParser.self).checker(IBSleepChecker.self)
+    
+    /// 获取手表喝水提醒设置
+    /// - Returns: Int  \
+    ///      0 = 关闭  \
+    ///      1 = 开启   \
+    ///   Read from ``IBResponse``.value
+    static let fetchWater = IBAbility.short.id(0x24).parser(IBWaterParser.self)
+    
+    /// 设置喝水提醒
+    /// - Parameter water: see ``IBWater``
+    static let water = IBAbility.long.id(0x14).parser(IBGeneralParser.self).checker(IBWaterChecker.self)
+    
+    /// 设置手表公英制单位
+    /// - Parameter value:Int   \
+    ///      0 = 公制  \
+    ///      1 = 英制
+    static let unit = IBAbility.short.id(0x28).parser(IBGeneralParser.self).checker(IBValueChecker.self)
+    
+    /// 设置训练目标
+    /// - Parameter trainTarget:see ``IBTrainTarget``
+    static let trainTarget = IBAbility.long.id(0x04).parser(IBGeneralParser.self).checker(IBTrainTargetChecker.self)
+    
+    // MARK: - 同步数据
+    /// 同步心率历史数据
+    /// - Parameter value:
+    ///   0 = 今天    \
+    ///   1 = 前1天   \
+    ///   2 =  前2天  \
+    ///   3 =  前3天  \
+    ///   依此类推,取值范围0~29。
+    /// - Returns: [IBHistoryHeartRateItem]  \
+    /// Read from ``IBResponse``.items
+    static let syncHeartRate = IBAbility.short.id(0x40).parser(IBSyncHeartRateParser.self).checker(IBValueChecker.self)
+    
+    /// 同步血氧历史数据
+    /// - Parameter value:
+    ///   0 = 今天    \
+    ///   1 = 前1天   \
+    ///   2 =  前2天  \
+    ///   3 =  前3天  \
+    ///   依此类推,取值范围0~29。
+    /// - Returns: [IBHistoryBloodOxygenItem]  \
+    /// Read from ``IBResponse``.items
+    static let syncBloodOxygen = IBAbility.short.id(0x41).parser(IBSyncBloodOxygenParser.self).checker(IBValueChecker.self)
+    
+    /// 同步历史步数
+    /// - Parameter value:
+    ///   0 = 今天    \
+    ///   1 = 前1天   \
+    ///   2 =  前2天  \
+    ///   3 =  前3天  \
+    ///   依此类推,取值范围0~29。
+    /// - Returns: [IBHistoryStepItem]  \
+    /// Read from ``IBResponse``.items
+    static let syncStepCount = IBAbility.short.id(0x43).parser(IBSyncStepParser.self).checker(IBValueChecker.self)
+    
+    /// 同步燃脂时间历史数据
+    /// - Parameter value:
+    ///   0 = 今天    \
+    ///   1 = 前1天   \
+    ///   2 =  前2天  \
+    ///   3 =  前3天  \
+    ///   依此类推,取值范围0~29。
+    /// - Returns: [IBHistoryBurningItem]  \
+    /// Read from ``IBResponse``.items
+    static let syncFatBurning = IBAbility.short.id(0x44).parser(IBSyncBurningParser.self).checker(IBValueChecker.self)
+
+    /// 同步站立历史数据
+    /// - Parameter value:
+    ///   0 = 今天    \
+    ///   1 = 前1天   \
+    ///   2 =  前2天  \
+    ///   3 =  前3天  \
+    ///   依此类推,取值范围0~29。
+    /// - Returns: [IBHistoryStandItem]  \
+    /// Read from ``IBResponse``.items
+    static let syncStand = IBAbility.short.id(0x45).parser(IBSyncStandParser.self).checker(IBValueChecker.self)
+    
+    /// 同步卡路里历史数据
+    /// - Parameter value:
+    ///   0 = 今天    \
+    ///   1 = 前1天   \
+    ///   2 =  前2天  \
+    ///   3 =  前3天  \
+    ///   依此类推,取值范围0~29。
+    /// - Returns: [IBHistoryCalorieItem]  \
+    /// Read from ``IBResponse``.items
+    static let syncCalorie = IBAbility.short.id(0x46).parser(IBSyncCalorieParser.self).checker(IBValueChecker.self)
+    
+    /// 同步距离历史数据
+    /// - Parameter value:
+    ///   0 = 今天    \
+    ///   1 = 前1天   \
+    ///   2 =  前2天  \
+    ///   3 =  前3天  \
+    ///   依此类推,取值范围0~29。
+    /// - Returns: [IBHistoryDistanceItem]  \
+    /// Read from ``IBResponse``.items
+    static let syncDistance = IBAbility.short.id(0x47).parser(IBSyncDistanceParser.self).checker(IBValueChecker.self)
+    
+    /// 同步基础训练历史数据
+    /// - Parameter value:
+    ///   0 = 今天    \
+    ///   1 = 前1天   \
+    ///   2 =  前2天  \
+    ///   3 =  前3天  \
+    ///   依此类推,取值范围0~29。
+    /// - Returns: [IBHistoryBaseTrainingItem]  \
+    /// Read from ``IBResponse``.items
+    static let syncTrain = IBAbility.short.id(0x49).parser(IBSyncTrainParser.self).checker(IBValueChecker.self)
+    
+    /// 同步睡眠历史数据
+    /// - Parameter value:
+    ///   0 = 今天    \
+    ///   1 = 前1天   \
+    ///   2 =  前2天  \
+    ///   3 =  前3天  \
+    ///   依此类推,取值范围0~29。
+    /// - Returns: [IBHistorySleepItem]  \
+    /// Read from ``IBResponse``.items
+    static let syncSleep = IBAbility.short.id(0x4B).parser(IBSyncSleepParser.self).checker(IBValueChecker.self)
+ 
+    /// 同步路径坐标集历史数据
+    /// - Parameter value:开始训练时间戳
+    /// - Returns: [IBHistoryGpsItem]  \
+    /// Read from ``IBResponse``.items
+    static let syncGPS = IBAbility.long.id(0x4F).parser(IBSyncGpsParser.self).checker(IBSyncGpsChecker.self)
+    
+    /// 发送同步数据结束消息
+    static let sendSyncEnd = IBAbility.short.id(0x2E).parser(IBGeneralParser.self)
+    
+    /// 获取SD卡剩余空间
+    /// - Returns: Int  \
+    /// Read from ``IBResponse``.value
+    static let disk = IBAbility.short.id(0x05).parser(IBDiskParser.self)
+    
+    /// 发送手机信息
+    /// - Returns: Int  \
+    ///    0 = 指令校验失败 \
+    ///    1 = 手表未与任何手机绑定 \
+    ///    2 = 手表已与该手机绑定  \
+    ///    3 = 手表已与其他手机绑定 \
+    ///   Read from ``IBResponse``.value
+    static let sendPhone = IBAbility.long.id(0x10).parser(IBSendPhoneParser.self).checker(IBSendPhoneChecker.self)
+    
+    /// 发送当前时间
+    /// - Parameter sendTime: see ``IBSendTime``
+    static let sendTime = IBAbility.long.id(0x02).parser(IBGeneralParser.self).checker(IBSendTimeChecker.self)
+    
+    /// 发送解除配对消息
+    static let unpair = IBAbility.short.id(0x10).parser(IBGeneralParser.self)
+    
+    /// 发送取消解除配对消息
+    static let sendUnpair = IBAbility.short.id(0x2D).parser(IBGeneralParser.self)
+    
+    /// 发送天气数据
+    /// - Parameter weather: see ``IBWeather``
+    static let sendWeather = IBAbility.long.id(0x20).parser(IBGeneralParser.self).checker(IBWeatherChecker.self)
+    
+    /// APP发送通讯录
+    /// - Parameter addressBook: see ``IBAddressBook``
+    /// 0~20
+    static let addressBook = IBAbility.long.id(0x0B).parser(IBGeneralParser.self).checker(IBAddressBookChecker.self)
+    
+//    /// 撤销当前文件传输
+//    static let revokeFileSend = IBAbility.short.id(0x20).parser(IBGeneralParser.self).checker(IBFileChecker.self)
+//    /// 获取指定类型所有文件(当前只支持 MP3 和 表盘)
+//     static let fetchFiles = IBAbility.short.id(0x21).parser(IBGeneralParser.self).checker(IBFileChecker.self)
+//    /// 删除指定类型所有文件(当前只支持 MP3 和 表盘)
+//    static let deleteFiles = IBAbility.short.id(0x22).parser(IBGeneralParser.self).checker(IBFileChecker.self)
+//
+//
+}
+
+extension IBAbility {
+
+
+//    
+//    // MARK: - Fetch 获取数据
+
+//    /// 撤销当前文件传输
+//    static let revokeFileTransfer = IBAbility.short.id(0x20)
+
+//    /// 删除指定文件(数据包为文件索引号)
+//    static let deleteFile = IBAbility.long.id(0x22)
+
+
+//    
+//    
+//    /// 发送OTA文件信息
+//    static let ota = IBAbility.file.id(0x20)
+//    /// 发送MP3文件信息
+//    static let mp3 = IBAbility.file.id(0x21)
+//    /// 发送表盘文件信息
+//    static let dial = IBAbility.file.id(0x22)
+//    /// APP发送文件校验码
+//    static let fileCheckCode = IBAbility.file.id(0x23)
+//    ///  发送单个表盘文件信息
+//    static let dialInfo = IBAbility.file.id(0x24)
+//    /// 发送EPO文件信息(04,25)
+//    static let epo = IBAbility.file.id(0x28)
+//    
+//    
+//    // MARK: - 私有
+
+//    
+//    /// 获取当前数据(jSON格式)【不使用】
+//    static let var681B9E92 = IBAbility.short.id(0x06)
+//    /// 开关多媒体功能(播放手机音乐、通话等)【不使用】
+//    static let var681254AC = IBAbility.short.id(0x09)
+//    /// 进入工厂测试模式【不使用】
+//    static let varD3D21DC8 = IBAbility.short.id(0x30)
+//    /// 同步压力历史数据【不使用】
+//    static let varA2A25CD8 = IBAbility.short.id(0x42)
+//    
+//    /// 修改手表蓝牙名称【未使用】
+//    static let var8FA46D73 = IBAbility.long.id(0x03)
+//    /// 同步基础训练实时心率记录【不使用】
+//    static let var6CFBF636 = IBAbility.long.id(0x4B)
+//    /// 同步当天任意时间之后的基础训练记录【不使用】
+//    static let var4D0264A9 = IBAbility.long.id(0x4D)
+//    /// APP发送靶心率区间值给手表(暂不用)
+//    static let var7EE39B21 = IBAbility.long.id(0x0C)
+//    
+//    /// 发送模块升级信息(04,26)【不使用】
+//    static let var1FFA68BC = IBAbility.file.id(0x26)
+//    ///  发送模块单个文件升级信息(04,27)【不使用】
+//    static let var9AD723C3 = IBAbility.file.id(0x27)
+//    /// 发送EPO文件信息(04,25)(旧版,不用)
+//    static let var4F791698 = IBAbility.file.id(0x25)
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBChecker.swift
new file mode 100644 (file)
index 0000000..43f0f55
--- /dev/null
@@ -0,0 +1,291 @@
+//
+//  IBChecker.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/3/19.
+//
+
+import Foundation
+
+/// 参数检查器协议
+protocol IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8]
+}
+
+extension Array where Element == IBParameter {
+    subscript(_ key: IBKey) -> Element? {
+        self.filter({ $0.key == key }).first
+    }
+}
+
+struct IBSwitchDialChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .dialID(dailID) = parameters[.dialID] else { fatalError("缺少参数: dialID") }
+        Logger.log(dailID)
+        return dailID.byte2
+    }
+}
+
+struct IBActionChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .action(action) = parameters[.action] else { fatalError("缺少参数: action") }
+        Logger.log(action)
+        return [action.byte]
+    }
+}
+
+struct IBSportChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .motionMode(mode) = parameters[.motionMode] else { fatalError("缺少参数: motionMode") }
+        Logger.log(mode)
+        return [mode.rawValue.byte]
+    }
+}
+
+struct IBLanguageChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .language(value) = parameters[.language] else { fatalError("缺少参数: language") }
+        Logger.log(value)
+        return [value.rawValue.byte]
+    }
+}
+struct IBAuxiliaryChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .auxiliary(value) = parameters[.auxiliary] else { fatalError("缺少参数: auxiliary") }
+        Logger.log(value)
+        return [value.rawValue.byte]
+    }
+}
+struct IBValueChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .value(value) = parameters[.value] else { fatalError("缺少参数: value") }
+        Logger.log(value)
+        return [value.byte]
+    }
+}
+
+struct IBHeartRateHighChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .value(value) = parameters[.value] else { fatalError("缺少参数: value") }
+        Logger.log(value)
+        return [(value == 0 ? 255 : value).byte]
+    }
+}
+
+struct IBTrainFrequencyChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .value(value) = parameters[.value] else { fatalError("缺少参数: value") }
+        Logger.log(value)
+        return value.byte2
+    }
+}
+
+struct IBSyncGpsChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .value(value) = parameters[.value] else { fatalError("缺少参数: value") }
+        Logger.log(value)
+        return value.byte4
+    }
+}
+
+struct IBFileChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .file(value) = parameters[.file] else { fatalError("缺少参数: file") }
+        Logger.log(value)
+        return [value.rawValue.byte]
+    }
+}
+
+struct IBTargetStepChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .targetStep(value) = parameters[.targetStep] else { fatalError("缺少参数: targetStep") }
+        Logger.log(value.description)
+        return value.value.byte4 + [value.ring.byte] + [value.vibrate.byte] + [value.notice.byte]
+    }
+}
+
+struct IBSleepChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .sleep(value) = parameters[.sleep] else { fatalError("缺少参数: sleep") }
+        Logger.log(value.description)
+        if value.sleepTimeSet == 1,let sleep = value.sleepTime,let getUp = value.getUpTime  {
+            return [value.sleepTimeSet.byte] + [sleep.hour.byte] + [sleep.minute.byte] + [getUp.hour.byte] + [getUp.minute.byte] + [value.alertBeforeMin20.byte]
+        }
+        return [value.sleepTimeSet.byte]
+    }
+}
+struct IBWaterChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .water(value) = parameters[.water] else { fatalError("缺少参数: water") }
+        Logger.log(value.description)
+        
+        var data = [value.alertState.byte]
+        if value.alertState == 1 {
+            data = data + [value.alertType.byte] + value.drinkingValue.byte2 + [value.start.hour.byte] + [value.start.minute.byte]
+        }
+        if value.alertType == 1 {
+                data = data + [value.end.hour.byte] + [value.end.minute.byte] + [value.frequency.rawValue.byte]
+        }
+        return data
+    }
+}
+
+struct IBHeartRateChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .heartRate(value) = parameters[.heartRate] else { fatalError("缺少参数: heartRate") }
+        Logger.log(value.description)
+        var data = [value.rate.rawValue.byte] + [value.state.byte]
+        if value.state == 1 {
+            data = data + [value.higherOrLower.byte] + [value.value.byte]
+        }
+        return data
+    }
+}
+struct IBBloodOxygenChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .bloodOxygen(value) = parameters[.bloodOxygen] else { fatalError("缺少参数: heartRate") }
+        Logger.log(value.description)
+        var data = [value.rate.rawValue.byte] + [value.state.byte]
+        if value.state == 1 {
+            data = data + [value.value.byte]
+        }
+        return data
+    }
+}
+struct IBScreenChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .screen(value) = parameters[.screen] else { fatalError("缺少参数: screen") }
+        Logger.log(value.description)
+        let data = [value.brightness.byte] + [value.duration.rawValue.byte] + [value.wrist.byte]
+        return data
+    }
+}
+
+struct IBOtherTargetChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .otherTarget(value) = parameters[.otherTarget] else { fatalError("缺少参数: otherTarget") }
+        Logger.log(value.description)
+        var data = value.calorie.byte2 + [value.mileage.byte] + [value.stand.byte] + [value.burning.byte] + [value.ringsState.byte] + [value.vibrateState.byte]
+        data = data + [value.noticeState.byte]
+        return data
+    }
+}
+
+struct IBVibrateSoundChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .vibrateSound(value) = parameters[.vibrateSound] else { fatalError("缺少参数: vibrateSound") }
+        Logger.log(value.description)
+        let data = [value.systemValue.byte] + [value.ringsValue.byte] + [value.mediaValue.byte] + [value.ringsState.byte] + [value.vibrateState.byte]
+        return data
+    }
+}
+struct IBUserInfoChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .userInfo(value) = parameters[.userInfo] else { fatalError("缺少参数: userInfo") }
+        Logger.log(value.description)
+        var data = [value.age.byte] + [value.gender.byte] + [value.height.byte] + value.weight.byte2 + value.step.byte4 + value.burning.byte2
+        data = data + [value.stand.byte] + [value.hourSystem.byte]
+        return data
+    }
+}
+
+
+struct IBClockChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .clock(value) = parameters[.clock] else { fatalError("缺少参数: clock") }
+        guard let data = value.encode else { fatalError("json解析异常: clock") }
+        Logger.log(value.jsonString ?? "")
+        
+        return data.bytes
+    }
+}
+struct IBTrainTargetChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .trainTarget(value) = parameters[.trainTarget] else { fatalError("缺少参数: trainTarget") }
+        guard let data = value.encode else { fatalError("json解析异常: trainTarget") }
+        Logger.log(value.jsonString ?? "")
+        
+        return data.bytes
+    }
+}
+struct IBSendPhoneChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .sendPhone(value) = parameters[.sendPhone] else { fatalError("缺少参数: sendPhone") }
+        guard let data = value.encode else { fatalError("json解析异常: sendPhone") }
+        Logger.log(value.jsonString ?? "")
+        
+        return data.bytes
+    }
+}
+struct IBSendTimeChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .sendTime(value) = parameters[.sendTime] else { fatalError("缺少参数: sendTime") }
+        guard let data = value.encode else { fatalError("json解析异常: sendTime") }
+        Logger.log(value.jsonString ?? "")
+        return data.bytes
+    }
+}
+struct IBWeatherChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .weather(value) = parameters[.weather] else { fatalError("缺少参数: weather") }
+        guard let data = value.encode else { fatalError("json解析异常: weather") }
+        Logger.log(value.jsonString ?? "")
+        return data.bytes
+    }
+}
+struct IBAddressBookChecker: IBChecker {
+    static func check(_ parameters: [IBParameter]) -> [UInt8] {
+        guard case let .addressBook(list) = parameters[.addressBook] else { fatalError("缺少参数: addressBook") }
+        if list.count > 20 { fatalError("参数: addressBook,最多不能超过20个联系人") }
+        var data = [list.count.byte]
+        var des = "[ "
+        for item in list {
+            des += item.description
+            //性别
+            data = data + [item.gender.byte]
+            //姓名
+            if let name = item.name,let value = name.data(using: .utf8),!value.isEmpty  {
+                data += [value.count.byte]
+                data += value.bytes
+            }
+            else{
+                data += [0.byte]
+            }
+            //电话号码个数
+            data += [item.phoneNumbers.count.byte]
+            if item.phoneNumbers.count > 2 { fatalError("参数: addressBook,电话号码书数不能超过两个") }
+            
+            for phone in item.phoneNumbers {
+                //电话号码
+                if let value = phone.number.data(using: .utf8),!value.isEmpty  {
+                    data += [value.count.byte]
+                    data += value.bytes
+                }
+                else{
+                    data += [0.byte]
+                }
+                //归属地
+                if let value = phone.location?.data(using: .utf8),!value.isEmpty  {
+                    data += [value.count.byte]
+                    data += value.bytes
+                }
+                else{
+                    data += [0.byte]
+                }
+                //运营商
+                if let value = phone.operator?.data(using: .utf8),!value.isEmpty  {
+                    data += [value.count.byte]
+                    data += value.bytes
+                }
+                else{
+                    data += [0.byte]
+                }
+            }
+        }
+        des += " ]"
+        Logger.log(des)
+        
+        return data
+    }
+}
+
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBKeeper.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBKeeper.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBManager.swift
new file mode 100644 (file)
index 0000000..765644a
--- /dev/null
@@ -0,0 +1,75 @@
+//
+//  IBManager.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/3/12.
+//
+
+import Foundation
+
+public typealias IBDiscoverHandler = (_ peripheral: IBPeripheral) -> Void
+public typealias IBCompleteHandler = (_ isSuccess: Bool) -> Void
+
+/// SDK 管理类
+public class IBManager {
+    
+    /// 初始化工作
+    public static func initializationSDK() {
+//        IBBluetooth.shared.scan(.default)
+        
+//        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
+//            IBBluetooth.shared.reconnected()
+//        }
+    }
+    
+    public static var logs: [String] { Logger.logs }
+}
+
+public extension IBManager {
+    /// SDK 版本号
+    static let version: String = "1.0.7"
+    /// 默认为 false
+    static var isDebug: Bool = false {
+        didSet { Logger.isDebug = isDebug }
+    }
+}
+
+// 蓝牙相关 API
+public extension IBManager {
+    
+    internal static var discoverHandler: IBDiscoverHandler?
+    
+    /// 扫描设备
+    /// - Parameters:
+    ///   - option: 扫描参数
+    ///   - discoverHandler: 结果回调
+    static func scan(_ option: IBScanOption = .default, discoverHandler: @escaping IBDiscoverHandler) {
+        Self.discoverHandler = discoverHandler
+        IBBluetooth.shared.scan(option)
+    }
+    
+    /// 停止扫描蓝牙设备
+    static func stopScan() {
+        Self.discoverHandler = nil
+        IBBluetooth.shared.stopScan()
+    }
+    
+    internal static var completeHandler: IBCompleteHandler?
+    
+    /// 连接设备
+    /// - Parameters:
+    ///   - peripheral: 扫描发现的设备
+    ///   - completeHandler: 结果回调
+    static func connect(_ peripheral: IBPeripheral, completeHandler: @escaping IBCompleteHandler) {
+        Self.completeHandler = completeHandler
+        IBBluetooth.shared.connect(peripheral)
+    }
+    
+    /// 断开与蓝牙设备的连接
+    /// - Parameter peripheral: 蓝牙设备对像
+    static func cancel(_ peripheral: IBPeripheral) {
+        IBBluetooth.shared.cancel(peripheral)
+    }
+    /// 当前已连接设备
+    static var currentConnected: IBPeripheral! { IBBluetooth.shared.currentConnected}
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBNotify.swift
new file mode 100644 (file)
index 0000000..a7c5a57
--- /dev/null
@@ -0,0 +1,268 @@
+//
+//  IBNotify.swift
+//
+//
+//  Created by Harvey on 2020/1/5.
+//  Copyright © 2020 姚作潘/Harvey. All rights reserved.
+//
+
+import Foundation
+
+fileprivate let center = NotificationCenter.default
+
+fileprivate class Weakly: NSObject {
+    weak var owner: NSObjectProtocol?
+    var delegateObservers: [NSObjectProtocol]
+    init(_ owner: NSObjectProtocol, delegateObservers: [NSObjectProtocol] = []) {
+        self.owner = owner
+        self.delegateObservers = delegateObservers
+    }
+}
+
+extension IBNotify {
+    
+    fileprivate struct Keeper {
+        static var observers: [Weakly] = []
+        static func remove(_ observer: NSObjectProtocol) {
+            observers
+                .filter { $0.owner === observer || isNil($0.owner) }
+                .forEach {
+                    $0.delegateObservers.forEach { center.removeObserver($0) }
+                }
+            observers.removeAll { isNil($0.owner) || $0.owner === observer }
+        }
+        
+        static func append(_ delegateObserver: NSObjectProtocol, owner: NSObjectProtocol) {
+            guard let weakly = observers.filter({ $0.owner === owner }).first else {
+                observers.append(Weakly(owner, delegateObservers: [delegateObserver]))
+                return
+            }
+            weakly.delegateObservers.append(delegateObserver)
+        }
+    }
+    
+    public struct Name {
+        fileprivate let rawValue: String
+        init(_ rawValue: String) {
+            self.rawValue = rawValue
+        }
+    }
+}
+
+public class IBNotify {
+    
+    init() {}
+    
+    private(set) var name: Notification.Name!
+    @discardableResult
+    func name(_ value: IBNotify.Name) -> Self {
+        name = Notification.Name(rawValue: value.rawValue)
+        return self
+    }
+    
+    private(set) var observer: NSObjectProtocol!
+    @discardableResult
+    func observer(_ value: NSObjectProtocol) -> Self {
+        observer = value
+        return self
+    }
+    
+    private(set) var queue: OperationQueue = .main
+    @discardableResult
+    func queue(_ value: OperationQueue) -> Self {
+        queue = value
+        return self
+    }
+    
+    private(set) var object: Any?
+    @discardableResult
+    func object(_ value: Any) -> Self {
+        object = value
+        return self
+    }
+    
+    func subscribe(_ value: @escaping (Notification) -> Void) {
+        if isNil(observer) || isNil(name) {
+            fatalError("observer or name must be not nil")
+        }
+        
+        let delegateObserver = center
+            .addObserver(forName: name,
+                         object: object,
+                         queue: queue,
+                         using: value)
+        Keeper.append(delegateObserver, owner: observer)
+    }
+    
+    static func remove(_ observer: NSObjectProtocol) {
+        center.removeObserver(observer)
+        Keeper.remove(observer)
+    }
+}
+
+extension IBNotify {
+    static func post(_ name: IBNotify.Name,
+                     object: Any? = nil,
+                     userInfo: [AnyHashable : Any] = [:]) {
+        center.post(name: Notification.Name(rawValue: name.rawValue),
+                    object: object, userInfo: userInfo)
+    }
+}
+
+public extension IBManager {
+    
+    /// 订阅通知
+    static func subscribe(_ name: IBNotify.Name,
+                          observer: NSObjectProtocol!,
+                          receive: @escaping (_ object: Any?, _ userInfo: [String: Any]) -> Void) {
+        IBNotify()
+            .name(name)
+            .observer(observer)
+            .subscribe { notification in
+                receive(notification.object, notification.userInfo as? [String: Any] ?? [:])
+        }
+    }
+    
+    /// 移除订阅通知的观察者
+    static func remove(_ observer: NSObjectProtocol) {
+        IBNotify.remove(observer)
+    }
+}
+
+public extension IBNotify.Name {
+    /// 比如电量、信号强度
+//    static let deviceInfoUpdate = IBNotify.Name("IOTBluetooth.DeviceInfoUpdate")
+    
+    /// 连接设备发生变动,一种情况断开连接,一种是切换设备
+//    static let connectedDeviceDidChange = IBNotify.Name("IOTBluetooth.DeviceConnected")
+    
+    /// 鉴权状态改变,通过object 获取状态,0准备鉴权。1等待手表同意连接。  2等待手机确认配对请求。3鉴权成功。4鉴权失败。
+//    static let authStateDidChange = IBNotify.Name("IOTBluetooth.AuthStateDidChange")
+    
+    /// 发现设备
+//    static let deviceHasFound = IBNotify.Name("IOTBluetooth.DeviceHasFound")
+    
+    
+    /// 连接状态发生改变。通过object 获取状态,0断开连接。1连上设备。
+    static let connectStateDidChange = IBNotify.Name("IOTBluetooth.ConnectStateDidChange")
+    
+    /// 远程拍照动作。通过object获取状态。2进行手势拍照,3退出
+    static let takePhoto = IBNotify.Name("IOTBluetooth.TakePhoto")
+    
+    /// 手表请求天气数据
+    static let weather = IBNotify.Name("IOTBluetooth.weather")
+    
+    /// 找到设备
+    static let didFindDevice = IBNotify.Name("IOTBluetooth.FindDevice")
+    
+    /// 训练状态变化,通过object获取状态IBTarinState对象
+    static let trainStateDidChanged = IBNotify.Name("IOTBluetooth.TrainStateChanged")
+    
+    /// 获取手表推送的训练实时数据,通过object获取状态IBTrainingData对象
+    static let trainingData = IBNotify.Name("IOTBluetooth.TrainingData")
+}
+
+extension IBNotify {
+    
+    /// 处理手表推送过来的通知
+    static func handle(_ data: Data) -> Bool {
+        //拍照
+        if IBAbility.takePhoto.isAnswer(data) {
+            let result = IBGeneralParser.dataPacket(data)
+            let code = result[0].int
+            if code == 2 || code == 3 {
+                post(.takePhoto, object:code)
+            }
+            return true
+        }
+        //找到设备
+        if IBAbility.find.isAnswer(data) {
+            var bytes = data.bytes
+            bytes.removeLast()
+            let code = bytes.last!.int
+            if code == 2 {
+                post(.didFindDevice)
+            }
+            return true
+        }
+        //训练状态变化
+        if data.bytes[1] == 0x05 && data.bytes[2] == 0x50 {
+            let result = IBGeneralParser.dataPacket(data)
+            let model = IBTarinState(result.data)
+            post(.trainStateDidChanged, object: model)
+            return true
+        }
+        //训练实时数据
+        if IBAbility.currentHeartRate.isAnswer(data) {
+            let dataPacket = IBGeneralParser.dataPacket(data)
+            if  let model = IBTrainingData(dataPacket.data) {
+                post(.trainingData, object: model)
+            }
+            return true
+        }
+        //请求天气
+        if data.bytes[1] == 0x05 && data.bytes[2] == 0x03 {
+            post(.weather)
+            return true
+        }
+        
+        //配对
+        if data.bytes[1] == 0x05 && data.bytes[2] == 0x11 {
+            let result = IBGeneralParser.dataPacket(data)
+            let code = result[0].int
+            let desc = ["拒绝配对","确认配对","确认解绑其他手机","其他"]
+            Logger.log("收到确认配对消息:",desc[code])
+            return true
+        }
+        if data.bytes[1] == 0x05 && data.bytes[2] == 0x1 {
+            let result = IBGeneralParser.dataPacket(data)
+            let code = result[0].int
+            let desc = ["拒绝解绑","确认解绑"]
+            Logger.log("收到确认解绑消息 ",desc[code])
+            return true
+        }
+        if data.bytes[1] == 0x05 && data.bytes[2] == 0x10 {
+            var result = IBGeneralParser.dataPacket(data)
+            let code = result[0].int
+            if code == 1{
+                result.removeFirst()
+                UserDefaults.standard.setValue(result, forKey: "IOTBletooth.mac")
+                UserDefaults.standard.synchronize()
+                
+                Logger.log("收到配对成功消息",result)
+                //发送时间
+                IBNotify.sendTime()
+            }
+            else{
+                Logger.log("收到配对失败消息")
+                IBManager.completeHandler?(false)
+                IBManager.completeHandler = nil
+            }
+            
+            return true
+        }
+        return false
+    }
+    
+     static func sendTime(){
+        let date = Date()
+        let model = IBSendTime()
+        model.time = Int(date.timeIntervalSince1970)
+        model.week = date.weekDay.rawValue - 1
+        model.year = date.year - 2000
+        model.month = date.month
+        model.day = date.day
+        model.hour = date.hour
+        model.minute = date.minute
+        model.second = date.second
+        
+        IBSender()
+            .ability(IBAbility.sendTime)
+            .parameters([.sendTime(model)])
+            .response { result in
+                Logger.log(result)
+                IBManager.completeHandler?(true)
+                IBManager.completeHandler = nil
+            }
+    }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParameter.swift
new file mode 100644 (file)
index 0000000..23dfa2e
--- /dev/null
@@ -0,0 +1,180 @@
+//
+//  IBParameter.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/3/19.
+//
+
+import Foundation
+
+enum IBKey: String {
+    case dialID = "dialID"
+    case action = "action"
+    case motionMode = "motionMode"
+    case language = "language"
+    case value = "value"
+    case file = "file"
+    case targetStep = "targetStep"
+    case sleep = "sleep"
+    case water = "water"
+    case clock = "clock"
+    case heartRate = "heartRate"
+    case bloodOxygen = "bloodOxygen"
+    case screen = "screen"
+    case vibrateSound = "vibrateSound"
+    case otherTarget = "otherTarget"
+    case trainTarget = "trainTarget"
+    case sendPhone = "sendPhone"
+    case sendTime = "sendTime"
+    case auxiliary = "auxiliary"
+    case userInfo = "userInfo"
+    case weather = "weather"
+    case addressBook = "addressBook"
+}
+
+/// 参数
+public enum IBParameter {
+    /// 表盘ID
+    case dialID(Int)
+    /// 1=(开始、启用、开启),0=(停止、禁用、关闭)
+    case action(Int)
+    /// 运动模式
+    case motionMode(IBMotionMode)
+    /// 语言
+    case language(IBLanguage)
+    /// 通用值设置
+    case value(Int)
+    /// 文件
+    case file(IBFile)
+    /// 训练目标
+    case targetStep(IBTargetStep)
+    /// 睡眠
+    case sleep(IBSleep)
+    /// 喝水
+    case water(IBWater)
+    /// 设置闹钟
+    case clock(IBClock)
+    /// 设置心率
+    case heartRate(IBHeartRate)
+    /// 血氧
+    case bloodOxygen(IBBloodOxygen)
+    /// 设置屏幕
+    case screen(IBScreen)
+    /// 设置声音和震动
+    case vibrateSound(IBVibrateSound)
+    /// 设置目标设置
+    case otherTarget(IBOtherTarget)
+    /// 设置训练目标
+    case trainTarget(IBTrainTarget)
+    /// 发送手机信息
+    case sendPhone(IBPhoneInfo)
+    /// 发送当前时间
+    case sendTime(IBSendTime)
+    /// 设置辅助功能
+    case auxiliary(IBAuxiliary)
+    /// 设置用户数据
+    case userInfo(IBUserInfo)
+    /// 发送天气数据
+    case weather(IBWeather)
+    /// 发送通讯录
+    case addressBook([IBAddressBook])
+    
+    var key: IBKey {
+        switch self {
+        case .dialID(_): return .dialID
+        case .action(_): return .action
+        case .motionMode(_): return .motionMode
+        case .language(_): return .language
+        case .value(_): return .value
+        case .file(_): return .file
+        case .targetStep(_): return .targetStep
+        case .sleep(_): return .sleep
+        case .water(_): return .water
+        case .clock(_): return .clock
+        case .heartRate(_): return .heartRate
+        case .bloodOxygen(_): return .bloodOxygen
+        case .screen(_): return .screen
+        case .vibrateSound(_): return .vibrateSound
+        case .auxiliary(_): return .auxiliary
+        case .otherTarget(_): return .otherTarget
+        case .trainTarget(_): return .trainTarget
+        case .sendPhone(_): return .sendPhone
+        case .sendTime(_): return .sendTime
+        case .userInfo(_): return .userInfo
+        case .weather(_): return .weather
+        case .addressBook(_): return .addressBook
+        }
+    }
+}
+
+public enum IBMotionMode: Int {
+    /// 健走
+    case walking = 0
+    /// 跑步
+    case running
+    /// 骑行
+    case ride
+}
+
+public enum IBFile: Int {
+    /// OTA 文件
+    case ota = 1
+    /// MP3 文件
+    case mp3
+    /// 表盘信息
+    case dial
+    /// 天气
+    case weather
+    /// 闹钟
+    case clock
+}
+//0:运动
+//1:心率
+//2:血氧监测
+//3:训练
+//4:训练记录
+//5:指南针
+//6:天气
+//7:音乐
+//8:电话
+//9:录音
+//10:秒表
+//11:计时器
+//12:闹钟
+//13:蓝牙
+//14:睡眠
+//15:设置
+public enum IBAuxiliary: Int {
+    /// 运动
+    case sport = 0
+    /// 心率
+    case heart
+    /// 血氧监测
+    case bloodOxygen
+    /// 训练
+    case train
+    /// 训练记录
+    case tarinRecord
+    /// 指南针
+    case compass
+    /// 天气
+    case weather
+    /// 音乐
+    case music
+    /// 电话
+    case mobile
+    /// 录音
+    case record
+    /// 秒表
+    case secondTimer
+    /// 计时器
+    case timer
+    /// 闹钟
+    case clock
+    /// 蓝牙
+    case bluetooth
+    /// 睡眠
+    case sleep
+    /// 设置
+    case setting
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBParser.swift
new file mode 100644 (file)
index 0000000..97e3ea5
--- /dev/null
@@ -0,0 +1,329 @@
+//
+//  IBParser.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/3/18.
+//
+
+import Foundation
+
+/// 蓝牙数据解析协议
+protocol IBParser {
+    /// 解析数据
+     static func parse(_ data: Data) -> IBResponse
+}
+
+extension IBParser {
+    static func dataPacket(_ data: Data) -> [UInt8] {
+        let dataLength = data[7...10].int
+        return data.suffix(from: 17).prefix(dataLength).map({ $0 })
+    }
+}
+
+struct IBBatteryParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        var bytes = data.bytes
+        bytes.removeLast()
+        return .response.code(.success).value(bytes.last!.int)
+    }
+}
+
+struct IBSwitchDialParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        var bytes = data.bytes
+        bytes.removeLast()
+        let code = bytes.last!.int
+        let desc = ["指令校验失败", "切换成功", "无该表盘资源", "切换失败"]
+        return .response.code(code == 1 ? .success:.failure).message(desc[code]).value(code)
+    }
+}
+struct IBFetchCurrentDialParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let bytes = dataPacket(data)
+        let value = bytes.int
+        return .response.code(.success).value(value)
+    }
+}
+struct IBFetchAuxiliaryParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let bytes = dataPacket(data)
+        guard let value = IBAuxiliary(rawValue: bytes.int) else {return .response.code(.parseError) }
+        return .response.code(.success).value(value)
+    }
+}
+
+struct IBCheckDialParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        var bytes = data.bytes
+        bytes.removeLast()
+        let code = bytes.last!.int
+        let desc = ["指令校验失败", "已安装", "未安装"]
+        return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code)
+    }
+}
+
+/// 通用解析(1 Byte)
+struct IBGeneralParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+//        guard data.isEmpty == false else { return IBSingleByteSimulateParser.parse() }
+        
+        let code = dataPacket(data).int
+        return .response.code(code == 1 ? .success:.failure)
+    }
+}
+struct IBTakePhotoParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let code = dataPacket(data).int
+        let desc = ["指令校验失败", "操作成功", "进行手势拍照","退出"]
+        return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code)
+    }
+}
+struct IBWaterParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+//        guard data.isEmpty == false else { return IBSingleByteSimulateParser.parse() }
+        
+        let code = dataPacket(data).int
+        let desc = code == 0 ? "关闭":"开启"
+        return .response.code(.success).value(code).message(desc)
+    }
+}
+struct IBFindParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let code = dataPacket(data).int
+        let desc = ["指令校验失败", "操作成功", "手表已找到,停止查找"]
+        return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code)
+    }
+}
+struct IBSendPhoneParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let code = dataPacket(data).int
+        let desc = ["指令校验失败","手表未与任何手机绑定", "手表已与该手机绑定", "手表已与其他手机绑定"]
+        return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code)
+    }
+}
+
+
+struct IBWatchStateParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        var bytes = data.bytes
+        bytes.removeLast()
+        let code = bytes.last!.int
+        let desc = [0:"空闲状态", 9:"USB存储状态", 10:"运动训练状态"]
+        return .response.code(.success).value(code).message(desc[code] ?? "")
+    }
+}
+
+
+struct IBDiskParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let code = dataPacket[0].int
+        if code == 1 {
+            var value: Int = 0
+            value = dataPacket.suffix(from: 1).map({ $0 }).int
+            return .response.code(.success).value(value)
+        }
+        else {
+            return .response.code(.failure)
+        }
+    }
+}
+
+struct IBTrainingDataParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        if dataPacket.count == 0 {
+            return .response.code(.success)
+        }
+        guard let model = IBTrainingData(dataPacket.data) else { return .response.code(.parseError) }
+        return .response.code(.success).value(model)
+    }
+}
+struct IBHeartRateParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBHeartRate(dataPacket.data)
+        Logger.log(model.description)
+        return .response.code(.success).value(model)
+    }
+}
+struct IBBloodOxygenParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBBloodOxygen(dataPacket.data)
+        Logger.log(model.description)
+        return .response.code(.success).value(model)
+    }
+}
+
+struct IBSyncTrainParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        guard let model = JSON.decode(IBHistoryBaseTraining.self, from: dataPacket.data) else { return .response.code(.parseError) }
+        return .response.code(.success).items(model.list)
+        
+//        let dataPacket = baseData.jsonString()!
+//        guard let model = JSON.decode(IBHistoryBaseTraining.self, from: dataPacket.data!) else { return .response.code(.parseError) }
+//        Logger.log(model.jsonString ?? "")
+//        return .response.code(.success).items(model.list)
+    }
+}
+struct IBSyncHeartRateParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBHistoryHeartRate(dataPacket.data)
+//        Logger.log(model.list)
+        return .response.code(.success).items(model.list)
+    }
+}
+struct IBSyncBloodOxygenParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBHistoryBloodOxygen(dataPacket.data)
+//        Logger.log(model.list)
+        return .response.code(.success).items(model.list)
+    }
+}
+struct IBSyncStepParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBHistoryStep(dataPacket.data)
+//        Logger.log(model.list)
+        return .response.code(.success).items(model.list)
+    }
+}
+struct IBSyncBurningParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBHistoryBurning(dataPacket.data)
+//        Logger.log(model.list)
+        return .response.code(.success).items(model.list)
+    }
+}
+struct IBSyncStandParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBHistoryStand(dataPacket.data)
+//        Logger.log(model.list)
+        return .response.code(.success).items(model.list)
+    }
+}
+struct IBSyncCalorieParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBHistoryCalorie(dataPacket.data)
+//        Logger.log(model.list)
+        return .response.code(.success).items(model.list)
+    }
+}
+struct IBSyncDistanceParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBHistoryDistance(dataPacket.data)
+//        Logger.log(model.list)
+        return .response.code(.success).items(model.list)
+    }
+}
+struct IBSyncSleepParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBHistorySleep(dataPacket.data)
+//        Logger.log(model.list)
+        return .response.code(.success).items(model.list)
+    }
+}
+struct IBSyncGpsParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let model = IBHistoryGps(dataPacket.data)
+//        Logger.log(model.list)
+        return .response.code(.success).items(model.list)
+    }
+}
+
+
+struct IBAblutParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        guard let model = JSON.decode(IBAbout.self, from: dataPacket.data) else { return .response.code(.parseError) }
+        Logger.log(model.jsonString ?? "")
+        return .response.code(.success).value(model)
+    }
+}
+struct IBClockParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        guard let model = JSON.decode(IBClock.self, from: dataPacket.data) else { return .response.code(.parseError) }
+        Logger.log(model.jsonString ?? "")
+        return .response.code(.success).items(model.alarms)
+    }
+}
+struct IBSetClockParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+//        guard data.isEmpty == false else { return IBClockSimulateParser.parse() }
+        let code = dataPacket(data).int
+        return .response.code(code == 1 ? .success:.failure)
+    }
+}
+
+struct IBFetchDialsParser: IBParser {
+    static func parse(_ data: Data) -> IBResponse {
+        let dataPacket = dataPacket(data)
+        let num = dataPacket[0].int
+        var list:[Int] = []
+        for i in 0..<num {
+            list.append(dataPacket.data.subdata(in: 2*i..<2*i + 2).int)
+        }
+        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)
+    }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSender.swift
new file mode 100644 (file)
index 0000000..d9527d8
--- /dev/null
@@ -0,0 +1,111 @@
+//
+//  IBSender.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/3/15.
+//
+
+import Foundation
+
+public typealias IBCompletionHandler = (_ result: IBResponse) -> Void
+
+//public typealias IBCmdSuccess = (_ result: IBResponse) -> Void
+//public typealias IBCmdError = (_ error: IBError) -> Void
+
+/// 蓝牙消息发送类
+public class IBSender {
+    public init() {
+        Self.all.append(self)
+    }
+    
+    var ability: IBAbility!
+    var parameters: [IBParameter] = []
+    var completion: IBCompletionHandler!
+    
+//    var cmdSuccess: IBCmdSuccess?
+//    var cmdFailure: IBCmdError?
+    
+    var data: [UInt8] = []
+    
+    var dataPacket: [UInt8] {
+        if isValue(ability.checker) {
+            data = ability.checker.check(parameters)
+        }
+        
+//        let data: [UInt8] = data?.bytes ?? []
+        /// 分包数
+        let packageCount = 1
+        /// 数据包总长度
+        let dataTotalLength = data.count
+        /// 数据包长度
+        let dataLength = data.count
+        /// 数据包索引
+        let index = 1
+        
+        var bytes: [UInt8] = [0x73, ability.type.rawValue, ability.id]
+        bytes = bytes + packageCount.byte4 + dataTotalLength.byte4 + index.byte4 + dataLength.byte2
+       
+        if data.count > 0 {
+            bytes = bytes + data
+            bytes = bytes + [LRC.calculateLRC(data)]
+        } else {
+            bytes = bytes + [0x00]
+        }
+        
+        return bytes
+    }
+    
+    func isAnswer(_ data: Data) -> Bool {
+        return ability.isAnswer(data)
+    }
+    
+    func parse(_ data: Data) -> IBResponse {
+        return ability.parser.parse(data)
+    }
+    
+    deinit {
+//        Logger.log("deinit IBSender")
+    }
+}
+
+public extension IBSender {
+    
+    /// 功能接口
+    @discardableResult
+    func ability(_ value: IBAbility) -> Self {
+        ability = value
+        return self
+    }
+    
+    /// 参数
+    @discardableResult
+    func parameters(_ value: [IBParameter]) -> Self {
+        parameters = value
+        return self
+    }
+    
+    /// 执行与响应处理
+    @discardableResult
+    func response(_ handler: @escaping IBCompletionHandler) -> Self {
+        completion = handler
+        IBBluetooth.shared.send(self)
+        return self
+    }
+//    /// 执行与响应处理
+//    @discardableResult
+//    func response(success: @escaping IBCmdSuccess,failure:@escaping IBCmdError) -> Self {
+//        cmdSuccess = success
+//        cmdFailure = failure
+//        IBBluetooth.shared.send(self)
+//        return self
+//    }
+}
+
+extension IBSender {
+    static var all: [IBSender] = []
+    
+    /// 从缓存实例集合中移除该实例
+    func remove() {
+        Self.all.removeAll { $0 === self }
+    }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSimulateParser.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBSimulateParser.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBluetooth.swift
new file mode 100644 (file)
index 0000000..2ba56a0
--- /dev/null
@@ -0,0 +1,389 @@
+//
+//  IBluetooth.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/2/4.
+//
+
+import Foundation
+import CoreBluetooth
+
+/// Max is 482
+let dataPacketMaxBytes = 450
+
+public class IBScanOption {
+    var values: [String : Any] = [:]
+    var serviceUUIDs: [String]? = nil
+    
+    /// 允许重复扫描
+    @discardableResult
+    public func allowDuplicates(_ value: Bool) -> Self {
+        values[CBCentralManagerScanOptionAllowDuplicatesKey] = value
+        return self
+    }
+    
+    /// 扫描指定服务
+    @discardableResult
+    public func solicited(_ serviceUUIDs: [String]) -> Self {
+        self.serviceUUIDs = serviceUUIDs
+        values[CBCentralManagerScanOptionSolicitedServiceUUIDsKey] = serviceUUIDs.map({ CBUUID(string: $0) })
+        return self
+    }
+    
+    public static let `default` = IBScanOption()
+    public static var custom: IBScanOption { IBScanOption() }
+}
+
+/// 主服务
+let serviceUUID: String = "18E0"
+/// Notify 特征值
+let notifyCharacteristicUUID: String = "2AE5"
+/// Read 特征值
+let readCharacteristicUUID: String = "2AE2"
+/// Write 特征值(发送字节数≤20)
+let writeCharacteristicUUID: String = "2AE3"
+/// Write 特征值(发送字节数>20)
+let writeBigCharacteristicUUID: String = "2AE4"
+/// Write 特征值(发送文件OAT、MP3等)
+let writeFileCharacteristicUUID: String = "2AE0"
+
+class IBBluetooth: NSObject {
+    static let shared = IBBluetooth()
+    
+    private override init() {
+        super.init()
+        centralManager = CBCentralManager(delegate: self, queue: nil)
+    }
+    
+    /// 2AE5
+    private var notifyCharacteristic: CBCharacteristic!
+    /// 2AE2
+    private var readCharacteristic: CBCharacteristic!
+    /// 2AE3
+    private var writeCharacteristic: CBCharacteristic!
+    /// 2AE4
+    private var writeBigCharacteristic: CBCharacteristic!
+    /// 2AE0
+    private var writeFileCharacteristic: CBCharacteristic!
+    
+    /// 中央管理类,管理蓝牙的扫描连接
+    var centralManager: CBCentralManager!
+    
+    /// 扫描到的蓝牙设备集合
+    var devices: [IBPeripheral] = []
+    
+    private var option: IBScanOption = .default
+    func scan(_ option: IBScanOption = .default) {
+        self.option = option
+        startScan()
+    }
+    
+    /// 重连接设备
+    func reconnected() {
+        guard let connectedPeripheral = centralManager.retrieveConnectedPeripherals(withServices: [CBUUID(string: serviceUUID)]).first else { return }
+        currentConnected = IBPeripheral()
+        currentConnected.peripheral = connectedPeripheral
+        centralManager.connect(connectedPeripheral)
+    }
+    
+    /// 当前连接设备
+    var currentConnected: IBPeripheral!
+    /// 最后消息发送实例
+    private weak var lastSender: IBSender?
+    
+    /// 开始扫描
+    private func startScan() {
+        if centralManager.state == .poweredOn {
+            Logger.log("开始扫描蓝牙设备...")
+        }
+        centralManager.scanForPeripherals(withServices: nil, options: option.values)
+    }
+    
+    /// 停止扫描
+    func stopScan(){
+        Logger.log("停止扫描蓝牙设备...")
+        centralManager.stopScan()
+    }
+    
+    /// 连接蓝牙设备
+    ///
+    /// - Parameter peripheral: 蓝牙设备对像
+    func connect(_ peripheral: IBPeripheral) {
+        currentConnected = peripheral
+        centralManager.connect(peripheral.peripheral)
+    }
+    
+    /// 断开与蓝牙设备的连接
+    ///  
+    /// - Parameter peripheral: 蓝牙设备对像
+    func cancel(_ peripheral: IBPeripheral) {
+        centralManager.cancelPeripheralConnection(peripheral.peripheral)
+    }
+    
+    /// 发送数据
+    /// - Parameters:
+    ///   - sender: 消息类
+    func send(_ sender: IBSender) {
+        if let _ = lastSender {
+            sender.remove()
+            Logger.log("有命令正在执行中...")
+            return
+        }
+        lastSender = sender
+        startTimer()
+        
+        let dataPacket = sender.dataPacket
+        Logger.log("\(sender.ability.characteristicUUID!) Write:", dataPacket.data.joined(" "), "Bytes(\(dataPacket.count))")
+        
+        var writeCharacteristic: CBCharacteristic = writeCharacteristic
+        if sender.ability.characteristicUUID == writeBigCharacteristicUUID {
+            writeCharacteristic = writeBigCharacteristic
+        }
+        if sender.ability.characteristicUUID == writeFileCharacteristicUUID {
+            writeCharacteristic = writeFileCharacteristic
+        }
+        
+        currentConnected.peripheral.writeValue(dataPacket.data, for: writeCharacteristic, type: .withResponse)
+    }
+    
+    func sendDeviceInfo(){
+//        IBNotify.post(.authStateDidChange, userInfo: ["state": 0])
+        
+        let mac = UserDefaults.standard.object(forKey: "IOTBletooth.mac") as? [Int] ?? []
+        let model = IBPhoneInfo()
+        model.mac = mac
+        IBSender()
+            .ability(IBAbility.sendPhone)
+            .parameters([.sendPhone(model)])
+            .response { result in
+                Logger.log(result)
+                guard let value = result.value as? Int else {return}
+                if value == 2 {
+                    //通知连接成功
+                    IBManager.completeHandler?(true)
+                    IBManager.completeHandler = nil
+                }
+                else if value == 1{
+                    //等待手表同意连接
+                    Logger.log("等待手表同意连接")
+                }
+                else if value == 3{
+                    //手表已与其他手机绑定
+                    IBManager.completeHandler?(false)
+                    IBManager.completeHandler = nil
+                }
+            }
+    }
+}
+
+extension IBBluetooth: CBCentralManagerDelegate {
+    
+    /// 手机蓝牙状态更新
+    /// - Parameter central: 中央管理类
+    func centralManagerDidUpdateState(_ central: CBCentralManager) {
+        switch central.state {
+        case .unknown:
+            Logger.log("unknown")
+        case .resetting:
+            Logger.log("resetting")
+        case .unsupported:
+            Logger.log("unsupported")
+        case .unauthorized:
+            Logger.log("unauthorized")
+        case .poweredOff:
+            Logger.log("poweredOff")
+        case .poweredOn:
+            startScan()
+            Logger.log("poweredOn")
+        @unknown default:
+            break
+        }
+    }
+    
+    /// 发现设备
+    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
+        
+        guard let manufacturerData = advertisementData["kCBAdvDataManufacturerData"] as? Data else { return }
+        guard let manufacturer = IBManufacturer(manufacturerData) else { return }
+        
+        let device = IBPeripheral()
+        device.code = manufacturer.code
+        device.isBind = manufacturer.isBind
+        device.rssi = RSSI.intValue
+        device.peripheral = peripheral
+        device.productID = manufacturer.productID
+        device.customerID = manufacturer.productID
+        device.advertisementData = advertisementData
+        device.macAddress = manufacturer.macAddress
+        device.softwareVersion = manufacturer.softwareVersion
+        IBManager.discoverHandler?(device)
+        Logger.log("发现设备", device.name, device.macAddress)
+    }
+    
+    /// 连接上设备
+    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
+        peripheral.delegate = self
+        peripheral.discoverServices([CBUUID(string: serviceUUID)])
+//        IBNotify.post(.connectedDeviceDidChange)
+        Logger.log("已连接", currentConnected.name)
+        IBNotify.post(.connectStateDidChange, object: 1)
+    }
+    
+    /// 连接失败
+    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
+        Logger.log("连接失败",error?.localizedDescription ?? "" )
+        currentConnected = nil
+        IBManager.completeHandler?(false)
+        IBManager.completeHandler = nil
+    }
+    
+    /// 断开连接
+    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
+        Logger.log("断开连接")
+        
+        deinitTimer()
+        lastSender?.completion?(.response.code(.failure))
+        lastSender?.remove()
+        
+        IBManager.completeHandler?(false)
+        IBManager.completeHandler = nil
+        IBNotify.post(.connectStateDidChange,object: 0)
+    }
+}
+
+extension IBBluetooth: CBPeripheralDelegate {
+    
+    /// 读取到信号
+    func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
+        
+    }
+    
+    /// 发现服务
+    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
+       peripheral
+            .services?
+            .forEach {
+                peripheral.discoverCharacteristics(nil, for: $0)
+            }
+    }
+    
+    /// 发现服务找那个的特征,然后遍历所有特征订阅
+    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
+        service
+            .characteristics?
+            .forEach {
+                if $0.uuid.uuidString == notifyCharacteristicUUID {
+                    notifyCharacteristic = $0
+                    peripheral.setNotifyValue(true, for: $0)
+                    return
+                }
+                
+                if $0.uuid.uuidString == readCharacteristicUUID {
+                    readCharacteristic = $0
+                    peripheral.setNotifyValue(true, for: $0)
+                    return
+                }
+                
+                if $0.uuid.uuidString == writeCharacteristicUUID {
+                    writeCharacteristic = $0
+                    return
+                }
+                
+                if $0.uuid.uuidString == writeBigCharacteristicUUID {
+                    writeBigCharacteristic = $0
+                    return
+                }
+                
+                if $0.uuid.uuidString == writeFileCharacteristicUUID {
+                    writeFileCharacteristic = $0
+                    return
+                }
+            }
+    }
+    
+    /// 来自蓝牙设备的通知
+    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
+        // 读取特征的值
+        peripheral.readValue(for: characteristic)
+    }
+    
+    /// 蓝牙设备发过来的数据
+    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
+        if let error = error {
+            Logger.log(error)
+        }
+        
+        guard let data = characteristic.value else { return }
+        if(data.count == 2 && data.int == 0){
+            Logger.log("收到0000指令异常")
+            return
+        }
+        
+        let mark = characteristic.isNotifying ? "Notify Read" : "Read"
+        let uuid = characteristic.uuid.uuidString
+        Logger.log(mark, uuid, "->", data.joined(" "), "Bytes(\(data.count))")
+        
+        if lastSender?.isAnswer(data) == true {
+            deinitTimer()
+            
+            let result = lastSender!.parse(data)
+            lastSender?.completion?(result)
+            lastSender?.remove()
+            return
+        }
+        
+        if characteristic.uuid.uuidString == readCharacteristicUUID  {
+            let info = IBWatchInfo(data)
+            currentConnected.battery = info.battery
+            currentConnected.macAddress = info.macAddress
+            currentConnected.productID = info.productID
+            currentConnected.customerID = info.customerID 
+            sendDeviceInfo()
+            return
+        }
+        
+//        if characteristic.uuid.uuidString == notifyCharacteristicUUID {
+//            Logger.log("Notify == ")
+//            return
+//        }
+        if IBNotify.handle(data) {
+//            Logger.log("通知消息")
+            return
+        }
+        
+        Logger.log("Other handle")
+    }
+    
+    /// 写入完成(发送指令完成)
+    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
+        if let error = error {
+            Logger.log(error)
+            return
+        }
+        
+        Logger.log(characteristic.uuid.uuidString, "write success")
+    }
+}
+
+extension IBBluetooth {
+    
+    static var timer: Timer?
+    func startTimer() {
+        deinitTimer()
+        
+        Self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false, block: { [unowned self] _ in
+            deinitTimer()
+            lastSender?.completion?(.response.code(.timeout))
+            lastSender?.remove()
+        })
+        Logger.log("创建定时器")
+    }
+    
+    func deinitTimer() {
+        guard let _ = Self.timer else { return }
+        Self.timer?.invalidate()
+        Self.timer = nil
+        
+        Logger.log("销毁定时器")
+    }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/IOTBluetooth.h b/IOTBluetooth/IOTBluetooth/IOTBluetooth/IOTBluetooth.h
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBAbout.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBAddressBook.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBAddressBook.swift
new file mode 100644 (file)
index 0000000..fef7b43
--- /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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBBloodOxygen.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBClock.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBClock.swift
new file mode 100644 (file)
index 0000000..7b12f33
--- /dev/null
@@ -0,0 +1,46 @@
+//
+//  IBClock.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/3/21.
+//
+
+import Foundation
+
+/// 闹钟模型
+public class IBClock: Codable {
+    /// 闹钟列表
+    public var alarms: [IBClockInfo] = []
+
+    public init() { }
+}
+
+/// 闹钟信息模型
+public class IBClockInfo: Codable {
+    /// 闹钟id 范围为0~4. (即闹钟暂定最多设置为5个)
+    public var id: Int = 0
+    /// 小时
+    public var hour: Int = 0
+    /// 分钟
+    public var minute: Int = 0
+    /// 0关闭闹钟,1开启闹钟
+    public var enable: Int = 0
+    /// 闹钟重复列表
+    ///
+    /// - 0 不重复,响一次
+    /// - 1 周一
+    /// - 2 周二
+    /// - ......
+    /// - 7 周日
+    public var `repeat`: [Int] = [0]
+
+//    enum CodingKeys: String, CodingKey {
+//        case id = "id"
+//        case hour = "hour"
+//        case minute = "minute"
+//        case enable = "enable"
+//        case `repeat` = "repeat"
+//    }
+    
+    public init() { }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBCode.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBCode.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHeartRate.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHeartRate.swift
new file mode 100644 (file)
index 0000000..554758d
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  IBHeartRate.swift
+//  IOTBluetooth
+//
+//  Created by incar on 2024/5/16.
+//
+
+import Foundation
+/// 心率设置模型
+public class IBHeartRate {
+    /// 监测频率
+    public var rate: IBHeartRateFrequency = .none
+    /// 心率提醒 0:关闭 1:开启。 为0时后面区间阈值无需设置。
+    public var state: Int = 1
+    /// 心率区间阈值 高于或低于 0:低于 1:高于
+    public var higherOrLower: Int = 0
+    /// 心率区间阈值 30~200
+    public var value: Int = 100
+    
+    public init() { }
+    
+    init(_ data: Data) {
+        rate = IBHeartRateFrequency(rawValue:data[0].int) ?? .none
+        state = data[1].int
+        if state == 1 {
+            higherOrLower = data[2].int
+            value = data[3].int
+        }
+    }
+ 
+    var description: String  {
+        return "IBHeartRate(rate: \(rate), state:\(state), higherOrLower:\(higherOrLower), value:\(value))"
+    }
+}
+/// 提醒频率
+public enum IBHeartRateFrequency: Int {
+    /// 关闭
+    case none = 0
+    /// 1分钟
+    case min1
+    /// 5分钟
+    case min5
+    /// 10分钟
+    case min10
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBaseTraining.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBaseTraining.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBloodOxygen.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryBurning.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryCalorie.swift
new file mode 100644 (file)
index 0000000..b6e2e87
--- /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[0...3].int
+                model.value = data[i+4...i+7].int
+                historyList.append(model)
+            }
+        }
+        list = historyList
+    }
+}
+
+/// 卡路里历史记录
+public class IBHistoryCalorieItem {
+    /// 时间戳
+    public var time: Int = 0
+    /// 单位kcal
+    public var value: Int = 0
+    
+    public init() { }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryDistance.swift
new file mode 100644 (file)
index 0000000..0c876bd
--- /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].int
+                historyList.append(model)
+            }
+        }
+        list = historyList
+    }
+}
+
+/// 距离历史记录
+public class IBHistoryDistanceItem {
+    /// 时间戳
+    public var time: Int = 0
+    /// 单位km
+    public var value: Int = 0
+    
+    public init() { }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryGps.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryGps.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryHeartRate.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistorySleep.swift
new file mode 100644 (file)
index 0000000..ebf1904
--- /dev/null
@@ -0,0 +1,86 @@
+//
+//  IBHistorySleep.swift
+//  IOTBluetooth
+//
+//  Created by incar on 2024/5/30.
+//
+
+import Foundation
+/// 睡眠历史模型
+ class IBHistorySleep {
+    /// 记录
+    var list:[IBHistorySleepItem] = []
+        
+    init(_ data: Data) {
+        if(data.count <= 0){
+            return
+        }
+        
+        var historyList:[IBHistorySleepItem] = []
+        let number = data[0].int
+        
+        
+        for i in stride(from: 0, to: data.count, by: 8){
+            if data.count > i + 7 {
+                let model = IBHistorySleepItem()
+//                model.time = data[i...i+3].int
+//                model.value = data[i+4...i+7].int
+//                historyList.append(model)
+            }
+        }
+        list = historyList
+    }
+}
+
+/// 睡眠历史记录
+public class IBHistorySleepItem {
+    /// 睡眠ID,0~255,表示第几次睡眠
+    public var sleepId: UInt8 = 0
+    /// 睡眠类型,0:小睡(无总时长之后的所有项目)1:睡眠
+    public var type: Int = 0
+    /// 入睡时间戳,Unix时间戳
+    public var startTime: Int = 0
+    /// 出睡时间戳,Unix时间戳
+    public var endTime: Int = 0
+    /// 总时长
+    public var totalSleep: IBSleepTime = IBSleepTime(hour: 0, minute: 0)
+    /// 深睡时长
+    public var deepSleep: IBSleepTime = IBSleepTime(hour: 0, minute: 0)
+    /// 浅睡时长
+    public var lightSleep: IBSleepTime = IBSleepTime(hour: 0, minute: 0)
+    /// 快速动眼时长
+    public var remSleep: IBSleepTime = IBSleepTime(hour: 0, minute: 0)
+    /// 清醒时长
+    public var awake: IBSleepTime = IBSleepTime(hour: 0, minute: 0)
+    /// 睡眠状态
+    public var states:[IBSleepState] = []
+
+
+    public init() { }
+}
+
+/// 时间
+public struct IBSleepTime {
+    /// 时
+    public var hour: UInt8
+    /// 分 0~59
+    public var minute: UInt8
+    
+    public init(hour: UInt8, minute: UInt8) {
+        self.hour = hour
+        self.minute = minute
+    }
+    
+    var description: String  {
+        return "IBSleepTime(hour: \(hour), minute:\(minute)"
+    }
+}
+
+
+/// 睡眠状态
+public class IBSleepState {
+    /// Unix时间戳
+    public var time: Int32 = 0
+    /// 状态,0:进入睡眠   1:浅层睡眠   2:深层睡眠   3:清醒   4:结束睡眠   7:未出睡最后一笔睡眠数据(资料上报结束标志)   9:出睡最后一笔睡眠数据(资料上报结束标志)   12:快速眼动   13:取消入睡通知(睡眠不足 60 分钟出睡上报状态)   14:结束小睡
+    public var state: Int8 = 0
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryStand.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryStand.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBHistoryStep.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBLanguage.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBManufacturer.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBOtherTarget.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPeripheral.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPeripheral.swift
new file mode 100644 (file)
index 0000000..02fd72c
--- /dev/null
@@ -0,0 +1,55 @@
+//
+//  IBPeripheral.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/3/6.
+//
+
+import Foundation
+import CoreBluetooth
+
+public class IBPeripheral {
+    
+    /// 信号强度(分贝)
+    public internal(set) var rssi: Int = 0
+    
+    /// 外围设备实例
+    public internal(set) var peripheral: CBPeripheral! {
+        didSet {
+            if let name = peripheral.name { 
+                self.name = name
+            }
+            uuid = peripheral.identifier
+        }
+    }
+    
+    /// mac 地址
+    public internal(set) var macAddress: String = ""
+    
+    /// 广播数据
+    public internal(set) var advertisementData: [String: Any] = [:]
+    
+    /// CBPeripheral.identifier
+    public private(set) var uuid: UUID!
+    
+    /// 名称
+    public private(set) var name: String = "Unknown"
+    
+    /// 软件版本
+    public internal(set) var softwareVersion: String = ""
+    
+    /// 电量,连接设备后可用,默认为 0
+    public internal(set) var battery: Int = 0
+    
+    /// 识别符
+    var code: String = ""
+    
+    /// 是否绑定(1=绑定)
+    var isBind: Int = 0
+    
+    /// 客户 ID
+    var customerID: Int = 0
+    
+    /// 产品ID
+    var productID: Int = 0
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPhoneInfo.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBPhoneInfo.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBResponse.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBScreen.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBScreen.swift
new file mode 100644 (file)
index 0000000..26d3cbd
--- /dev/null
@@ -0,0 +1,46 @@
+//
+//  IBScreen.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/3/21.
+//
+
+import Foundation
+
+/// 屏幕参数
+public class IBScreen {
+    /// 亮度 [0, 255]
+    public var brightness: Int = 10
+    
+    /// 亮屏时长
+    public var duration: IBDuration = .second10
+    
+    /// 抬腕亮屏(0=关闭, 1=开启)
+    public var wrist: Int = 1
+    
+    public init() { }
+    
+    init(_ data: Data) {
+        brightness = data[0].int
+        duration = IBDuration(rawValue: data[1].int) ?? .second5
+        wrist = data[2].int
+    }
+    var description: String  {
+        return "IBScreen(brightness: \(brightness), duration:\(duration), wrist:\(wrist))"
+    }
+}
+
+public enum IBDuration: Int {
+    /// 5 秒
+    case second5 = 0
+    /// 10 秒
+    case second10
+    /// 15 秒
+    case second15
+    /// 30 秒
+    case second30
+    /// 1 分钟
+    case minute
+    /// 5 分钟
+    case minute5
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBSendTime.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBSendTime.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBSleep.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTargetStep.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTarinState.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTrainTarget.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBTrainingData.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBUserInfo.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBVibrateSound.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBVibrateSound.swift
new file mode 100644 (file)
index 0000000..802fdc8
--- /dev/null
@@ -0,0 +1,39 @@
+////
+////   IBVibrateSound.swift
+////  IOTBluetooth
+////
+////  Created by incar on 2024/5/9.
+////
+//
+/// 声音震动模型
+public class IBVibrateSound {
+    /// 系统音量调节0~100
+    public var systemValue: Int = 0
+    
+    /// 铃声音量调节0~100
+    public var ringsValue: Int = 0
+    
+    /// 媒体音量调节0~100
+    public var mediaValue: Int = 0
+ 
+    /// 铃声开关0:关闭 1:开启
+    public var ringsState: Int = 0
+    
+    /// 震动开关0:关闭 1:开启
+    public var vibrateState: Int = 0
+    
+    public init() { }
+    
+    init(_ data: Data) {
+        systemValue = data[0].int
+        ringsValue = data[1].int
+        mediaValue = data[2].int
+        ringsState = data[3].int
+        vibrateState = data[4].int
+    }
+    
+    var description: String  {
+        return "IBVibrateSound(systemValue: \(systemValue), ringsValue:\(ringsValue), mediaValue:\(mediaValue), ringsState:\(ringsState), vibrateState:\(vibrateState))"
+    }
+}
+
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWatchInfo.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWatchInfo.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWater.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Models/IBWeather.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Any.swift
new file mode 100644 (file)
index 0000000..64e712f
--- /dev/null
@@ -0,0 +1,181 @@
+//
+//  Any.swift
+//  MyBase
+//
+//  Created by Harvey on 2021/6/2.
+//
+
+import Foundation
+
+/// 判断可选类型是否为 nil
+func isNil(_ any: Any?) -> Bool {
+    switch any {
+    case .none: return true
+    case .some: return false
+    }
+}
+
+/// 判断可选类型是否有值
+func isValue(_ any: Any?) -> Bool {
+    return isNil(any) == false
+}
+
+/// for Array
+func isEmpty(_ value: [Any]?) -> Bool {
+    return isNil(value) || value!.count == 0
+}
+
+/// for Array
+func isNotEmpty(_ value: [Any]?) -> Bool {
+    return isEmpty(value) == false
+}
+
+/// for Dictionary
+func isEmpty(_ value: [AnyHashable: Any]?) -> Bool {
+    return isNil(value) || value!.count == 0
+}
+
+/// for Dictionary
+func isNotEmpty(_ value: [AnyHashable: Any]?) -> Bool {
+    return isEmpty(value) == false
+}
+
+/// for String
+func isEmpty(_ value: String?) -> Bool {
+    return isNil(value) || value!.count == 0
+}
+
+/// for String
+func isNotEmpty(_ value: String?) -> Bool {
+    return isEmpty(value) == false
+}
+
+public extension String {
+    
+    /// 补足8位(长度为8的整数倍)
+    var binary: String {
+        let bytes = (Double(count) / 8.0).upInt
+        let offset = bytes * 8 - count
+        let prefix = Array(repeating: "0", count: offset).joined()
+        return prefix + self
+    }
+}
+
+public extension Double {
+    var upInt: Int { Int(ceil(self)) }
+}
+
+public extension Int {
+    private static let binaryPack = 0b11111111
+    
+    /// convert to data with byte (max is 4294967295)
+    ///
+    /// 低位 --> 高位
+    var bytes: [UInt8] {
+        var mutatingSelf = self
+        var bytes = [UInt8(mutatingSelf & Self.binaryPack)]
+        
+        var count = 1
+        mutatingSelf = mutatingSelf >> (count * 8)
+        while mutatingSelf > 0 {
+            bytes.append(UInt8(mutatingSelf & Self.binaryPack))
+            mutatingSelf = mutatingSelf >> (count * 8)
+            count = count + 1
+        }
+        return bytes
+    }
+    
+    /// convert to data with 1 byte
+    var byte: UInt8 {
+        UInt8(self & Self.binaryPack)
+    }
+    
+    /// convert to data with 2 byte
+    ///
+    /// 低位 --> 高位
+    var byte2: [UInt8] {
+        [
+            UInt8(self & Self.binaryPack),
+            UInt8(self >> 8 & Self.binaryPack)
+        ]
+    }
+    
+    /// convert to data with 3 byte
+    ///
+    /// 低位 --> 高位
+    var byte3: [UInt8] {
+        [
+            UInt8(self & Self.binaryPack),
+            UInt8(self >> 8 & Self.binaryPack),
+            UInt8(self >> 16 & Self.binaryPack)
+        ]
+    }
+    
+    /// convert to data with 4 byte
+    ///
+    /// 低位 --> 高位
+    var byte4: [UInt8] {
+        [
+            UInt8(self & Self.binaryPack),
+            UInt8(self >> 8 & Self.binaryPack),
+            UInt8(self >> 16 & Self.binaryPack),
+            UInt8(self >> 24 & Self.binaryPack)
+        ]
+    }
+}
+
+public extension BinaryInteger {
+    
+    /// e.g. 0000100110010101
+    var binaryString: String {
+        .init(self, radix: 2).binary
+    }
+}
+
+public extension Array where Element == UInt8 {
+    
+    var int: Int {
+        self
+            .map { Int($0) }
+            .enumerated()
+            .map { $1 << ($0 * 8) }
+            .reduce(0, { partialResult, value in
+                partialResult | value
+            })
+    }
+    
+    var data: Data { Data(self) }
+}
+
+public extension Data {
+    var bytes: [UInt8] { map { $0 } }
+    
+    func joined(format: String = "%02x", _ separator: String = "") -> String {
+        self
+            .map { String(format: format, $0) }
+            .joined(separator: separator)
+    }
+    
+    func reversedJoined(format: String = "%02x", _ separator: String = "") -> String {
+        self
+            .map { String(format: format, $0) }
+            .reversed()
+            .joined(separator: separator)
+    }
+    
+    var int: Int {
+        self
+            .map { Int($0) }
+            .enumerated()
+            .map { $1 << ($0 * 8) }
+            .reduce(0, { partialResult, value in
+                partialResult | value
+            })
+    }
+    
+    var lrc: UInt8 { LRC.calculateLRC(bytes) }
+}
+
+public extension UInt8 {
+    var int: Int { Int(self) }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/CRC16.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/CRC16.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Date.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Date.swift
new file mode 100644 (file)
index 0000000..9fc933d
--- /dev/null
@@ -0,0 +1,145 @@
+//
+//  Date.swift
+//  MyBase
+//
+//  Created by Harvey on 2021/5/18.
+//
+
+import Foundation
+
+// MARK: - Extension Date
+extension Date {
+    
+    static let formatter = DateFormatter()
+    
+    struct Format {
+        
+        let rawValue: String
+        init(_ rawValue: String) {
+            self.rawValue = rawValue
+        }
+        
+        /// e.g. 01:01:01
+        static let time: Format = .init("HH:mm:ss")
+        
+        /// e.g. 01:01
+        static let shortTime: Format = .init("HH:mm")
+        
+        /// e.g. 01-01
+        static let date: Format = .init("MM-dd")
+        
+        /// e.g. 01月01日
+        static let date1: Format = .init("MM月dd日")
+        
+        /// e.g. 2020-01-01
+        static let short: Format = .init("yyyy-MM-dd")
+        
+        /// e.g. 2020年01月01日
+        static let short1: Format = .init("yyy年MM月dd日")
+        
+        /// e.g. 2020-01-01 01:01
+        static let middle: Format = .init("yyyy-MM-dd HH:mm")
+        
+        /// e.g. 2020年01月01日 01:01
+        static let middle1: Format = .init("yyy年MM月dd日 HH:mm")
+        
+        /// e.g. 2020-01-01 01:01:01
+        static let long: Format = .init("yyyy-MM-dd HH:mm:ss")
+        
+        /// e.g. 2020年01月01日 01:01:01
+        static let long1: Format = .init("yyyy年MM月dd日 HH:mm:ss")
+        
+        /// e.g. 2020-01-01 01:01:01.088
+        static let full: Format = .init("yyyy-MM-dd HH:mm:ss.SSS")
+        
+        /// e.g. 2020年01月01日 01:01:01.088
+        static let full1: Format = .init("yyyy年MM月dd日 HH:mm:ss.SSS")
+    }
+    
+    /// 格式化成本地时间
+    func format(_ format: Date.Format = .middle) -> String {
+        Self.formatter.dateFormat = format.rawValue
+        return Self.formatter.string(from: self)
+    }
+    
+    enum Unit {
+        case minute(Int)
+        case hour(Int)
+        case day(Int)
+        
+        /// 转换成秒
+        var second: TimeInterval {
+            switch self {
+            case .minute(let minute): return 60.0 * Double(minute)
+            case .hour(let hour): return 60.0 * 60.0 * Double(hour)
+            case .day(let day): return 60.0 * 60.0 * 24.0 * Double(day)
+            }
+        }
+    }
+}
+
+let today: Date = .init()
+
+/// 本地化
+extension Date {
+    /// 星期几
+    enum Week: Int {
+        /// 星期日
+        case sun = 1
+        /// 星期一
+        case mon
+        /// 星期二
+        case tue
+        /// 星期三
+        case wed
+        /// 星期四
+        case thur
+        /// 星期五
+        case fri
+        /// 星期六
+        case sat
+    }
+    
+//    /// 当天的第一秒
+//    var fromTimeInterval: TimeInterval {
+//        let today = format(.short)
+//        return today.timeIntervalSince1970(.short)
+//    }
+//    
+//    /// 当天最后一秒
+//    var toTimeInterval: TimeInterval { fromTimeInterval + Unit.day(1).second }
+    
+    /// 哪一年
+    var year: Int { component(.year) }
+    
+    /// 哪个月
+    var month: Int { component(.month) }
+    
+    /// 当月的第几天
+    var day: Int { component(.day) }
+    
+    /// 当月的第几天
+    var hour: Int { component(.hour)}
+    
+    /// 当月的第几天
+    var minute: Int { component(.minute)}
+    
+    /// 当月的第几天
+    var second: Int { component(.second)}
+    
+    /// 当月的第几周
+    var  weekOfMonth: Int { component(.weekOfMonth) }
+    
+    /// 今年的第几周
+    var  weekOfYear: Int { component(.weekOfYear) }
+    
+    /// 星期几
+    var weekDay: Week { Week(rawValue: component(.weekday))! }
+    
+    /// 假设当天是周五,表示本月的第几个周五
+    var weekdayOrdinal: Int { component(.weekdayOrdinal) }
+    
+    private func component(_ component: Calendar.Component) -> Int {
+        return Calendar.autoupdatingCurrent.component(component, from: self)
+    }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/IBPeripheralUUID.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/IBPeripheralUUID.swift
new file mode 100644 (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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/JSON.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/LRC.swift b/IOTBluetooth/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/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Logger.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/Logger.swift
new file mode 100644 (file)
index 0000000..1236c7f
--- /dev/null
@@ -0,0 +1,33 @@
+//
+//  Logger.swift
+//
+//
+//  Created by Harvey on 2021/4/27.
+//
+
+import Foundation
+
+/// 日记管理
+class Logger {
+    static var isDebug: Bool = false
+    
+    fileprivate static let shared = Logger()
+    private init() { }
+    
+    fileprivate var dateString: String { Date().format(.full) }
+    
+    static var logs: [String] = []
+}
+
+extension Logger {
+    static func log(_ logs: Any..., line: Int = #line, separator: String = " ", terminator: String = "\n") {
+        objc_sync_enter(Logger.shared)
+        if Logger.isDebug {
+            print(Logger.shared.dateString, terminator: " [IOTBluetooth-\(String(format: "%03d", line))]: ")
+            print(logs, separator: separator, terminator: terminator)
+            let logString = Logger.shared.dateString + " [IOTBluetooth-\(String(format: "%03d", line))]: " + logs.map({ "\($0)" }).joined(separator: separator) + terminator
+            Self.logs.append(logString)
+        }
+        objc_sync_exit(Logger.shared)
+    }
+}
diff --git a/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/UTF8.swift b/IOTBluetooth/IOTBluetooth/IOTBluetooth/Tools/UTF8.swift
new file mode 100644 (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) }
+}