--- /dev/null
+//
+// 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()
+ }
+ }
+}
--- /dev/null
+//
+// 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
+ }
+}
+
--- /dev/null
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
--- /dev/null
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
--- /dev/null
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
--- /dev/null
+<?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>
--- /dev/null
+<?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="{"pn":"Frank的iPhone","tp":1,"mac": []}" 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>
--- /dev/null
+//
+// 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)")
+ }
+}
--- /dev/null
+//
+// 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
+ }
+}
--- /dev/null
+//
+// 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)]),
+]
--- /dev/null
+<?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>
--- /dev/null
+//
+// 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")
+ }
+}
+
--- /dev/null
+//
+// 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)
+ }
+}
--- /dev/null
+// !$*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 */;
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "self:">
+ </FileRef>
+</Workspace>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<Bucket
+ uuid = "3E7E940C-0C97-40F4-A990-DDBAB66B37E1"
+ type = "1"
+ version = "2.0">
+</Bucket>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+//
+// 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)
+}
--- /dev/null
+//
+// 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
+ }
+}
+
--- /dev/null
+//
+// 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) }
+ }
+}
--- /dev/null
+//
+// 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}
+}
--- /dev/null
+//
+// 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
+ }
+ }
+}
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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)
+ }
+}
--- /dev/null
+//
+// 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 }
+ }
+}
--- /dev/null
+//
+// 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)
+// }
+//}
--- /dev/null
+//
+// 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("销毁定时器")
+ }
+}
--- /dev/null
+//
+// 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>
+
+
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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` ?? ""))"
+ }
+}
+
+
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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 "未知错误"
+ }
+ }
+
+}
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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
+//}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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))"
+ }
+}
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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
+ }
+}
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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)"
+ }
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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))"
+ }
+}
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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))"
+ }
+}
--- /dev/null
+//
+// 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))"
+ }
+}
--- /dev/null
+////
+//// 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))"
+ }
+}
+
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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) }
+}
--- /dev/null
+//
+// 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
+ }
+}
--- /dev/null
+//
+// 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)
+ }
+}
--- /dev/null
+//
+// 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
+}
--- /dev/null
+//
+// 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
+ }
+}
--- /dev/null
+//
+// 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
+ }
+}
+
+
--- /dev/null
+//
+// 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)
+ }
+}
--- /dev/null
+//
+// 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) }
+}