--- /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()
+
+ navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Log", style: .plain, target: self, action: #selector(showLog))
+ }
+
+ @objc func showLog(){
+ let vc = storyboard!.instantiateViewController(identifier: "LogViewController")
+ navigationController?.pushViewController(vc, animated: true)
+ }
+}
+
+extension AbilityViewController: UITableViewDelegate, UITableViewDataSource {
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ datas.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let data = datas[indexPath.row]
+ let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+ let label = cell.viewWithTag(10) as! UILabel
+ label.text = "\(indexPath.row) " + data.ability.description + " \(data.label)"
+ return cell
+ }
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+ let data = datas[indexPath.row]
+ if data.label.isEqual("发送OTA文件"){
+ self.sendOta()
+ }
+ else if data.label.isEqual("发送MP3文件"){
+ self.sendMp3()
+ }
+ else if data.label.isEqual("发送EPO文件"){
+ self.sendEpo()
+ }
+ else if data.label.isEqual("发送自定义表盘文件"){
+ self.sendCustomDial()
+ }
+ else if data.label.isEqual("发送市场表盘文件"){
+ self.sendMarketDial()
+ }
+ else if data.label.isEqual("撤销当前文件传输"){
+ self.revokeFileSend()
+ }
+ else{
+ activityIndicatorView.startAnimating()
+ IBSender()
+ .ability(data.ability)
+ .parameters(data.parameters)
+ .response {[weak self] result in
+ guard let self = self else {return}
+ print(result)
+ activityIndicatorView.stopAnimating()
+ }
+ }
+ }
+}
+extension AbilityViewController{
+ func sendOta(){
+ guard let path = Bundle.main.path(forResource: "20240604_155433_2_V1.0.1_20240618_113111_ota", ofType: "fwpkg") else {return}
+ let info = IBSendOta()
+ info.major = 2
+ info.mid = 1
+ info.minor = 4
+ info.isForced = 0
+ info.filePath = path
+
+ activityIndicatorView.startAnimating()
+ IBFileSender().ota(info).progress({ total, current in
+// print("\(total) : \(current)")
+ }).response {[weak self] result in
+ print(result)
+ self?.activityIndicatorView.stopAnimating()
+ }
+ }
+ func sendMp3(){
+ guard let path = Bundle.main.path(forResource: "大约在冬季", ofType: "mp3") else {return}
+ let info = IBSendMp3()
+ info.name = "大约在冬季.mp3"
+ info.filePath = path
+
+ activityIndicatorView.startAnimating()
+ IBFileSender().mp3(info).progress({ total, current in
+ print("\(total) : \(current)")
+ }).response {[weak self] result in
+ print(result)
+ self?.activityIndicatorView.stopAnimating()
+ }
+ }
+ func sendEpo(){
+
+ }
+ func sendCustomDial(){
+ //自定义表盘目前硬件端还不支持
+// guard let path = Bundle.main.path(forResource: "galaxy", ofType: "lzma") else {return}
+// let file = IBDialFile()
+// file.filePath = path
+// file.fileName = "galaxy.lzma"
+//
+// let info = IBDial()
+// info.UID = 0
+// info.files = [file]
+//
+// info.batteryStyle = 1
+// info.timeStyle = 1
+// info.timeColor = 1
+//
+// activityIndicatorView.startAnimating()
+// IBFileSender().dial(info).progress({ total, current in
+// print("\(total) : \(current)")
+// }).response {[weak self] result in
+// print(result)
+// self?.activityIndicatorView.stopAnimating()
+// }
+ }
+
+ //目前只支持表盘市场
+ func sendMarketDial(){
+//系统表盘
+// ID: 101 | Name: sportdial
+// ID: 102 | Name: kaleidoscope
+// ID: 1052 | Name: classic
+// ID: 103 | Name: jellyfish
+// ID: 1028 | Name: KUMI
+
+ guard let path = Bundle.main.path(forResource: "galaxy", ofType: "lzma") else {return}
+ let file = IBDialFile()
+ file.filePath = path
+ file.fileName = "galaxy.lzma"
+
+ let info = IBDial()
+ info.UID = 10002
+ info.files = [file]
+
+ info.theme = "黑暗"
+
+// activityIndicatorView.startAnimating()
+ IBFileSender().dial(info).progress({ total, current in
+ print("\(total) : \(current)")
+ }).response {[weak self] result in
+ print(result)
+// self?.activityIndicatorView.stopAnimating()
+ }
+ }
+
+ func revokeFileSend(){
+ IBFileSender().revokeFileSend(){ result in
+ print(result)
+ }
+ }
+}
--- /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.
+ HsFileAgentSdk.shared.isDebug = true
+ HsFileAgentSdk.shared.initSDK()
+
+ 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
+//
+// Datas.swift
+// Example
+//
+// Created by yaozuopan on 2024/3/19.
+//
+
+import Foundation
+import IOTBluetooth
+
+
+
+let datas: [(ability: IBAbility, label: String, parameters: [IBParameter])] = [
+ (.userData,"设置用户数据",[.userInfo(IBUserInfo())]),
+ (.language, "设置语言", [.language(.chinese)]),
+ (.about,"关于本机内容",[]),
+ (.find, "开始查找手表", [.action(1)]),
+ (.find, "停止查找手表", [.action(0)]),
+ (.message, "开启消息推送功能", [.action(1)]),
+ (.message, "关闭消息推送功能", [.action(0)]),
+
+ (.takePhoto, "开启手势拍照", [.action(1)]),
+ (.takePhoto, "关闭手势拍照", [.action(0)]),
+ (.restore, "恢复出厂设置", []),
+ (.reboot, "重新启动", []),
+ (.shutdown, "关机", []),
+ (.fetchScreen, "获取手表屏幕参数", []),
+ (.screen, "设置手表屏幕参数", [.screen(Config.setScreen())]),
+ (.fetchBattery, "获取电量", []),
+ (.fetchClock, "获取闹钟设置", []),
+ (.setClock, "发送闹钟设置", [.clock(IBClock())]),
+
+ (.fetchCurrentDialId, "发送当前表盘ID", []),
+ (.switchDial, "切换表盘", [.dialID(1028)]),
+ (.deleteDial, "删除表盘", [.dialID(10001)]),
+ (.fetchDials, "获取所有表盘UID", []),
+ (.checkDialID, "获取某表盘ID是否已安装", [.dialID(10001)]),
+
+ (.fetchWatchState, "获取当前手表状态", []),
+ (.fetchVibrateSound, "获取手表声音与震动参数", []),
+ (.vibrateSound, "设置手表声音与震动参数", [.vibrateSound(Config.setVibrateSound())]),
+ (.fetchAuxiliary, "获取手表辅助功能参数", []),
+ (.auxiliary, "设置手表辅助功能参数", [.auxiliary(.bloodOxygen)]),
+ (.unit, "设置手表公英制单位", [.value(1)]),
+
+
+ (.disk, "获取SD卡剩余空间", []),
+ (.unpair, "发送解除配对消息", []),
+ (.sendUnpair, "发送取消解除配对消息", []),
+
+
+ (.startSport, "开启指定运动模式", [.motionMode(.walking)]),
+ (.trainPauseResume, "训练暂停", [.action(0)]),
+ (.trainPauseResume, "训练恢复", [.action(1)]),
+ (.endSport, "结束运动模式", []),
+
+ (.heartRateMode, "开启自动获取实时心率", [.action(1)]),
+ (.heartRateMode, "关闭自动获取实时心率", [.action(0)]),
+ (.heartRateHigh, "设置最高心率提醒", [.value(180)]),
+ (.heartRateHigh, "关闭最高心率提醒", [.value(0)]),
+ (.fetchHeartRate, "获取手表心率参数", []),
+ (.heartRate, "设置手表心率参数", [.heartRate(IBHeartRate())]),
+ (.currentHeartRate, "获取实时心率", []),
+
+ (.fetchBloodOxygen, "获取手表血氧设置", []),
+ (.bloodOxygen, "设置手表血氧参数", [.bloodOxygen(IBBloodOxygen())]),
+ (.trainFrequency, "设置训练实时数据采样频率", [.value(30)]),
+
+ (.fetchTargetStep, "获取目标步数设置", []),
+ (.targetStep, "设置目标步数", [.targetStep(IBTargetStep())]),
+ (.fetchOtherTarget, "获取其他目标设置", []),
+ (.targetOther, "设置其他目标", [.otherTarget(IBOtherTarget())]),
+ (.trainTarget, "设置训练目标", [.trainTarget(IBTrainTarget())]),
+ (.fetchSleep, "获取睡眠设置", []),
+ (.sleep, "设置睡眠", [.sleep(IBSleep())]),
+ (.fetchWater, "获取手表喝水提醒设置", []),
+ (.water, "设置喝水提醒", [.water(IBWater())]),
+
+
+ (.syncHeartRate, "同步心率历史数据", [.value(0)]),
+ (.syncBloodOxygen, "同步血氧历史数据", [.value(0)]),
+ (.syncStepCount, "同步历史步数数据", [.value(0)]),
+ (.syncFatBurning, "同步燃脂时间历史数据", [.value(0)]),
+ (.syncStand, "同步站立历史数据", [.value(0)]),
+ (.syncCalorie, "同步卡路里历史数据", [.value(0)]),
+ (.syncDistance, "同步距离历史数据", [.value(0)]),
+ (.syncTrain, "同步基础训练历史数据", [.value(0)]),
+ (.syncSleep, "同步睡眠历史数据", [.value(0)]),
+ (.syncGPS, "同步路径坐标集历史数据", [.value(1654012800)]),
+ (.sendSyncEnd, "发送同步数据结束消息", []),
+ (.addressBook, "发送通讯录", [.addressBook([IBAddressBook()])]),
+
+// (.epo, "发送EPO文件", []),
+ (.ota, "发送OTA文件", []),
+ (.mp3, "发送MP3文件", []),
+// (.dial, "发送自定义表盘文件", []),
+ (.dial, "发送市场表盘文件", []),
+ (.revokeFileSend, "撤销当前文件传输", []),
+
+
+ (.deleteFile, "删除指定文件", [.deleteFile(IBDeleteFile())]),
+ (.deleteFiles, "删除指定类型所有文件", [.file(.mp3)]),
+ (.fetchFiles, "获取指定类型所有文件", [.file(.mp3)]),
+
+ (.sendWeather, "发送天气数据", [.weather(IBWeather())]),
+]
+
+class Config{
+ static func setScreen()->IBScreenConfig{
+ let model = IBScreenConfig()
+ model.type = 0
+ model.brightness = 255
+
+// model.type = 1
+// model.duration = .minute
+// model.wrist = 0
+ return model
+ }
+ static func setVibrateSound()->IBVibrateConfig{
+ let model = IBVibrateConfig()
+// model.type = 0
+// model.systemValue = 92
+
+// model.type = 1
+// model.ringsValue = 48
+//
+ model.type = 3
+ model.ringsState = 1
+ model.vibrateState = 1
+ return model
+ }
+}
--- /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>
+ <key>NSAppTransportSecurity</key>
+ <dict>
+ <key>NSAllowsArbitraryLoads</key>
+ <true/>
+ </dict>
+</dict>
+</plist>
--- /dev/null
+//
+// LogViewController.swift
+// Example
+//
+// Created by yaozuopan on 2024/3/14.
+//
+
+import UIKit
+import IOTBluetooth
+import IOTBluetooth
+
+class LogViewController: BaseViewController {
+
+ @IBOutlet weak var textView: UITextView!
+
+ override func viewDidLoad() {
+ title = "蓝牙日志"
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+ textView.text = HsFileAgentSdk.shared.logs.joined(separator: "\n")
+
+ let range = NSMakeRange(textView.text.count - 1, 1)
+ textView.scrollRangeToVisible(range)
+ }
+}
+
--- /dev/null
+//
+// ViewController.swift
+// Example
+//
+// Created by yaozuopan on 2024/3/11.
+//
+
+import UIKit
+import IOTBluetooth
+import CoreBluetooth
+
+class ViewController: BaseViewController {
+
+ @IBOutlet weak var listView: UITableView!
+ private var dataList: [CBPeripheral] = []
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ title = "IOTBluetooth"
+ navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Log", style: .plain, target: self, action: #selector(showLog))
+ title = "扫描"
+ }
+
+
+ @IBAction func startScan(_ sender: UIButton) {
+ IBBluetoothManager.shared.startScan()
+ IBBluetoothManager.shared.discoverCallBack = { [unowned self] peripheral in
+ if dataList.contains(peripheral){
+ return
+ }
+ if let name = peripheral.name,name.isEmpty{
+ return
+ }
+// print(peripheral.name)
+ dataList.append(peripheral)
+ listView.reloadData()
+ }
+ IBBluetoothManager.shared.didConnectedCallBack = {[unowned self] peripheral in
+ activityIndicatorView.stopAnimating()
+ let vc = storyboard!.instantiateViewController(identifier: "AbilityViewController")
+ self.navigationController?.pushViewController(vc, animated: true)
+ }
+ IBBluetoothManager.shared.disConnectedCallBack = { peripheral in
+ print("蓝牙已经断开!!!!!!!!!!!")
+ }
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+ IBBluetoothManager.shared.stopScan()
+ }
+ @objc func showLog(){
+ let vc = storyboard!.instantiateViewController(identifier: "LogViewController")
+ navigationController?.pushViewController(vc, animated: true)
+ }
+}
+
+extension ViewController: UITableViewDelegate, UITableViewDataSource {
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ dataList.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+ let label = cell.viewWithTag(10) as! UILabel
+ label.text = dataList[indexPath.row].name ?? "" + "(点击连接)"
+ return cell
+ }
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ activityIndicatorView.startAnimating()
+ let device = dataList[indexPath.row]
+ IBBluetoothManager.shared.connect(device)
+ }
+}
--- /dev/null
+//
+// IBluetooth.swift
+// IOTBluetooth
+//
+// Created by yaozuopan on 2024/2/4.
+//
+
+import Foundation
+import CoreBluetooth
+import IOTBluetooth
+
+/// Max is 482
+let dataPacketMaxBytes = 450
+
+/// 主服务
+let serviceUUID: String = "18E0"
+/// Notify 特征值
+let notifyCharacteristicUUID: String = "2AE5"
+/// Read 特征值
+let readCharacteristicUUID: String = "2AE2"
+/// Write 特征值(发送字节数≤20)
+let writeCharacteristicUUID: String = "2AE3"
+/// Write 特征值(发送字节数>20)
+let writeBigCharacteristicUUID: String = "2AE4"
+/// Write 特征值(发送文件OAT、MP3等)
+let writeFileCharacteristicUUID: String = "2AE0"
+
+public typealias IBDiscoverHandler = (_ peripheral: CBPeripheral) -> Void
+public typealias IBDidConectedHandler = (_ peripheral: CBPeripheral) -> Void
+public typealias IBDisConectedHandler = (_ peripheral: CBPeripheral) -> Void
+
+class IBBluetoothManager: NSObject {
+ static let shared = IBBluetoothManager()
+
+ /// 2AE5
+ private var notifyCharacteristic: CBCharacteristic!
+ /// 2AE2
+ private var readCharacteristic: CBCharacteristic!
+ /// 2AE3
+ private var writeCharacteristic: CBCharacteristic!
+ /// 2AE4
+ private var writeBigCharacteristic: CBCharacteristic!
+ /// 2AE0
+ private var writeFileCharacteristic: CBCharacteristic!
+
+ /// 中央管理类,管理蓝牙的扫描连接
+ var centralManager: CBCentralManager!
+ /// 当前连接设备
+ var currentPeripheral: CBPeripheral?
+
+ /// 扫描到设备
+ var discoverCallBack:IBDiscoverHandler?
+ /// 连上设备
+ var didConnectedCallBack:IBDidConectedHandler?
+ /// 设备断开连接
+ var disConnectedCallBack:IBDisConectedHandler?
+
+ private override init() {
+ super.init()
+ centralManager = CBCentralManager(delegate: self, queue: nil)
+ setupFileSdk()
+ }
+
+ func setupFileSdk(){
+ HsFileAgentSdk.shared.isDebug = true
+ HsFileAgentSdk.shared.sdkSendToDevice { data in
+ print(data)
+ }
+ }
+}
+
+extension IBBluetoothManager{
+ /// 开始扫描
+ func startScan() {
+ Logger.log("开始扫描...")
+ centralManager.scanForPeripherals(withServices: [CBUUID(string: serviceUUID)], options: nil)
+ if let peripheral = centralManager.retrieveConnectedPeripherals(withServices: [CBUUID(string: serviceUUID)]).first{
+ discoverCallBack?(peripheral)
+ }
+ }
+
+ /// 停止扫描
+ func stopScan(){
+ Logger.log("停止扫描蓝牙设备...")
+ centralManager.stopScan()
+ }
+
+ /// 连接蓝牙设备
+ func connect(_ peripheral: CBPeripheral) {
+ centralManager.connect(peripheral)
+ }
+
+ /// 断开与蓝牙设备的连接
+ func cancel(_ peripheral: CBPeripheral) {
+ centralManager.cancelPeripheralConnection(peripheral)
+ }
+
+ /// 发送数据
+ func send(_ sender: IBSender) {
+ //// if currentPeripheral.peripheral.state == .disconnected{
+ //// sender.completion(.response.code(.failure).message("手机未连上蓝牙设备"))
+ //// sender.remove()
+ //// Logger.log("手机未连上蓝牙设备!")
+ //// return
+ //// }
+ //
+ //
+ // for dataPacket in sender.dataPacket {
+ // Thread.sleep(forTimeInterval:0.02)
+ //
+ // Logger.log("\(sender.ability.characteristicUUID!) Write:", dataPacket.data.joined(" "), "Bytes(\(dataPacket.count))")
+ //
+ // var writeCharacteristic: CBCharacteristic = writeCharacteristic
+ // if sender.ability.characteristicUUID == writeBigCharacteristicUUID {
+ // writeCharacteristic = writeBigCharacteristic
+ // }
+ // if sender.ability.characteristicUUID == writeFileCharacteristicUUID {
+ // writeCharacteristic = writeFileCharacteristic
+ // }
+ // currentPeripheral.peripheral.writeValue(dataPacket.data, for: writeCharacteristic, type: .withResponse)
+ // }
+ }
+ func send(fileData data: Data) {
+ //// Thread.sleep(forTimeInterval:0.02)
+ //// if currentPeripheral.peripheral.state == .disconnected{
+ //// self.fileSender?.lastData?.fileWriteHander?(.response.code(.failure).message("手机未连上蓝牙设备"))
+ //// Logger.log("手机未连上蓝牙设备!")
+ //// return
+ //// }
+ //
+ // Logger.log("\(writeFileCharacteristicUUID) Write:", data)
+ // currentPeripheral.peripheral.writeValue(data, for: writeFileCharacteristic, type: .withResponse)
+ }
+}
+
+extension IBBluetoothManager: CBCentralManagerDelegate {
+ func centralManagerDidUpdateState(_ central: CBCentralManager) {
+ switch central.state {
+ case .unknown:
+ Logger.log("unknown")
+ case .resetting:
+ Logger.log("resetting")
+ case .unsupported:
+ Logger.log("unsupported")
+ case .unauthorized:
+ Logger.log("unauthorized")
+ case .poweredOff:
+ Logger.log("poweredOff")
+ case .poweredOn:
+ startScan()
+ Logger.log("poweredOn")
+ @unknown default:
+ break
+ }
+ }
+
+ func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
+// IBManager.discoverHandler?(device)
+ discoverCallBack?(peripheral)
+ }
+
+ func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
+ currentPeripheral = peripheral
+ peripheral.delegate = self
+ peripheral.discoverServices([CBUUID(string: serviceUUID)])
+ Logger.log("连接成功", peripheral.name ?? "")
+ didConnectedCallBack?(peripheral)
+ }
+
+ func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
+ Logger.log("连接失败",error?.localizedDescription ?? "" )
+ currentPeripheral = nil
+ }
+
+ func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
+ Logger.log("已断开连接")
+ disConnectedCallBack?(peripheral)
+ }
+}
+
+extension IBBluetoothManager: CBPeripheralDelegate {
+ func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
+
+ }
+ func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
+ peripheral
+ .services?
+ .forEach {
+ peripheral.discoverCharacteristics(nil, for: $0)
+ }
+ }
+ func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
+ service
+ .characteristics?
+ .forEach {
+ if $0.uuid.uuidString == notifyCharacteristicUUID {
+ notifyCharacteristic = $0
+ peripheral.setNotifyValue(true, for: $0)
+ return
+ }
+
+ if $0.uuid.uuidString == readCharacteristicUUID {
+ readCharacteristic = $0
+ peripheral.setNotifyValue(true, for: $0)
+ return
+ }
+
+ if $0.uuid.uuidString == writeCharacteristicUUID {
+ writeCharacteristic = $0
+ return
+ }
+
+ if $0.uuid.uuidString == writeBigCharacteristicUUID {
+ writeBigCharacteristic = $0
+ return
+ }
+
+ if $0.uuid.uuidString == writeFileCharacteristicUUID {
+ writeFileCharacteristic = $0
+ return
+ }
+ }
+ }
+ func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
+ peripheral.readValue(for: characteristic)
+ }
+
+ func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
+ if let error = error {
+ Logger.log(error)
+ }
+ HsFileAgentSdk.shared.sendToSdk(characteristic.value!)
+ }
+
+ func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
+ if let error = error {
+ Logger.log("发送失败 ",characteristic.uuid.uuidString,error.localizedDescription)
+ }
+ else{
+ Logger.log(characteristic.uuid.uuidString, "write success")
+ }
+ }
+}
--- /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()
+//// }
+//// 发送天气数据 设备重启
+//// 获取sd卡剩余空间 失败
+//// 同步路径坐标集历史数据 无响应
+//// 结束运动模式后立即发送其他指令 设备重启
+// }
+//
+// public static var logs: [String] { Logger.logs }
+//}
+//
+//public extension IBManager {
+// /// SDK 版本号
+// static let version: String = "1.0.23"
+// /// 默认为 false
+// static var isDebug: Bool = false {
+// didSet { Logger.isDebug = isDebug }
+// }
+//}
+//
+//// 蓝牙相关 API
+//public extension IBManager {
+//
+// internal static var discoverHandler: IBDiscoverHandler?
+//
+// /// 扫描设备
+// /// - Parameters:
+// /// - option: 扫描参数
+// /// - discoverHandler: 结果回调
+// static func scan(_ option: IBScanOption = .default, discoverHandler: @escaping IBDiscoverHandler) {
+// Self.discoverHandler = discoverHandler
+// IBBluetooth.shared.scan(option)
+// }
+//
+// /// 停止扫描蓝牙设备
+// static func stopScan() {
+// Self.discoverHandler = nil
+// IBBluetooth.shared.stopScan()
+// }
+//
+// internal static var completeHandler: IBCompleteHandler?
+//
+// /// 连接设备
+// /// - Parameters:
+// /// - peripheral: 扫描发现的设备
+// /// - completeHandler: 结果回调
+// static func connect(_ peripheral: IBPeripheral, completeHandler: @escaping IBCompleteHandler) {
+// Self.completeHandler = completeHandler
+// IBBluetooth.shared.connect(peripheral)
+// }
+//
+// /// 断开与蓝牙设备的连接
+// /// - Parameter peripheral: 蓝牙设备对像
+// static func cancel(_ peripheral: IBPeripheral) {
+// IBBluetooth.shared.cancel(peripheral)
+// }
+// /// 当前已连接设备
+// static var currentConnected: IBPeripheral! { IBBluetooth.shared.currentConnected}
+//}
--- /dev/null
+//
+// Logger.swift
+//
+//
+// Created by Harvey on 2021/4/27.
+//
+
+import Foundation
+
+/// 日记管理
+public class Logger {
+ static var isDebug: Bool = true
+
+ fileprivate static let shared = Logger()
+ private var fileHandle:FileHandle?
+ private init() {
+ let path = getPath()
+ fileHandle = FileHandle(forWritingAtPath: path)!
+// fileHandle?.seekToEndOfFile()
+ }
+
+ fileprivate var dateString: String {
+ let f = DateFormatter()
+ f.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
+ return f.string(from: Date())
+ }
+
+ static var logs: [String] = []
+
+
+ private func getPath()->String{
+// let path = NSHomeDirectory() + "/Documents/IOTBluetooth/" + "\(Date().format(.middle)).log"
+ let foldPath = NSHomeDirectory() + "/Documents/IOTBluetooth/"
+ let fileManager = FileManager.default
+ if !fileManager.fileExists(atPath: foldPath) {
+ do {
+ try FileManager.default.createDirectory(atPath: foldPath, withIntermediateDirectories: true, attributes: nil)
+ } catch {
+ return ""
+ }
+ }
+
+ let f = DateFormatter()
+ f.dateFormat = "yyyy-MM-dd HHmmss"
+ var time = f.string(from: Date())
+
+ let path = foldPath + "\(time).log"
+ if !FileManager.default.fileExists(atPath: path) {
+ FileManager.default.createFile(atPath: path, contents: nil, attributes: nil)
+ }
+ return path
+ }
+}
+
+public extension Logger {
+ static func log(_ logs: Any..., line: Int = #line, separator: String = " ", terminator: String = "\n") {
+ objc_sync_enter(Logger.shared)
+ let logString = Logger.shared.dateString + " [app-\(String(format: "%03d", line))]: " + logs.map({ "\($0)" }).joined(separator: separator) + terminator
+ if Logger.isDebug {
+ print(Logger.shared.dateString, terminator: " [app-\(String(format: "%03d", line))]: ")
+ print(logs, separator: separator, terminator: terminator)
+ Self.logs.append(logString)
+ }
+// Logger.shared.saveLog(log: logString)
+ objc_sync_exit(Logger.shared)
+
+
+ }
+
+ private func saveLog(log:String){
+ fileHandle?.write(log.data(using: .utf8)!)
+// if #available(iOS 13.0, *) {
+// try? fileHandle.close()
+// } else {
+// // Fallback on earlier versions
+// }
+ }
+}
--- /dev/null
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 56;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 180220CE2C2D5D560090D34A /* IBAiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180220CD2C2D5D560090D34A /* IBAiResponse.swift */; };
+ 180B01982BFC831300524729 /* IBHistoryHeartRate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180B01972BFC831300524729 /* IBHistoryHeartRate.swift */; };
+ 180B019E2BFC9CF400524729 /* IBHistoryBaseTraining.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180B019D2BFC9CF400524729 /* IBHistoryBaseTraining.swift */; };
+ 180CE2652C0D728F00A7006E /* IBAddressBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180CE2642C0D728F00A7006E /* IBAddressBook.swift */; };
+ 18113B2F2C13195900E04997 /* galaxy.lzma in Resources */ = {isa = PBXBuildFile; fileRef = 18113B2E2C13195900E04997 /* galaxy.lzma */; };
+ 18113B312C1400D700E04997 /* 黄沙-名决.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 18113B302C1400D700E04997 /* 黄沙-名决.mp3 */; };
+ 18113B332C1406CB00E04997 /* 大约在冬季.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 18113B322C1406CB00E04997 /* 大约在冬季.mp3 */; };
+ 181441CF2C11571400CCDD46 /* IBMp3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181441CE2C11571400CCDD46 /* IBMp3.swift */; };
+ 181441D12C11575D00CCDD46 /* IBOta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181441D02C11575D00CCDD46 /* IBOta.swift */; };
+ 181441D32C115AC500CCDD46 /* IBFileSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181441D22C115AC500CCDD46 /* IBFileSender.swift */; };
+ 181441D52C116AE500CCDD46 /* IBDial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181441D42C116AE500CCDD46 /* IBDial.swift */; };
+ 18204C002C199B8600F94E69 /* IBFileSender+Mp3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18204BFF2C199B8600F94E69 /* IBFileSender+Mp3.swift */; };
+ 18204C022C199D0E00F94E69 /* IBFileSender+Ota.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18204C012C199D0E00F94E69 /* IBFileSender+Ota.swift */; };
+ 18204C042C199E7800F94E69 /* IBFileSender+Dial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18204C032C199E7800F94E69 /* IBFileSender+Dial.swift */; };
+ 182DDFD42C5CE68900D44D37 /* IBAiValid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182DDFD32C5CE68900D44D37 /* IBAiValid.swift */; };
+ 1839DBD02C14442200E33B6E /* IBFetchFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1839DBCF2C14442200E33B6E /* IBFetchFile.swift */; };
+ 184431322BF3720E00546E4E /* IBOtherTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184431312BF3720E00546E4E /* IBOtherTarget.swift */; };
+ 1849DC8F2BEC8007002C2D2D /* IBVibrateSound.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1849DC8E2BEC8007002C2D2D /* IBVibrateSound.swift */; };
+ 1849DC912BECD131002C2D2D /* IBCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1849DC902BECD131002C2D2D /* IBCode.swift */; };
+ 184B94CB2BF606CC0042BEB3 /* IBTrainTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184B94CA2BF606CC0042BEB3 /* IBTrainTarget.swift */; };
+ 1859DFA02BFF5A2700112E8C /* IBSendTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1859DF9F2BFF5A2700112E8C /* IBSendTime.swift */; };
+ 185E9C3B2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg in Resources */ = {isa = PBXBuildFile; fileRef = 185E9C3A2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg */; };
+ 18884C442C11DBA300E01458 /* IBDeleteFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18884C432C11DBA300E01458 /* IBDeleteFile.swift */; };
+ 18884C462C12A4EA00E01458 /* IBEpo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18884C452C12A4EA00E01458 /* IBEpo.swift */; };
+ 18993AA22C073FED006742D2 /* IBBloodOxygen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18993AA12C073FED006742D2 /* IBBloodOxygen.swift */; };
+ 189C15892C05E0CD00676A80 /* IBTarinState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15882C05E0CD00676A80 /* IBTarinState.swift */; };
+ 189C158B2C05F27C00676A80 /* IBAbout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C158A2C05F27C00676A80 /* IBAbout.swift */; };
+ 189C158D2C05F58A00676A80 /* IBHistoryBloodOxygen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C158C2C05F58A00676A80 /* IBHistoryBloodOxygen.swift */; };
+ 189C158F2C05F85200676A80 /* IBHistoryStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C158E2C05F85200676A80 /* IBHistoryStep.swift */; };
+ 189C15912C05FBA100676A80 /* IBHistoryBurning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15902C05FBA100676A80 /* IBHistoryBurning.swift */; };
+ 189C15932C05FFA600676A80 /* IBHistoryStand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15922C05FFA600676A80 /* IBHistoryStand.swift */; };
+ 189C15952C05FFC700676A80 /* IBHistoryCalorie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15942C05FFC700676A80 /* IBHistoryCalorie.swift */; };
+ 189C15972C05FFEE00676A80 /* IBHistoryDistance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189C15962C05FFEE00676A80 /* IBHistoryDistance.swift */; };
+ 18C9589B2C082FA7006A5DE8 /* IBWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C9589A2C082FA7006A5DE8 /* IBWeather.swift */; };
+ 18C9589D2C08600C006A5DE8 /* IBHistoryGps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C9589C2C08600C006A5DE8 /* IBHistoryGps.swift */; };
+ 18C9589F2C086A9D006A5DE8 /* IBHistorySleep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C9589E2C086A9D006A5DE8 /* IBHistorySleep.swift */; };
+ 18E7AC922BF44C6800CB9393 /* IBSleep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E7AC912BF44C6800CB9393 /* IBSleep.swift */; };
+ 18E7AC942BF4B31000CB9393 /* IBWater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E7AC932BF4B31000CB9393 /* IBWater.swift */; };
+ 18E7AC962BF5E39900CB9393 /* IBHeartRate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E7AC952BF5E39900CB9393 /* IBHeartRate.swift */; };
+ 18EC6E282BFF4C6D00757CA8 /* IBPhoneInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EC6E272BFF4C6D00757CA8 /* IBPhoneInfo.swift */; };
+ 18EF69512C45130200AA0A36 /* IBAiManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF69502C45130200AA0A36 /* IBAiManager.swift */; };
+ 18EF69532C45136800AA0A36 /* IBPairling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF69522C45136800AA0A36 /* IBPairling.swift */; };
+ 18EF69552C45266D00AA0A36 /* IBReceiveManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF69542C45266D00AA0A36 /* IBReceiveManager.swift */; };
+ 18EF69702C4798BB00AA0A36 /* libbz2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18EF696B2C47766400AA0A36 /* libbz2.tbd */; };
+ 18EF69712C4798C600AA0A36 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18EF69692C47765800AA0A36 /* libiconv.tbd */; };
+ 18EF69722C4798D100AA0A36 /* liblzma.5.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18EF69672C47764400AA0A36 /* liblzma.5.tbd */; };
+ 18EF69732C4798DD00AA0A36 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18EF695F2C476E2000AA0A36 /* libz.tbd */; };
+ 18EF69762C47A0F200AA0A36 /* AIAudio.sbc in Resources */ = {isa = PBXBuildFile; fileRef = 18EF69752C47A0F200AA0A36 /* AIAudio.sbc */; };
+ 18EF69772C47A0F200AA0A36 /* AIAudio.sbc in Resources */ = {isa = PBXBuildFile; fileRef = 18EF69752C47A0F200AA0A36 /* AIAudio.sbc */; };
+ 18EF69792C47A86D00AA0A36 /* IBTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF69782C47A86D00AA0A36 /* IBTools.swift */; };
+ 18EF697A2C47A86D00AA0A36 /* IBTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EF69782C47A86D00AA0A36 /* IBTools.swift */; };
+ 18EFF59B2C080F14001B1E0E /* IBUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */; };
+ 3FC0CFCE2CD8634200F97933 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC0CFCD2CD8634200F97933 /* Logger.swift */; };
+ 3FFE56DF2CD6157F000C7415 /* IBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9F6AC02BA04BE100DA520F /* IBManager.swift */; };
+ 3FFE56E02CD61584000C7415 /* IBBluetoothManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39C52B980C88006A3B18 /* IBBluetoothManager.swift */; };
+ 3FFE56E72CD624D6000C7415 /* HsFileAgentSdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFE56E62CD624D6000C7415 /* HsFileAgentSdk.swift */; };
+ 8A0A39BF2B980BEF006A3B18 /* IOTBluetooth.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8A0A39C92B980CC2006A3B18 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */; };
+ 8A0A39CB2B980CFC006A3B18 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39CA2B980CFC006A3B18 /* Logger.swift */; };
+ 8A0A39D22B980E4C006A3B18 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D12B980E4C006A3B18 /* JSON.swift */; };
+ 8A0A39D42B980E93006A3B18 /* Any.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D32B980E93006A3B18 /* Any.swift */; };
+ 8A0A39D62B980EC9006A3B18 /* IBPeripheralUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D52B980EC9006A3B18 /* IBPeripheralUUID.swift */; };
+ 8A0A39D82B980F58006A3B18 /* UTF8.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D72B980F58006A3B18 /* UTF8.swift */; };
+ 8A0A39DA2B981037006A3B18 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0A39D92B981037006A3B18 /* Date.swift */; };
+ 8A1DFF522B9EB4F800B735FF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */; };
+ 8A1DFF562B9EB4F800B735FF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1DFF552B9EB4F800B735FF /* ViewController.swift */; };
+ 8A1DFF592B9EB4F900B735FF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8A1DFF572B9EB4F900B735FF /* Main.storyboard */; };
+ 8A1DFF5B2B9EB4F900B735FF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8A1DFF5A2B9EB4F900B735FF /* Assets.xcassets */; };
+ 8A1DFF5E2B9EB4F900B735FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8A1DFF5C2B9EB4F900B735FF /* LaunchScreen.storyboard */; };
+ 8A34B9252BA2A069000F66BD /* IBWatchInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B9242BA2A069000F66BD /* IBWatchInfo.swift */; };
+ 8A34B9292BA2AB86000F66BD /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B9282BA2AB86000F66BD /* LogViewController.swift */; };
+ 8A34B92B2BA2D446000F66BD /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B92A2BA2D446000F66BD /* BaseViewController.swift */; };
+ 8A34B92D2BA2FA02000F66BD /* IBManufacturer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A34B92C2BA2FA02000F66BD /* IBManufacturer.swift */; };
+ 8A5822CF2BA1A67700590AB6 /* IBAbility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A5822CE2BA1A67700590AB6 /* IBAbility.swift */; };
+ 8A5822D12BA1C80700590AB6 /* LRC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A5822D02BA1C80700590AB6 /* LRC.swift */; };
+ 8A65F95F2BABEC4D005C1EA4 /* IBClock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F95E2BABEC4D005C1EA4 /* IBClock.swift */; };
+ 8A65F9612BAC0810005C1EA4 /* IBSimulateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F9602BAC0810005C1EA4 /* IBSimulateParser.swift */; };
+ 8A65F9632BAC1018005C1EA4 /* IBTargetStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F9622BAC1018005C1EA4 /* IBTargetStep.swift */; };
+ 8A65F9652BAC271B005C1EA4 /* IBScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A65F9642BAC271B005C1EA4 /* IBScreen.swift */; };
+ 8A76B6092BA84C2B0016F2AB /* AbilityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A76B6082BA84C2B0016F2AB /* AbilityViewController.swift */; };
+ 8A76B60B2BA851730016F2AB /* IBParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A76B60A2BA851730016F2AB /* IBParser.swift */; };
+ 8A79D2252BA4278E0096A98C /* IBSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A79D2242BA4278E0096A98C /* IBSender.swift */; };
+ 8A79D2272BA433330096A98C /* IBNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A79D2262BA433330096A98C /* IBNotify.swift */; };
+ 8A79D2292BA43F5A0096A98C /* IBResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A79D2282BA43F5A0096A98C /* IBResponse.swift */; };
+ 8A878FE02BA93CF1008C7F26 /* Datas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A878FDF2BA93CF1008C7F26 /* Datas.swift */; };
+ 8A878FE22BA9444C008C7F26 /* IBParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A878FE12BA9444C008C7F26 /* IBParameter.swift */; };
+ 8A878FE42BA94D09008C7F26 /* IBChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A878FE32BA94D09008C7F26 /* IBChecker.swift */; };
+ 8A87FBB32BAA7F3900096761 /* IBLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A87FBB22BAA7F3900096761 /* IBLanguage.swift */; };
+ 8A87FBB52BAA85FB00096761 /* IBTrainingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A87FBB42BAA85FB00096761 /* IBTrainingData.swift */; };
+ 8A9F6ABD2BA0463D00DA520F /* CRC16.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9F6ABC2BA0463D00DA520F /* CRC16.swift */; };
+ 8A9F6ABE2BA0475100DA520F /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */; };
+ 8A9F6AC22BA05CB500DA520F /* IOTBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */; };
+ 8A9F6AC32BA05CB500DA520F /* IOTBluetooth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 8A9F6AC42BA05CB500DA520F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 8A0A39B22B980BEF006A3B18 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 8A0A39BA2B980BEF006A3B18;
+ remoteInfo = IOTBluetooth;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 8A9F6AC62BA05CB500DA520F /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 8A9F6AC32BA05CB500DA520F /* IOTBluetooth.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 180220CD2C2D5D560090D34A /* IBAiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAiResponse.swift; sourceTree = "<group>"; };
+ 180B01972BFC831300524729 /* IBHistoryHeartRate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryHeartRate.swift; sourceTree = "<group>"; };
+ 180B019D2BFC9CF400524729 /* IBHistoryBaseTraining.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryBaseTraining.swift; sourceTree = "<group>"; };
+ 180CE2642C0D728F00A7006E /* IBAddressBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAddressBook.swift; sourceTree = "<group>"; };
+ 18113B2E2C13195900E04997 /* galaxy.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = galaxy.lzma; sourceTree = "<group>"; };
+ 18113B302C1400D700E04997 /* 黄沙-名决.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "黄沙-名决.mp3"; sourceTree = "<group>"; };
+ 18113B322C1406CB00E04997 /* 大约在冬季.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "大约在冬季.mp3"; sourceTree = "<group>"; };
+ 181441CE2C11571400CCDD46 /* IBMp3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBMp3.swift; sourceTree = "<group>"; };
+ 181441D02C11575D00CCDD46 /* IBOta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBOta.swift; sourceTree = "<group>"; };
+ 181441D22C115AC500CCDD46 /* IBFileSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBFileSender.swift; sourceTree = "<group>"; };
+ 181441D42C116AE500CCDD46 /* IBDial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBDial.swift; sourceTree = "<group>"; };
+ 18204BFF2C199B8600F94E69 /* IBFileSender+Mp3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Mp3.swift"; sourceTree = "<group>"; };
+ 18204C012C199D0E00F94E69 /* IBFileSender+Ota.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Ota.swift"; sourceTree = "<group>"; };
+ 18204C032C199E7800F94E69 /* IBFileSender+Dial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IBFileSender+Dial.swift"; sourceTree = "<group>"; };
+ 182DDFD32C5CE68900D44D37 /* IBAiValid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAiValid.swift; sourceTree = "<group>"; };
+ 1839DBCF2C14442200E33B6E /* IBFetchFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBFetchFile.swift; sourceTree = "<group>"; };
+ 184431312BF3720E00546E4E /* IBOtherTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBOtherTarget.swift; sourceTree = "<group>"; };
+ 1849DC8E2BEC8007002C2D2D /* IBVibrateSound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBVibrateSound.swift; sourceTree = "<group>"; };
+ 1849DC902BECD131002C2D2D /* IBCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBCode.swift; sourceTree = "<group>"; };
+ 184B94CA2BF606CC0042BEB3 /* IBTrainTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTrainTarget.swift; sourceTree = "<group>"; };
+ 1859DF9F2BFF5A2700112E8C /* IBSendTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBSendTime.swift; sourceTree = "<group>"; };
+ 185E9C3A2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg */ = {isa = PBXFileReference; lastKnownFileType = file; path = 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg; sourceTree = "<group>"; };
+ 18884C432C11DBA300E01458 /* IBDeleteFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBDeleteFile.swift; sourceTree = "<group>"; };
+ 18884C452C12A4EA00E01458 /* IBEpo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBEpo.swift; sourceTree = "<group>"; };
+ 18993AA12C073FED006742D2 /* IBBloodOxygen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBBloodOxygen.swift; sourceTree = "<group>"; };
+ 189C15882C05E0CD00676A80 /* IBTarinState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTarinState.swift; sourceTree = "<group>"; };
+ 189C158A2C05F27C00676A80 /* IBAbout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAbout.swift; sourceTree = "<group>"; };
+ 189C158C2C05F58A00676A80 /* IBHistoryBloodOxygen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryBloodOxygen.swift; sourceTree = "<group>"; };
+ 189C158E2C05F85200676A80 /* IBHistoryStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryStep.swift; sourceTree = "<group>"; };
+ 189C15902C05FBA100676A80 /* IBHistoryBurning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryBurning.swift; sourceTree = "<group>"; };
+ 189C15922C05FFA600676A80 /* IBHistoryStand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryStand.swift; sourceTree = "<group>"; };
+ 189C15942C05FFC700676A80 /* IBHistoryCalorie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryCalorie.swift; sourceTree = "<group>"; };
+ 189C15962C05FFEE00676A80 /* IBHistoryDistance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryDistance.swift; sourceTree = "<group>"; };
+ 18C9589A2C082FA7006A5DE8 /* IBWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBWeather.swift; sourceTree = "<group>"; };
+ 18C9589C2C08600C006A5DE8 /* IBHistoryGps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistoryGps.swift; sourceTree = "<group>"; };
+ 18C9589E2C086A9D006A5DE8 /* IBHistorySleep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHistorySleep.swift; sourceTree = "<group>"; };
+ 18E7AC912BF44C6800CB9393 /* IBSleep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBSleep.swift; sourceTree = "<group>"; };
+ 18E7AC932BF4B31000CB9393 /* IBWater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBWater.swift; sourceTree = "<group>"; };
+ 18E7AC952BF5E39900CB9393 /* IBHeartRate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBHeartRate.swift; sourceTree = "<group>"; };
+ 18EC6E272BFF4C6D00757CA8 /* IBPhoneInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBPhoneInfo.swift; sourceTree = "<group>"; };
+ 18EF69502C45130200AA0A36 /* IBAiManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAiManager.swift; sourceTree = "<group>"; };
+ 18EF69522C45136800AA0A36 /* IBPairling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBPairling.swift; sourceTree = "<group>"; };
+ 18EF69542C45266D00AA0A36 /* IBReceiveManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBReceiveManager.swift; sourceTree = "<group>"; };
+ 18EF695F2C476E2000AA0A36 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
+ 18EF69672C47764400AA0A36 /* liblzma.5.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = liblzma.5.tbd; path = usr/lib/liblzma.5.tbd; sourceTree = SDKROOT; };
+ 18EF69692C47765800AA0A36 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; };
+ 18EF696B2C47766400AA0A36 /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; };
+ 18EF69752C47A0F200AA0A36 /* AIAudio.sbc */ = {isa = PBXFileReference; lastKnownFileType = file; path = AIAudio.sbc; sourceTree = "<group>"; };
+ 18EF69782C47A86D00AA0A36 /* IBTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTools.swift; sourceTree = "<group>"; };
+ 18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBUserInfo.swift; sourceTree = "<group>"; };
+ 3FC0CFCD2CD8634200F97933 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
+ 3FFE56E62CD624D6000C7415 /* HsFileAgentSdk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HsFileAgentSdk.swift; sourceTree = "<group>"; };
+ 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = IOTBluetooth.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IOTBluetooth.h; sourceTree = "<group>"; };
+ 8A0A39C52B980C88006A3B18 /* IBBluetoothManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBBluetoothManager.swift; sourceTree = "<group>"; };
+ 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; };
+ 8A0A39CA2B980CFC006A3B18 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
+ 8A0A39D12B980E4C006A3B18 /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
+ 8A0A39D32B980E93006A3B18 /* Any.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Any.swift; sourceTree = "<group>"; };
+ 8A0A39D52B980EC9006A3B18 /* IBPeripheralUUID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBPeripheralUUID.swift; sourceTree = "<group>"; };
+ 8A0A39D72B980F58006A3B18 /* UTF8.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTF8.swift; sourceTree = "<group>"; };
+ 8A0A39D92B981037006A3B18 /* Date.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
+ 8A1DFF4F2B9EB4F800B735FF /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+ 8A1DFF552B9EB4F800B735FF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
+ 8A1DFF582B9EB4F900B735FF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+ 8A1DFF5A2B9EB4F900B735FF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+ 8A1DFF5D2B9EB4F900B735FF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+ 8A1DFF5F2B9EB4F900B735FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ 8A34B9242BA2A069000F66BD /* IBWatchInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBWatchInfo.swift; sourceTree = "<group>"; };
+ 8A34B9282BA2AB86000F66BD /* LogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
+ 8A34B92A2BA2D446000F66BD /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = "<group>"; };
+ 8A34B92C2BA2FA02000F66BD /* IBManufacturer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBManufacturer.swift; sourceTree = "<group>"; };
+ 8A5822CE2BA1A67700590AB6 /* IBAbility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBAbility.swift; sourceTree = "<group>"; };
+ 8A5822D02BA1C80700590AB6 /* LRC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRC.swift; sourceTree = "<group>"; };
+ 8A65F95E2BABEC4D005C1EA4 /* IBClock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBClock.swift; sourceTree = "<group>"; };
+ 8A65F9602BAC0810005C1EA4 /* IBSimulateParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBSimulateParser.swift; sourceTree = "<group>"; };
+ 8A65F9622BAC1018005C1EA4 /* IBTargetStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTargetStep.swift; sourceTree = "<group>"; };
+ 8A65F9642BAC271B005C1EA4 /* IBScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBScreen.swift; sourceTree = "<group>"; };
+ 8A76B6082BA84C2B0016F2AB /* AbilityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AbilityViewController.swift; sourceTree = "<group>"; };
+ 8A76B60A2BA851730016F2AB /* IBParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBParser.swift; sourceTree = "<group>"; };
+ 8A79D2242BA4278E0096A98C /* IBSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBSender.swift; sourceTree = "<group>"; };
+ 8A79D2262BA433330096A98C /* IBNotify.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBNotify.swift; sourceTree = "<group>"; };
+ 8A79D2282BA43F5A0096A98C /* IBResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBResponse.swift; sourceTree = "<group>"; };
+ 8A878FDF2BA93CF1008C7F26 /* Datas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Datas.swift; sourceTree = "<group>"; };
+ 8A878FE12BA9444C008C7F26 /* IBParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBParameter.swift; sourceTree = "<group>"; };
+ 8A878FE32BA94D09008C7F26 /* IBChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBChecker.swift; sourceTree = "<group>"; };
+ 8A87FBB22BAA7F3900096761 /* IBLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBLanguage.swift; sourceTree = "<group>"; };
+ 8A87FBB42BAA85FB00096761 /* IBTrainingData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBTrainingData.swift; sourceTree = "<group>"; };
+ 8A9F6ABC2BA0463D00DA520F /* CRC16.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRC16.swift; sourceTree = "<group>"; };
+ 8A9F6AC02BA04BE100DA520F /* IBManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBManager.swift; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 8A0A39B82B980BEF006A3B18 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 18EF69732C4798DD00AA0A36 /* libz.tbd in Frameworks */,
+ 18EF69722C4798D100AA0A36 /* liblzma.5.tbd in Frameworks */,
+ 18EF69712C4798C600AA0A36 /* libiconv.tbd in Frameworks */,
+ 18EF69702C4798BB00AA0A36 /* libbz2.tbd in Frameworks */,
+ 8A0A39C92B980CC2006A3B18 /* CoreBluetooth.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 8A1DFF4C2B9EB4F800B735FF /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8A9F6AC22BA05CB500DA520F /* IOTBluetooth.framework in Frameworks */,
+ 8A9F6ABE2BA0475100DA520F /* CoreBluetooth.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 18113B292C1318FD00E04997 /* Resource */ = {
+ isa = PBXGroup;
+ children = (
+ 18EF69752C47A0F200AA0A36 /* AIAudio.sbc */,
+ 18113B322C1406CB00E04997 /* 大约在冬季.mp3 */,
+ 18113B302C1400D700E04997 /* 黄沙-名决.mp3 */,
+ 185E9C3A2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg */,
+ 18113B2E2C13195900E04997 /* galaxy.lzma */,
+ );
+ path = Resource;
+ sourceTree = "<group>";
+ };
+ 3FFE56DE2CD614B8000C7415 /* ble */ = {
+ isa = PBXGroup;
+ children = (
+ 3FC0CFCD2CD8634200F97933 /* Logger.swift */,
+ 8A9F6AC02BA04BE100DA520F /* IBManager.swift */,
+ 8A0A39C52B980C88006A3B18 /* IBBluetoothManager.swift */,
+ );
+ path = ble;
+ sourceTree = "<group>";
+ };
+ 8A0A39B12B980BEF006A3B18 = {
+ isa = PBXGroup;
+ children = (
+ 8A0A39BD2B980BEF006A3B18 /* IOTBluetooth */,
+ 8A1DFF502B9EB4F800B735FF /* Example */,
+ 8A0A39BC2B980BEF006A3B18 /* Products */,
+ 8A0A39C72B980CC2006A3B18 /* Frameworks */,
+ );
+ sourceTree = "<group>";
+ };
+ 8A0A39BC2B980BEF006A3B18 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */,
+ 8A1DFF4F2B9EB4F800B735FF /* Example.app */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 8A0A39BD2B980BEF006A3B18 /* IOTBluetooth */ = {
+ isa = PBXGroup;
+ children = (
+ 8A9F6ABF2BA04B5100DA520F /* Models */,
+ 8A0A39D02B980DFD006A3B18 /* Tools */,
+ 18EF69542C45266D00AA0A36 /* IBReceiveManager.swift */,
+ 8A5822CE2BA1A67700590AB6 /* IBAbility.swift */,
+ 8A878FE32BA94D09008C7F26 /* IBChecker.swift */,
+ 8A76B60A2BA851730016F2AB /* IBParser.swift */,
+ 8A65F9602BAC0810005C1EA4 /* IBSimulateParser.swift */,
+ 8A878FE12BA9444C008C7F26 /* IBParameter.swift */,
+ 8A79D2242BA4278E0096A98C /* IBSender.swift */,
+ 181441D22C115AC500CCDD46 /* IBFileSender.swift */,
+ 18204BFF2C199B8600F94E69 /* IBFileSender+Mp3.swift */,
+ 18204C012C199D0E00F94E69 /* IBFileSender+Ota.swift */,
+ 18204C032C199E7800F94E69 /* IBFileSender+Dial.swift */,
+ 8A79D2262BA433330096A98C /* IBNotify.swift */,
+ 18EF69522C45136800AA0A36 /* IBPairling.swift */,
+ 18EF69502C45130200AA0A36 /* IBAiManager.swift */,
+ 3FFE56E62CD624D6000C7415 /* HsFileAgentSdk.swift */,
+ 8A0A39BE2B980BEF006A3B18 /* IOTBluetooth.h */,
+ );
+ path = IOTBluetooth;
+ sourceTree = "<group>";
+ };
+ 8A0A39C72B980CC2006A3B18 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 18EF696B2C47766400AA0A36 /* libbz2.tbd */,
+ 18EF69692C47765800AA0A36 /* libiconv.tbd */,
+ 18EF69672C47764400AA0A36 /* liblzma.5.tbd */,
+ 18EF695F2C476E2000AA0A36 /* libz.tbd */,
+ 8A0A39C82B980CC2006A3B18 /* CoreBluetooth.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ 8A0A39D02B980DFD006A3B18 /* Tools */ = {
+ isa = PBXGroup;
+ children = (
+ 8A0A39D52B980EC9006A3B18 /* IBPeripheralUUID.swift */,
+ 8A0A39D32B980E93006A3B18 /* Any.swift */,
+ 8A0A39D92B981037006A3B18 /* Date.swift */,
+ 8A0A39D12B980E4C006A3B18 /* JSON.swift */,
+ 8A9F6ABC2BA0463D00DA520F /* CRC16.swift */,
+ 8A5822D02BA1C80700590AB6 /* LRC.swift */,
+ 8A0A39D72B980F58006A3B18 /* UTF8.swift */,
+ 8A0A39CA2B980CFC006A3B18 /* Logger.swift */,
+ 18EF69782C47A86D00AA0A36 /* IBTools.swift */,
+ );
+ path = Tools;
+ sourceTree = "<group>";
+ };
+ 8A1DFF502B9EB4F800B735FF /* Example */ = {
+ isa = PBXGroup;
+ children = (
+ 3FFE56DE2CD614B8000C7415 /* ble */,
+ 18113B292C1318FD00E04997 /* Resource */,
+ 8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */,
+ 8A1DFF552B9EB4F800B735FF /* ViewController.swift */,
+ 8A76B6082BA84C2B0016F2AB /* AbilityViewController.swift */,
+ 8A878FDF2BA93CF1008C7F26 /* Datas.swift */,
+ 8A34B92A2BA2D446000F66BD /* BaseViewController.swift */,
+ 8A34B9282BA2AB86000F66BD /* LogViewController.swift */,
+ 8A1DFF572B9EB4F900B735FF /* Main.storyboard */,
+ 8A1DFF5A2B9EB4F900B735FF /* Assets.xcassets */,
+ 8A1DFF5C2B9EB4F900B735FF /* LaunchScreen.storyboard */,
+ 8A1DFF5F2B9EB4F900B735FF /* Info.plist */,
+ );
+ path = Example;
+ sourceTree = "<group>";
+ };
+ 8A9F6ABF2BA04B5100DA520F /* Models */ = {
+ isa = PBXGroup;
+ children = (
+ 8A87FBB22BAA7F3900096761 /* IBLanguage.swift */,
+ 8A79D2282BA43F5A0096A98C /* IBResponse.swift */,
+ 8A65F95E2BABEC4D005C1EA4 /* IBClock.swift */,
+ 18E7AC912BF44C6800CB9393 /* IBSleep.swift */,
+ 8A65F9642BAC271B005C1EA4 /* IBScreen.swift */,
+ 18E7AC932BF4B31000CB9393 /* IBWater.swift */,
+ 8A65F9622BAC1018005C1EA4 /* IBTargetStep.swift */,
+ 8A87FBB42BAA85FB00096761 /* IBTrainingData.swift */,
+ 8A34B92C2BA2FA02000F66BD /* IBManufacturer.swift */,
+ 8A34B9242BA2A069000F66BD /* IBWatchInfo.swift */,
+ 184431312BF3720E00546E4E /* IBOtherTarget.swift */,
+ 184B94CA2BF606CC0042BEB3 /* IBTrainTarget.swift */,
+ 18E7AC952BF5E39900CB9393 /* IBHeartRate.swift */,
+ 18993AA12C073FED006742D2 /* IBBloodOxygen.swift */,
+ 180B01972BFC831300524729 /* IBHistoryHeartRate.swift */,
+ 189C158C2C05F58A00676A80 /* IBHistoryBloodOxygen.swift */,
+ 189C158E2C05F85200676A80 /* IBHistoryStep.swift */,
+ 189C15902C05FBA100676A80 /* IBHistoryBurning.swift */,
+ 189C15922C05FFA600676A80 /* IBHistoryStand.swift */,
+ 189C15942C05FFC700676A80 /* IBHistoryCalorie.swift */,
+ 189C15962C05FFEE00676A80 /* IBHistoryDistance.swift */,
+ 180B019D2BFC9CF400524729 /* IBHistoryBaseTraining.swift */,
+ 18C9589E2C086A9D006A5DE8 /* IBHistorySleep.swift */,
+ 18C9589C2C08600C006A5DE8 /* IBHistoryGps.swift */,
+ 1849DC8E2BEC8007002C2D2D /* IBVibrateSound.swift */,
+ 18EC6E272BFF4C6D00757CA8 /* IBPhoneInfo.swift */,
+ 1859DF9F2BFF5A2700112E8C /* IBSendTime.swift */,
+ 189C15882C05E0CD00676A80 /* IBTarinState.swift */,
+ 189C158A2C05F27C00676A80 /* IBAbout.swift */,
+ 18EFF59A2C080F14001B1E0E /* IBUserInfo.swift */,
+ 18C9589A2C082FA7006A5DE8 /* IBWeather.swift */,
+ 180CE2642C0D728F00A7006E /* IBAddressBook.swift */,
+ 181441CE2C11571400CCDD46 /* IBMp3.swift */,
+ 181441D02C11575D00CCDD46 /* IBOta.swift */,
+ 181441D42C116AE500CCDD46 /* IBDial.swift */,
+ 18884C452C12A4EA00E01458 /* IBEpo.swift */,
+ 18884C432C11DBA300E01458 /* IBDeleteFile.swift */,
+ 1839DBCF2C14442200E33B6E /* IBFetchFile.swift */,
+ 180220CD2C2D5D560090D34A /* IBAiResponse.swift */,
+ 1849DC902BECD131002C2D2D /* IBCode.swift */,
+ 182DDFD32C5CE68900D44D37 /* IBAiValid.swift */,
+ );
+ path = Models;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ 8A0A39B62B980BEF006A3B18 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8A0A39BF2B980BEF006A3B18 /* IOTBluetooth.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ 8A0A39BA2B980BEF006A3B18 /* IOTBluetooth */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 8A0A39C22B980BEF006A3B18 /* Build configuration list for PBXNativeTarget "IOTBluetooth" */;
+ buildPhases = (
+ 8A0A39B62B980BEF006A3B18 /* Headers */,
+ 8A0A39B72B980BEF006A3B18 /* Sources */,
+ 8A0A39B82B980BEF006A3B18 /* Frameworks */,
+ 8A0A39B92B980BEF006A3B18 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = IOTBluetooth;
+ productName = IOTBluetooth;
+ productReference = 8A0A39BB2B980BEF006A3B18 /* IOTBluetooth.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ 8A1DFF4E2B9EB4F800B735FF /* Example */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 8A1DFF622B9EB4F900B735FF /* Build configuration list for PBXNativeTarget "Example" */;
+ buildPhases = (
+ 8A1DFF4B2B9EB4F800B735FF /* Sources */,
+ 8A1DFF4C2B9EB4F800B735FF /* Frameworks */,
+ 8A1DFF4D2B9EB4F800B735FF /* Resources */,
+ 8A9F6AC62BA05CB500DA520F /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 8A9F6AC52BA05CB500DA520F /* PBXTargetDependency */,
+ );
+ name = Example;
+ productName = Example;
+ productReference = 8A1DFF4F2B9EB4F800B735FF /* Example.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 8A0A39B22B980BEF006A3B18 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1500;
+ LastUpgradeCheck = 1500;
+ TargetAttributes = {
+ 8A0A39BA2B980BEF006A3B18 = {
+ CreatedOnToolsVersion = 15.0;
+ LastSwiftMigration = 1500;
+ };
+ 8A1DFF4E2B9EB4F800B735FF = {
+ CreatedOnToolsVersion = 15.0;
+ };
+ };
+ };
+ buildConfigurationList = 8A0A39B52B980BEF006A3B18 /* Build configuration list for PBXProject "IOTBluetooth" */;
+ compatibilityVersion = "Xcode 14.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 8A0A39B12B980BEF006A3B18;
+ productRefGroup = 8A0A39BC2B980BEF006A3B18 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 8A0A39BA2B980BEF006A3B18 /* IOTBluetooth */,
+ 8A1DFF4E2B9EB4F800B735FF /* Example */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 8A0A39B92B980BEF006A3B18 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 18EF69762C47A0F200AA0A36 /* AIAudio.sbc in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 8A1DFF4D2B9EB4F800B735FF /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8A1DFF5E2B9EB4F900B735FF /* LaunchScreen.storyboard in Resources */,
+ 18113B312C1400D700E04997 /* 黄沙-名决.mp3 in Resources */,
+ 8A1DFF5B2B9EB4F900B735FF /* Assets.xcassets in Resources */,
+ 8A1DFF592B9EB4F900B735FF /* Main.storyboard in Resources */,
+ 185E9C3B2C2564F60078C808 /* 20240604_155433_2_V1.0.1_20240618_113111_ota.fwpkg in Resources */,
+ 18EF69772C47A0F200AA0A36 /* AIAudio.sbc in Resources */,
+ 18113B332C1406CB00E04997 /* 大约在冬季.mp3 in Resources */,
+ 18113B2F2C13195900E04997 /* galaxy.lzma in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 8A0A39B72B980BEF006A3B18 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8A79D2292BA43F5A0096A98C /* IBResponse.swift in Sources */,
+ 189C15932C05FFA600676A80 /* IBHistoryStand.swift in Sources */,
+ 1849DC8F2BEC8007002C2D2D /* IBVibrateSound.swift in Sources */,
+ 18204C042C199E7800F94E69 /* IBFileSender+Dial.swift in Sources */,
+ 180CE2652C0D728F00A7006E /* IBAddressBook.swift in Sources */,
+ 8A878FE22BA9444C008C7F26 /* IBParameter.swift in Sources */,
+ 184431322BF3720E00546E4E /* IBOtherTarget.swift in Sources */,
+ 189C158B2C05F27C00676A80 /* IBAbout.swift in Sources */,
+ 189C15972C05FFEE00676A80 /* IBHistoryDistance.swift in Sources */,
+ 180B019E2BFC9CF400524729 /* IBHistoryBaseTraining.swift in Sources */,
+ 8A87FBB52BAA85FB00096761 /* IBTrainingData.swift in Sources */,
+ 8A878FE42BA94D09008C7F26 /* IBChecker.swift in Sources */,
+ 18884C442C11DBA300E01458 /* IBDeleteFile.swift in Sources */,
+ 189C15892C05E0CD00676A80 /* IBTarinState.swift in Sources */,
+ 182DDFD42C5CE68900D44D37 /* IBAiValid.swift in Sources */,
+ 8A76B60B2BA851730016F2AB /* IBParser.swift in Sources */,
+ 18EFF59B2C080F14001B1E0E /* IBUserInfo.swift in Sources */,
+ 8A65F9612BAC0810005C1EA4 /* IBSimulateParser.swift in Sources */,
+ 180B01982BFC831300524729 /* IBHistoryHeartRate.swift in Sources */,
+ 189C158D2C05F58A00676A80 /* IBHistoryBloodOxygen.swift in Sources */,
+ 8A79D2272BA433330096A98C /* IBNotify.swift in Sources */,
+ 8A0A39D42B980E93006A3B18 /* Any.swift in Sources */,
+ 1849DC912BECD131002C2D2D /* IBCode.swift in Sources */,
+ 1839DBD02C14442200E33B6E /* IBFetchFile.swift in Sources */,
+ 8A34B92D2BA2FA02000F66BD /* IBManufacturer.swift in Sources */,
+ 184B94CB2BF606CC0042BEB3 /* IBTrainTarget.swift in Sources */,
+ 18204C002C199B8600F94E69 /* IBFileSender+Mp3.swift in Sources */,
+ 8A79D2252BA4278E0096A98C /* IBSender.swift in Sources */,
+ 18C9589D2C08600C006A5DE8 /* IBHistoryGps.swift in Sources */,
+ 189C15912C05FBA100676A80 /* IBHistoryBurning.swift in Sources */,
+ 8A9F6ABD2BA0463D00DA520F /* CRC16.swift in Sources */,
+ 18E7AC942BF4B31000CB9393 /* IBWater.swift in Sources */,
+ 8A5822CF2BA1A67700590AB6 /* IBAbility.swift in Sources */,
+ 8A65F9652BAC271B005C1EA4 /* IBScreen.swift in Sources */,
+ 1859DFA02BFF5A2700112E8C /* IBSendTime.swift in Sources */,
+ 18EF69792C47A86D00AA0A36 /* IBTools.swift in Sources */,
+ 8A65F9632BAC1018005C1EA4 /* IBTargetStep.swift in Sources */,
+ 181441D32C115AC500CCDD46 /* IBFileSender.swift in Sources */,
+ 8A34B9252BA2A069000F66BD /* IBWatchInfo.swift in Sources */,
+ 8A0A39D62B980EC9006A3B18 /* IBPeripheralUUID.swift in Sources */,
+ 189C15952C05FFC700676A80 /* IBHistoryCalorie.swift in Sources */,
+ 181441CF2C11571400CCDD46 /* IBMp3.swift in Sources */,
+ 18EF69512C45130200AA0A36 /* IBAiManager.swift in Sources */,
+ 18204C022C199D0E00F94E69 /* IBFileSender+Ota.swift in Sources */,
+ 18EC6E282BFF4C6D00757CA8 /* IBPhoneInfo.swift in Sources */,
+ 18EF69532C45136800AA0A36 /* IBPairling.swift in Sources */,
+ 18C9589F2C086A9D006A5DE8 /* IBHistorySleep.swift in Sources */,
+ 180220CE2C2D5D560090D34A /* IBAiResponse.swift in Sources */,
+ 8A65F95F2BABEC4D005C1EA4 /* IBClock.swift in Sources */,
+ 8A0A39D22B980E4C006A3B18 /* JSON.swift in Sources */,
+ 18E7AC962BF5E39900CB9393 /* IBHeartRate.swift in Sources */,
+ 18C9589B2C082FA7006A5DE8 /* IBWeather.swift in Sources */,
+ 8A0A39D82B980F58006A3B18 /* UTF8.swift in Sources */,
+ 181441D12C11575D00CCDD46 /* IBOta.swift in Sources */,
+ 18E7AC922BF44C6800CB9393 /* IBSleep.swift in Sources */,
+ 8A0A39CB2B980CFC006A3B18 /* Logger.swift in Sources */,
+ 189C158F2C05F85200676A80 /* IBHistoryStep.swift in Sources */,
+ 8A5822D12BA1C80700590AB6 /* LRC.swift in Sources */,
+ 8A0A39DA2B981037006A3B18 /* Date.swift in Sources */,
+ 181441D52C116AE500CCDD46 /* IBDial.swift in Sources */,
+ 18993AA22C073FED006742D2 /* IBBloodOxygen.swift in Sources */,
+ 3FFE56E72CD624D6000C7415 /* HsFileAgentSdk.swift in Sources */,
+ 18884C462C12A4EA00E01458 /* IBEpo.swift in Sources */,
+ 8A87FBB32BAA7F3900096761 /* IBLanguage.swift in Sources */,
+ 18EF69552C45266D00AA0A36 /* IBReceiveManager.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 8A1DFF4B2B9EB4F800B735FF /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8A878FE02BA93CF1008C7F26 /* Datas.swift in Sources */,
+ 8A34B92B2BA2D446000F66BD /* BaseViewController.swift in Sources */,
+ 8A76B6092BA84C2B0016F2AB /* AbilityViewController.swift in Sources */,
+ 3FFE56DF2CD6157F000C7415 /* IBManager.swift in Sources */,
+ 18EF697A2C47A86D00AA0A36 /* IBTools.swift in Sources */,
+ 3FC0CFCE2CD8634200F97933 /* Logger.swift in Sources */,
+ 3FFE56E02CD61584000C7415 /* IBBluetoothManager.swift in Sources */,
+ 8A1DFF562B9EB4F800B735FF /* ViewController.swift in Sources */,
+ 8A1DFF522B9EB4F800B735FF /* AppDelegate.swift in Sources */,
+ 8A34B9292BA2AB86000F66BD /* LogViewController.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 8A9F6AC52BA05CB500DA520F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 8A0A39BA2B980BEF006A3B18 /* IOTBluetooth */;
+ targetProxy = 8A9F6AC42BA05CB500DA520F /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 8A1DFF572B9EB4F900B735FF /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 8A1DFF582B9EB4F900B735FF /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "<group>";
+ };
+ 8A1DFF5C2B9EB4F900B735FF /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 8A1DFF5D2B9EB4F900B735FF /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 8A0A39C02B980BEF006A3B18 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.5;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 8A0A39C12B980BEF006A3B18 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.5;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ VALIDATE_PRODUCT = YES;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 8A0A39C32B980BEF006A3B18 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = M33X987285;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_MODULE_VERIFIER = YES;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Example",
+ "$(PROJECT_DIR)/IOTBluetooth",
+ );
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACH_O_TYPE = staticlib;
+ MARKETING_VERSION = 1.0;
+ MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
+ PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 4.2;
+ TARGETED_DEVICE_FAMILY = 1;
+ };
+ name = Debug;
+ };
+ 8A0A39C42B980BEF006A3B18 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = M33X987285;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_MODULE_VERIFIER = YES;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Example",
+ "$(PROJECT_DIR)/IOTBluetooth",
+ );
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACH_O_TYPE = staticlib;
+ MARKETING_VERSION = 1.0;
+ MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
+ PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 4.2;
+ TARGETED_DEVICE_FAMILY = 1;
+ };
+ name = Release;
+ };
+ 8A1DFF602B9EB4F900B735FF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1001;
+ DEVELOPMENT_TEAM = M33X987285;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Example",
+ );
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = Example/Info.plist;
+ INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "扫描智能手表需要开启蓝牙功能";
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
+ INFOPLIST_KEY_UIMainStoryboardFile = Main;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
+ INFOPLIST_KEY_UIUserInterfaceStyle = Light;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.5;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth.Example;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 1;
+ };
+ name = Debug;
+ };
+ 8A1DFF612B9EB4F900B735FF /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1001;
+ DEVELOPMENT_TEAM = M33X987285;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Example",
+ );
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = Example/Info.plist;
+ INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "扫描智能手表需要开启蓝牙功能";
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
+ INFOPLIST_KEY_UIMainStoryboardFile = Main;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
+ INFOPLIST_KEY_UIUserInterfaceStyle = Light;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.5;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.thisiot.IOTBluetooth.Example;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = 1;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 8A0A39B52B980BEF006A3B18 /* Build configuration list for PBXProject "IOTBluetooth" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 8A0A39C02B980BEF006A3B18 /* Debug */,
+ 8A0A39C12B980BEF006A3B18 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 8A0A39C22B980BEF006A3B18 /* Build configuration list for PBXNativeTarget "IOTBluetooth" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 8A0A39C32B980BEF006A3B18 /* Debug */,
+ 8A0A39C42B980BEF006A3B18 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 8A1DFF622B9EB4F900B735FF /* Build configuration list for PBXNativeTarget "Example" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 8A1DFF602B9EB4F900B735FF /* Debug */,
+ 8A1DFF612B9EB4F900B735FF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 8A0A39B22B980BEF006A3B18 /* Project object */;
+}
--- /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"?>
+<Scheme
+ LastUpgradeVersion = "1500"
+ version = "1.7">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "8A1DFF4E2B9EB4F800B735FF"
+ BuildableName = "Example.app"
+ BlueprintName = "Example"
+ ReferencedContainer = "container:IOTBluetooth.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ shouldAutocreateTestPlan = "YES">
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "8A1DFF4E2B9EB4F800B735FF"
+ BuildableName = "Example.app"
+ BlueprintName = "Example"
+ ReferencedContainer = "container:IOTBluetooth.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "8A1DFF4E2B9EB4F800B735FF"
+ BuildableName = "Example.app"
+ BlueprintName = "Example"
+ ReferencedContainer = "container:IOTBluetooth.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
--- /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
+//
+// HsFileAgentSdk.swift
+// IOTBluetooth
+//
+// Created by liangshi on 2024/11/2.
+//
+
+import Foundation
+
+public typealias IBCompleteHandler = (_ data: Data) -> Void
+/// SDK 管理类
+public class HsFileAgentSdk:NSObject {
+ public static let shared = HsFileAgentSdk()
+
+ private override init() {
+ super.init()
+ }
+
+ /// 初始化工作
+ public func initSDK() {
+ Logger.log("MixSdk version:",HsFileAgentSdk.version)
+ }
+
+ /// 日志
+ public var logs: [String] { Logger.logs }
+
+ /// SDK 版本号
+ public let version: String = "1.0.0"
+ /// 默认为 false
+ public var isDebug: Bool = false {
+ didSet { Logger.isDebug = isDebug }
+ }
+
+ /// 清空日志
+ public func cleanLog(){
+ Logger.logs.removeAll()
+ }
+
+ /// 文件发送实例
+ internal weak var fileSender: IBFileSender?
+
+
+
+ public func sendToSdk(_ data:Data){
+
+ }
+
+ public func sdkSendToDevice(_ callback:IBCompleteHandler){
+
+ }
+
+ func sendToDevice(fileData:Data){
+
+ }
+}
+
+
+extension HsFileAgentSdk {
+
+}
--- /dev/null
+//
+// IBAbility.swift
+// IOTBluetooth
+//
+// Created by yaozuopan on 2024/3/13.
+//
+
+import Foundation
+/// 主服务
+let serviceUUID: String = "18E0"
+/// Notify 特征值
+let notifyCharacteristicUUID: String = "2AE5"
+/// Read 特征值
+let readCharacteristicUUID: String = "2AE2"
+/// Write 特征值(发送字节数≤20)
+let writeCharacteristicUUID: String = "2AE3"
+/// Write 特征值(发送字节数>20)
+let writeBigCharacteristicUUID: String = "2AE4"
+/// Write 特征值(发送文件OAT、MP3等)
+let writeFileCharacteristicUUID: String = "2AE0"
+
+/// 功能指令
+public class IBAbility {
+
+ /// 命令类型
+ let type: `Type`
+
+ /// 命令ID
+ private(set) var id: UInt8 = 0x0
+
+ /// 指定解析器
+ private(set) var parser: IBParser.Type!
+
+ /// 指定参数检查器
+ private(set) var checker: IBChecker.Type!
+
+ /// 服务特征
+ private(set) var characteristicUUID: String!
+
+ private init(_ type: `Type`) {
+ self.type = type
+ }
+
+ private func id(_ value: UInt8) -> Self {
+ self.id = value
+ return self
+ }
+
+ private func parser<T: IBParser>(_ parser: T.Type) -> Self {
+ self.parser = parser
+ return self
+ }
+
+ private func checker<T: IBChecker>(_ checker: T.Type) -> Self {
+ self.checker = checker
+ return self
+ }
+
+ private func characteristic(_ uuid: String) -> Self {
+ characteristicUUID = uuid
+ return self
+ }
+
+
+
+ /// 短指令(不大于20个字节)
+ private static var short: IBAbility { IBAbility(.short).characteristic(writeCharacteristicUUID) }
+ /// 长指令(大于20个字节)
+ private static var long: IBAbility { IBAbility(.long).characteristic(writeBigCharacteristicUUID) }
+ /// 文件指令(用来发送文件,比如表盘、MP3、OTA等)
+ private static var file: IBAbility { IBAbility(.file).characteristic(writeFileCharacteristicUUID) }
+ /// ai指令
+ private static var ai: IBAbility { IBAbility(.ai).characteristic(writeCharacteristicUUID) }
+
+ public var description: String { "[\(String(format: "0x%02X, 0x%02X", type.rawValue, id))] \(characteristicUUID!)" }
+
+ func isAnswer(_ data: Data) -> Bool {
+ let answerTypeValue = data.bytes[1]
+ let answerID = data.bytes[2]
+
+ return type.rawValue == answerTypeValue && id == answerID
+ }
+}
+
+extension IBAbility {
+ enum `Type`: UInt8 {
+ /// 短指令
+ case short = 0x01
+ /// 长指令
+ case long = 0x02
+ /// 文件指令(用来发送文件,比如表盘、MP3、OTA等)
+ case file = 0x04
+ /// Ai指令
+ case ai = 0x05
+ }
+}
+
+public extension IBAbility {
+ /// 设置用户数据
+ /// - Parameter userInfo: see ``IBUserInfo``
+ static let userData = IBAbility.long.id(0x01).parser(IBGeneralParser.self).checker(IBUserInfoChecker.self)
+
+ /// 关于本机内容
+ static let about = IBAbility.short.id(0x03).parser(IBAblutParser.self)
+
+ /// 切换表盘
+ /// - Parameter dialID:Int
+ /// 表盘id
+ static let switchDial = IBAbility.short.id(0x02).parser(IBSwitchDialParser.self).checker(IBSwitchDialChecker.self)
+
+ /// APP删除表盘
+ /// - Parameter dialID:Int \
+ /// 表盘id
+ static let deleteDial = IBAbility.short.id(0x1B).parser(IBGeneralParser.self).checker(IBSwitchDialChecker.self)
+
+ /// 发送当前表盘ID
+ /// - Returns: Int \
+ /// Read from ``IBResponse``.value
+ static let fetchCurrentDialId = IBAbility.short.id(0x17).parser(IBFetchCurrentDialParser.self)
+
+ /// 查找手表
+ /// - Parameter action:
+ /// 0 = 停止查找 \
+ /// 1 = 开始查找
+ static let find = IBAbility.short.id(0x04).parser(IBFindParser.self).checker(IBActionChecker.self)
+
+ /// 开关消息推送功能
+ /// - Parameter action:
+ /// 0 = 关闭消息 \
+ /// 1 = 开启消息
+ static let message = IBAbility.short.id(0x08).parser(IBGeneralParser.self).checker(IBActionChecker.self)
+
+ /// 开启指定运动模式
+ /// - Parameter motionMode: see ``IBMotionMode``
+ static let startSport = IBAbility.short.id(0x0A).parser(IBGeneralParser.self).checker(IBSportChecker.self)
+
+ /// 结束运动模式
+ static let endSport = IBAbility.short.id(0x0B).parser(IBGeneralParser.self)
+
+ /// 设置训练实时数据采样频率
+ /// - Parameter value:Int \
+ /// 单位(秒)
+ static let trainFrequency = IBAbility.short.id(0x14).parser(IBGeneralParser.self).checker(IBTrainFrequencyChecker.self)
+
+ /// 训练暂停/恢复
+ /// - Parameter action:
+ /// 0 = 暂停 \
+ /// 1 = 恢复
+ static let trainPauseResume = IBAbility.short.id(0x16).parser(IBGeneralParser.self).checker(IBActionChecker.self)
+
+ /// 设置语言
+ /// - Parameter language: see ``IBLanguage``
+ static let language = IBAbility.short.id(0x0E).parser(IBGeneralParser.self).checker(IBLanguageChecker.self)
+
+ /// 获取手表心率参数
+ /// - Returns: ``IBHeartRate`` \
+ /// Read from ``IBResponse``.value
+ static let fetchHeartRate = IBAbility.short.id(0x1E).parser(IBHeartRateParser.self)
+
+ /// 设置手表心率参数
+ /// - Parameter heartRate: see ``IBHeartRate``
+ static let heartRate = IBAbility.long.id(0x12).parser(IBGeneralParser.self).checker(IBHeartRateChecker.self)
+
+ /// 获取手表血氧设置
+ /// - Returns: ``IBBloodOxygen`` \
+ /// Read from ``IBResponse``.value
+ static let fetchBloodOxygen = IBAbility.short.id(0x1F).parser(IBBloodOxygenParser.self)
+
+ /// 设置手表血氧参数
+ /// - Parameter heartRate: see ``IBBloodOxygen``
+ static let bloodOxygen = IBAbility.long.id(0x13).parser(IBGeneralParser.self).checker(IBBloodOxygenChecker.self)
+
+ /// 获取实时心率(实时训练数据)
+ /// - Returns: ``IBTrainingData`` \
+ /// Read from ``IBResponse``.value
+ static let currentHeartRate = IBAbility.short.id(0x0F).parser(IBTrainingDataParser.self)
+
+ /// 开启/关闭自动获取实时心率
+ /// - Parameter action:
+ /// 0 = 关闭自动获取 \
+ /// 1 = 开启自动获取
+ static let heartRateMode = IBAbility.short.id(0x11).parser(IBGeneralParser.self).checker(IBActionChecker.self)
+
+ /// 设置最高心率提醒
+ /// - Parameter value:Int \
+ /// 最高不超过 250 ,如果需要关闭最高心率提醒,可将最高心率设为255,开启则设正常值即可。
+ static let heartRateHigh = IBAbility.short.id(0x15).parser(IBGeneralParser.self).checker(IBHeartRateHighChecker.self)
+
+ /// 手势拍照
+ /// - Parameter action:
+ /// 0 = 退出手势拍照 \
+ /// 1 = 进入手势拍照
+ static let takePhoto = IBAbility.short.id(0x23).parser(IBTakePhotoParser.self).checker(IBActionChecker.self)
+
+ /// 恢复出厂设置
+ static let restore = IBAbility.short.id(0x25).parser(IBGeneralParser.self)
+
+ /// 重新启动
+ static let reboot = IBAbility.short.id(0x26).parser(IBGeneralParser.self)
+
+ /// 关机
+ static let shutdown = IBAbility.short.id(0x27).parser(IBGeneralParser.self)
+
+ /// 获取手表电池电量。
+ /// - Returns: Int \
+ /// Read from ``IBResponse``.value
+ static let fetchBattery = IBAbility.short.id(0x07).parser(IBBatteryParser.self)
+
+ /// 获取闹钟设置
+ /// - Returns: ``[IBClockInfo]`` \
+ /// Read from ``IBResponse``.items
+ static let fetchClock = IBAbility.short.id(0x01).parser(IBClockParser.self)
+
+ /// 发送闹钟设置
+ /// - Parameter clock: see ``IBClock``
+ static let setClock = IBAbility.long.id(0x21).parser(IBSetClockParser.self).checker(IBClockChecker.self)
+
+ /// 获取所有表盘UID
+ /// - Returns: [Int] \
+ /// Read from ``IBResponse``.items
+ static let fetchDials = IBAbility.short.id(0x2A).parser(IBFetchDialsParser.self)
+
+ /// APP获取某表盘ID是否已安装
+ /// - Parameter value:Int
+ /// - Returns: Int
+ /// 1 = 已安装 \
+ /// 2 = 未安装 \
+ /// Read from ``IBResponse``.value
+ static let checkDialID = IBAbility.long.id(0x0D).parser(IBCheckDialParser.self).checker(IBSwitchDialChecker.self)
+
+ /// 获取当前手表状态
+ /// - Returns: Int \
+ /// 0 = 空闲状态 \
+ /// 9 = USB存储状态 \
+ /// 10 = 运动训练状态 \
+ /// Read from ``IBResponse``.value
+ static let fetchWatchState = IBAbility.short.id(0x19).parser(IBWatchStateParser.self)
+
+ /// 获取手表屏幕参数
+ /// - Returns: ``IBScreen`` \
+ /// Read from ``IBResponse``.value
+ static let fetchScreen = IBAbility.short.id(0x1A).parser(IBScreenParser.self)
+
+ /// 设置手表屏幕参数
+ /// - Parameter screen: see ``screen``
+ static let screen = IBAbility.long.id(0x0E).parser(IBGeneralParser.self).checker(IBScreenChecker.self)
+
+ /// 获取手表声音与震动参数
+ /// - Returns: ``IBVibrateSound`` \
+ /// Read from ``IBResponse``.value
+ static let fetchVibrateSound = IBAbility.short.id(0x1C).parser(IBVibrateSoundParser.self)
+
+ /// APP设置手表声音与震动参数
+ /// - Parameter vibrateSound: see ``IBVibrateSound``
+ static let vibrateSound = IBAbility.long.id(0x0F).parser(IBGeneralParser.self).checker(IBVibrateSoundChecker.self)
+
+
+ /// 设置手表辅助功能参数
+ /// - Parameter auxiliary: see ``IBAuxiliary``
+ static let auxiliary = IBAbility.long.id(0x11).parser(IBGeneralParser.self).checker(IBAuxiliaryChecker.self)
+
+ /// 获取手表辅助功能参数
+ /// - Returns: IBAuxiliary \
+ /// Read from ``IBResponse``.value
+ static let fetchAuxiliary = IBAbility.short.id(0x1D).parser(IBFetchAuxiliaryParser.self)
+
+ /// 获取目标步数设置
+ /// - Returns: ``IBTargetStep`` \
+ /// Read from ``IBResponse``.value
+ static let fetchTargetStep = IBAbility.short.id(0x2B).parser(IBTargetStepParser.self)
+
+ /// 设置目标步数
+ /// - Parameter targetStep: see ``IBTrainTarget``
+ static let targetStep = IBAbility.long.id(0x16).parser(IBGeneralParser.self).checker(IBTargetStepChecker.self)
+
+ /// 获取其他目标设置
+ /// - Returns: ``IBOtherTarget`` \
+ /// Read from ``IBResponse``.value
+ static let fetchOtherTarget = IBAbility.short.id(0x2C).parser(IBOtherTargetParser.self)
+
+ /// 设置其他目标
+ /// - Parameter targetOther: see ``IBOtherTarget``
+ static let targetOther = IBAbility.long.id(0x17).parser(IBGeneralParser.self).checker(IBOtherTargetChecker.self)
+
+ /// 获取睡眠设置
+ /// - Returns: ``IBSleep`` \
+ /// Read from ``IBResponse``.value
+ static let fetchSleep = IBAbility.short.id(0x29).parser(IBSleepParser.self)
+
+ /// 设置睡眠
+ /// - Parameter sleep: see ``IBSleep``
+ static let sleep = IBAbility.long.id(0x15).parser(IBGeneralParser.self).checker(IBSleepChecker.self)
+
+ /// 获取手表喝水提醒设置
+ /// - Returns: Int \
+ /// 0 = 关闭 \
+ /// 1 = 开启 \
+ /// Read from ``IBResponse``.value
+ static let fetchWater = IBAbility.short.id(0x24).parser(IBWaterParser.self)
+
+ /// 设置喝水提醒
+ /// - Parameter water: see ``IBWater``
+ static let water = IBAbility.long.id(0x14).parser(IBGeneralParser.self).checker(IBWaterChecker.self)
+
+ /// 设置手表公英制单位
+ /// - Parameter value:Int \
+ /// 0 = 公制 \
+ /// 1 = 英制
+ static let unit = IBAbility.short.id(0x28).parser(IBGeneralParser.self).checker(IBValueChecker.self)
+
+ /// 设置训练目标
+ /// - Parameter trainTarget:see ``IBTrainTarget``
+ static let trainTarget = IBAbility.long.id(0x04).parser(IBGeneralParser.self).checker(IBTrainTargetChecker.self)
+
+ // MARK: - 同步数据
+ /// 同步心率历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryHeartRateItem] \
+ /// Read from ``IBResponse``.items
+ static let syncHeartRate = IBAbility.short.id(0x40).parser(IBSyncHeartRateParser.self).checker(IBValueChecker.self)
+
+ /// 同步血氧历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryBloodOxygenItem] \
+ /// Read from ``IBResponse``.items
+ static let syncBloodOxygen = IBAbility.short.id(0x41).parser(IBSyncBloodOxygenParser.self).checker(IBValueChecker.self)
+
+ /// 同步历史步数
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryStepItem] \
+ /// Read from ``IBResponse``.items
+ static let syncStepCount = IBAbility.short.id(0x43).parser(IBSyncStepParser.self).checker(IBValueChecker.self)
+
+ /// 同步燃脂时间历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryBurningItem] \
+ /// Read from ``IBResponse``.items
+ static let syncFatBurning = IBAbility.short.id(0x44).parser(IBSyncBurningParser.self).checker(IBValueChecker.self)
+
+ /// 同步站立历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryStandItem] \
+ /// Read from ``IBResponse``.items
+ static let syncStand = IBAbility.short.id(0x45).parser(IBSyncStandParser.self).checker(IBValueChecker.self)
+
+ /// 同步卡路里历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryCalorieItem] \
+ /// Read from ``IBResponse``.items
+ static let syncCalorie = IBAbility.short.id(0x46).parser(IBSyncCalorieParser.self).checker(IBValueChecker.self)
+
+ /// 同步距离历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryDistanceItem] \
+ /// Read from ``IBResponse``.items
+ static let syncDistance = IBAbility.short.id(0x47).parser(IBSyncDistanceParser.self).checker(IBValueChecker.self)
+
+ /// 同步基础训练历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistoryBaseTrainingItem] \
+ /// Read from ``IBResponse``.items
+ static let syncTrain = IBAbility.short.id(0x49).parser(IBSyncTrainParser.self).checker(IBValueChecker.self)
+
+ /// 同步睡眠历史数据
+ /// - Parameter value:
+ /// 0 = 今天 \
+ /// 1 = 前1天 \
+ /// 2 = 前2天 \
+ /// 3 = 前3天 \
+ /// 依此类推,取值范围0~29。
+ /// - Returns: [IBHistorySleepItem] \
+ /// Read from ``IBResponse``.items
+ static let syncSleep = IBAbility.short.id(0x4B).parser(IBSyncSleepParser.self).checker(IBValueChecker.self)
+
+ /// 同步路径坐标集历史数据
+ /// - Parameter value:开始训练时间戳
+ /// - Returns: [IBHistoryGpsItem] \
+ /// Read from ``IBResponse``.items
+ static let syncGPS = IBAbility.long.id(0x4F).parser(IBSyncGpsParser.self).checker(IBSyncGpsChecker.self)
+
+ /// 发送同步数据结束消息
+ static let sendSyncEnd = IBAbility.short.id(0x2E).parser(IBGeneralParser.self)
+
+ /// 获取SD卡剩余空间
+ /// - Returns: Int \
+ /// Read from ``IBResponse``.value
+ static let disk = IBAbility.short.id(0x05).parser(IBDiskParser.self)
+
+ /// 发送手机信息
+ /// - Returns: Int \
+ /// 0 = 指令校验失败 \
+ /// 1 = 手表未与任何手机绑定 \
+ /// 2 = 手表已与该手机绑定 \
+ /// 3 = 手表已与其他手机绑定 \
+ /// Read from ``IBResponse``.value
+ static let sendPhone = IBAbility.long.id(0x10).parser(IBSendPhoneParser.self).checker(IBSendPhoneChecker.self)
+
+ /// 发送当前时间
+ /// - Parameter sendTime: see ``IBSendTime``
+ static let sendTime = IBAbility.long.id(0x02).parser(IBGeneralParser.self).checker(IBSendTimeChecker.self)
+
+ /// 发送解除配对消息
+ static let unpair = IBAbility.short.id(0x10).parser(IBGeneralParser.self)
+
+ /// 发送取消解除配对消息
+ static let sendUnpair = IBAbility.short.id(0x2D).parser(IBGeneralParser.self)
+
+ /// 发送天气数据
+ /// - Parameter weather: see ``IBWeather``
+ static let sendWeather = IBAbility.long.id(0x20).parser(IBGeneralParser.self).checker(IBWeatherChecker.self)
+
+ /// 发送通讯录
+ /// - Parameter addressBook: see ``IBAddressBook``
+ /// 联系人个数0~20
+ static let addressBook = IBAbility.long.id(0x0B).parser(IBGeneralParser.self).checker(IBAddressBookChecker.self)
+
+ // MARK: - 文件
+
+ /// 发送MP3文件信息
+ /// - Parameter mp3: see ``IBMp3File``
+ static let mp3 = IBAbility.file.id(0x21).parser(IBGeneralParser.self).checker(IBMp3Checker.self)
+
+// /// 发送自定义表盘文件汇总信息
+// /// - Parameter customDial: see ``IBCustomDial``
+// static let customDial = IBAbility.file.id(0x22).parser(IBGeneralParser.self).checker(IBCustomDialChecker.self)
+
+ /// 发送表盘文件汇总信息
+ /// - Parameter dial: see ``IBDial``
+ static let dial = IBAbility.file.id(0x22).parser(IBGeneralParser.self).checker(IBDialChecker.self)
+
+ /// 发送单个表盘文件信息
+ /// - Parameter dialFile: see ``IBDailFile``
+ internal static let dialFile = IBAbility.file.id(0x24).parser(IBGeneralParser.self).checker(IBDialFileChecker.self)
+
+ /// 发送文件校验码
+ /// - Parameter value:Int
+ internal static let fileCheckCode = IBAbility.file.id(0x23).parser(IBGeneralParser.self).checker(IBValueChecker.self)
+
+ /// 删除指定文件
+ /// - Parameter deleteFile: see ``IBDeleteFile``
+ static let deleteFile = IBAbility.long.id(0x22).parser(IBGeneralParser.self).checker(IBDeleteFileChecker.self)
+
+ /// 删除指定类型所有文件(当前只支持 MP3 和 表盘)
+ /// - Parameter file: see ``IBFile``
+ static let deleteFiles = IBAbility.short.id(0x22).parser(IBGeneralParser.self).checker(IBFileChecker.self)
+
+ /// 发送OTA文件信息
+ /// - Parameter ota: see ``IBOtaFile``
+ static let ota = IBAbility.file.id(0x20).parser(IBGeneralParser.self).checker(IBOtaChecker.self)
+
+
+ /// 发送EPO文件信息
+ /// - Parameter epo: see ``[IBEpo]``
+ internal static let epo = IBAbility.file.id(0x28).parser(IBGeneralParser.self).checker(IBEpoChecker.self)
+
+ /// 撤销当前文件传输
+ /// - Parameter file: see ``IBFile``
+ static let revokeFileSend = IBAbility.short.id(0x20).parser(IBGeneralParser.self).checker(IBFileChecker.self)
+
+ /// 获取指定类型所有文件(当前只支持 MP3 和 表盘)
+ /// - Parameter file: see ``IBFile``
+ /// - Returns: IBFetchFile \
+ /// Read from ``IBResponse``.value
+ static let fetchFiles = IBAbility.short.id(0x21).parser(IBFetchFilesParser.self).checker(IBFileChecker.self)
+
+
+ /// 询问AI模型是否可用回执
+ /// - Parameter aiValid:IBAiValid
+ internal static let aiValid = IBAbility(.ai).characteristic(writeBigCharacteristicUUID).id(0x06).parser(IBGeneralParser.self).checker(IBAiValidChecker.self)
+
+ /// 发送Ai准备状态回执
+ /// - Parameter value:Int
+ /// 0 = 失败 \
+ /// 1 = 成功 \
+ internal static let aiReady = IBAbility.ai.id(0x05).parser(IBGeneralParser.self).checker(IBValueChecker.self)
+
+ /// 获取Ai录音文件
+ /// - Parameter value:Data \ AI录音文件内容(pcm格式)
+ /// Read from ``IBResponse``.value
+ internal static let fetchAiAudio = IBAbility.short.id(0x34).parser(IBfetchAiAudioParser.self)
+
+ /// 发送Ai回应内容
+ /// - Parameter aiResponse: see ``IBAiResponse``
+ internal static let aiResponse = IBAbility.long.id(0x1f).parser(IBGeneralParser.self).checker(IBAiReponseChecker.self)
+}
+
+extension IBAbility {
+
+
+ //
+ //
+ // // MARK: - 私有
+ //
+ // /// 获取当前数据(jSON格式)【不使用】
+ // static let var681B9E92 = IBAbility.short.id(0x06)
+ // /// 开关多媒体功能(播放手机音乐、通话等)【不使用】
+ // static let var681254AC = IBAbility.short.id(0x09)
+ // /// 进入工厂测试模式【不使用】
+ // static let varD3D21DC8 = IBAbility.short.id(0x30)
+ // /// 同步压力历史数据【不使用】
+ // static let varA2A25CD8 = IBAbility.short.id(0x42)
+ //
+ // /// 修改手表蓝牙名称【未使用】
+ // static let var8FA46D73 = IBAbility.long.id(0x03)
+ // /// 同步基础训练实时心率记录【不使用】
+ // static let var6CFBF636 = IBAbility.long.id(0x4B)
+ // /// 同步当天任意时间之后的基础训练记录【不使用】
+ // static let var4D0264A9 = IBAbility.long.id(0x4D)
+ // /// APP发送靶心率区间值给手表(暂不用)
+ // static let var7EE39B21 = IBAbility.long.id(0x0C)
+ //
+ // /// 发送模块升级信息(04,26)【不使用】
+ // static let var1FFA68BC = IBAbility.file.id(0x26)
+ // /// 发送模块单个文件升级信息(04,27)【不使用】
+ // static let var9AD723C3 = IBAbility.file.id(0x27)
+ // /// 发送EPO文件信息(04,25)(旧版,不用)
+ // static let var4F791698 = IBAbility.file.id(0x25)
+}
--- /dev/null
+////
+//// IBAiManager.swift
+//// IOTBluetooth
+////
+//// Created by incar on 2024/7/15.
+////
+//
+//import Foundation
+////import MixSdk
+//class IBAiManager{
+// static let shared = IBAiManager()
+// var audioData = Data()
+//
+// var reply = ""
+//
+// init(){
+// MixSdk.shared.isDebug = true
+// setupChatCallBack()
+// }
+//
+// func aiValid(_ data:Data){
+// let result = IBGeneralParser.dataPacket(data)
+// let code = result.int
+// let desc = ["文心一言","ChatGPT","KIMI",":豆包"]
+// Logger.log("询问模型是否可用:",desc[code])
+// let model = IBAiValid()
+// model.isValid = true
+// if code == 1 {
+// model.key = "sk-proj-04ogvrZQqyaiv1OWw8OZT3BlbkFJG9Gyr8oFvRdmr3oXQNbX"
+// }
+// IBSender()
+// .ability(IBAbility.aiValid)
+// .parameters([.aiValid(model)])
+// .response { result in
+//
+// }
+//
+// }
+// func aiRequest(_ data:Data){
+// let result = IBGeneralParser.dataPacket(data)
+// let code = result.int
+// let desc = ["文心一言","ChatGPT","KIMI",":豆包"]
+// Logger.log("使用模型:",desc[code])
+// IBSender()
+// .ability(IBAbility.aiReady)
+// .parameters([.value(1)])
+// .response {[weak self] result in
+// self?.fectchAiAudio()
+// }
+//
+//
+// var platform = MSPlatform.doubao
+// var key = ""
+// if code == 0 {
+// platform = .yiyan
+// }
+// else if code == 1 {
+// platform = .chatGPT
+// key = "sk-proj-04ogvrZQqyaiv1OWw8OZT3BlbkFJG9Gyr8oFvRdmr3oXQNbX"
+//// let gptModel = "gpt-4-turbo"
+//// MixSdk.shared.chatGpt.changeModel(model:gptModel )
+// }
+// else if code == 2 {
+// platform = .kimi
+// }
+// else if code == 3 {
+// platform = .doubao
+// }
+// MixSdk.shared.setPlatform(platform,key: key)
+// }
+// func fectchAiAudio(){
+// IBSender()
+// .ability(IBAbility.fetchAiAudio)
+// .parameters([])
+// .response {[weak self] result in
+// guard let self = self else {return}
+// if result.isSuccess,let data = result.value as? Data{
+// receiveAiData(sbcData: data)
+// }
+// else{
+// Logger.log(result)
+// let model = IBAiResponse()
+// model.type = 0
+// sendRelpy(model: model)
+// }
+// }
+// }
+//
+//
+//}
+//extension IBAiManager{
+// func receiveAiData(sbcData:Data){
+// let pcmData = MSAudioConvert.sbcTopcm(sbcData)
+// if pcmData.count == 0{
+// Logger.log("sbc2pcm失败")
+// let model = IBAiResponse()
+// model.type = 0
+// sendRelpy(model: model)
+// return
+// }
+// guard let pcmPath = IBTools.saveToFile(data: pcmData) else {return}
+// MixSdk.shared.stt.sentenceRecognize(filePath: pcmPath,format:"pcm",sample_rate:"16000",lang: "zh-cn") {[weak self] text, error in
+// guard let self = self else {return}
+// if let text = text,text.count > 0{
+// Logger.log(text)
+// let model = IBAiResponse()
+// model.type = 1
+// model.value = text
+// sendRelpy(model: model)
+//
+// MixSdk.shared.chat.send(content: text)
+// }
+// else{
+// Logger.log(error ?? "")
+// let model = IBAiResponse()
+// model.type = 0
+// sendRelpy(model: model)
+// }
+// }
+// }
+//
+//
+// func setupChatCallBack(){
+// let chat = MixSdk.shared.chat
+// chat.callBack = {[weak self] model in
+// guard let self = self else {return}
+// print("\(model.type) : \(model.content ?? "")")
+//
+// if model.type == "start" {
+// reply = ""
+// }
+// else if model.type == "end" {
+//// let model = IBAiResponse()
+//// model.type = 2
+//// model.value = reply
+//// sendRelpy(model: model)
+// }
+// else if model.type == "middle"{
+//// reply += model.content ?? ""
+// let data = IBAiResponse()
+// data.type = 2
+// data.value = model.content ?? ""
+// sendRelpy(model: data)
+// }
+// }
+// }
+//
+// func sendRelpy(model:IBAiResponse){
+// IBSender()
+// .ability(IBAbility.aiResponse)
+// .parameters([.aiResponse(model)])
+// .response { result in
+// Logger.log(result)
+// }
+// }
+//}
+//
+//extension IBAiManager{
+// func loginAction() {
+// MixSdk.shared.isDebug = true
+// let pwd = "aWGKrBWaijsKD5eEsVVU5rjWPI1sVy"//注册获得的密码
+// self.startLogin(password:pwd)
+// }
+// func startLogin(password:String) {
+// MixSdk.shared.isDebug = true
+// let deviceId = "281106AFB0F7"
+// let productKey = "HHosiYUNdG42t1cTgKOzIUPOvAVMI2QT"
+//
+// MixSdk.shared.auth.login(productKey:productKey,deviceId: deviceId, password: password){[weak self] token,error in
+// if let err = error{
+// if case .reponseError(let code,let message) = err,code == 50021{
+// //50021是账号或密码错误.可以在这里调用注册,重新注册后,之前的密码会被覆盖,APP更新保存的新密码
+// self?.registerAction()
+// }
+// }
+// else{
+// print(token ?? "")
+// }
+// }
+// }
+// @objc func registerAction() {
+// let deviceId = "281106AFB0F7"
+// let productId = "pafOCKLiUbeVHbdZlddH"
+// let productKey = "HHosiYUNdG42t1cTgKOzIUPOvAVMI2QT"
+// MixSdk.shared.auth.register(productId: productId, productKey: productKey, deviceId: deviceId){[weak self] password,error in
+// if let pwd = password{
+// print(pwd)
+// //deviceId和password APP要确保这个一一对应。
+// self?.startLogin(password: pwd)
+// }
+// else{
+// print(error!)
+// }
+// }
+// }
+//}
--- /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 IBOtaChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .ota(value) = parameters[.ota] else { fatalError("缺少参数: ota") }
+ guard let data = value.encode else { fatalError("json解析异常: ota") }
+ Logger.log(value.jsonString ?? "")
+ return data.bytes
+ }
+}
+
+struct IBAiReponseChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .aiResponse(value) = parameters[.aiResponse] else { fatalError("缺少参数: aiReponse") }
+ Logger.log(value.description)
+
+ var data = [value.type.byte]
+ if let value = value.value.data(using: .utf8),!value.isEmpty {
+ data += value.count.byte2
+ data += value.bytes
+ }
+ return data
+ }
+}
+struct IBAiValidChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .aiValid(value) = parameters[.aiValid] else { fatalError("缺少参数: aiValid") }
+ Logger.log(value.description)
+
+ var data = value.isValid ? [1.byte]:[0.byte]
+ if value.isValid == true,let value = value.key?.data(using: .utf8),!value.isEmpty {
+ data += value.bytes
+ }
+ return data
+ }
+}
+struct IBTargetStepChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .targetStep(value) = parameters[.targetStep] else { fatalError("缺少参数: targetStep") }
+ Logger.log(value.description)
+ return value.value.byte4 + [value.ring.byte] + [value.vibrate.byte] + [value.notice.byte]
+ }
+}
+
+struct IBSleepChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .sleep(value) = parameters[.sleep] else { fatalError("缺少参数: sleep") }
+ Logger.log(value.description)
+ if value.sleepTimeSet == 1,let sleep = value.sleepTime,let getUp = value.getUpTime {
+ return [value.sleepTimeSet.byte] + [sleep.hour.byte] + [sleep.minute.byte] + [getUp.hour.byte] + [getUp.minute.byte] + [value.alertBeforeMin20.byte]
+ }
+ return [value.sleepTimeSet.byte]
+ }
+}
+struct IBWaterChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+// guard case let .water(value) = parameters[.water] else { fatalError("缺少参数: water") }
+// Logger.log(value.description)
+//
+// var data = [value.alertState.byte]
+// if value.alertState == 1 {
+// data = data + [value.alertType.byte] + value.drinkingValue.byte2 + [value.start.hour.byte] + [value.start.minute.byte]
+// }
+// if value.alertType == 1 {
+// data = data + [value.end.hour.byte] + [value.end.minute.byte] + [value.frequency.rawValue.byte]
+// }
+// return data
+
+ guard case let .water(value) = parameters[.water] else { fatalError("缺少参数: water") }
+ Logger.log(value.description)
+
+ if value.alertState == 0 {
+ return [value.alertState.byte]
+ }
+ var data = [value.alertState.byte] + [value.alertType.byte] + value.drinkingValue.byte2 + [value.start.hour.byte] + [value.start.minute.byte]
+ if value.alertType == 1 {
+ data = data + [value.end.hour.byte] + [value.end.minute.byte] + [value.frequency.rawValue.byte]
+ }
+ return data
+ }
+}
+
+struct IBHeartRateChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .heartRate(value) = parameters[.heartRate] else { fatalError("缺少参数: heartRate") }
+ Logger.log(value.description)
+ var data = [value.rate.rawValue.byte] + [value.state.byte]
+ if value.state == 1 {
+ data = data + [value.higherOrLower.byte] + [value.value.byte]
+ }
+ return data
+ }
+}
+struct IBBloodOxygenChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .bloodOxygen(value) = parameters[.bloodOxygen] else { fatalError("缺少参数: heartRate") }
+ Logger.log(value.description)
+ var data = [value.rate.rawValue.byte] + [value.state.byte]
+ if value.state == 1 {
+ data = data + [value.value.byte]
+ }
+ return data
+ }
+}
+
+
+struct IBOtherTargetChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .otherTarget(value) = parameters[.otherTarget] else { fatalError("缺少参数: otherTarget") }
+ Logger.log(value.description)
+ var data = value.calorie.byte2 + [value.mileage.byte] + [value.stand.byte] + [value.burning.byte] + [value.ringsState.byte] + [value.vibrateState.byte]
+ data = data + [value.noticeState.byte]
+ return data
+ }
+}
+
+struct IBVibrateSoundChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .vibrateSound(value) = parameters[.vibrateSound] else { fatalError("缺少参数: IBVibrateConfig") }
+ Logger.log(value.description)
+ if value.type == 0{
+ if let systemValue = value.systemValue {
+ return [value.type.byte] + [systemValue.byte]
+ }
+ else{
+ fatalError("参数错误: IBScreenConfig")
+ }
+ }
+ else if value.type == 1{
+ if let ringsValue = value.ringsValue{
+ return [value.type.byte] + [ringsValue.byte]
+ }
+ else{
+ fatalError("参数错误: IBScreenConfig")
+ }
+ }
+ else if value.type == 3{
+ if let ringsState = value.ringsState,let vibrateState = value.vibrateState{
+ return [value.type.byte] + [ringsState.byte] + [vibrateState.byte]
+ }
+ else{
+ fatalError("参数错误: IBScreenConfig")
+ }
+ }
+ return []
+ }
+}
+struct IBScreenChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .screen(value) = parameters[.screen] else { fatalError("缺少参数: IBScreenConfig") }
+ Logger.log(value.description)
+ if value.type == 0{
+ if let brightness = value.brightness {
+ return [value.type.byte] + [brightness.byte]
+ }
+ else{
+ fatalError("参数错误: IBScreenConfig")
+ }
+ }
+ else{
+ if let duration = value.duration,let wrist = value.wrist {
+ return [value.type.byte] + [duration.rawValue.byte] + [wrist.byte]
+ }
+ else{
+ fatalError("参数错误: IBScreenConfig")
+ }
+ }
+ }
+}
+struct IBUserInfoChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .userInfo(value) = parameters[.userInfo] else { fatalError("缺少参数: userInfo") }
+ Logger.log(value.description)
+ var data = [value.age.byte] + [value.gender.byte] + [value.height.byte] + value.weight.byte2 + value.step.byte4 + value.burning.byte2
+ data = data + [value.stand.byte] + [value.hourSystem.byte]
+ return data
+ }
+}
+
+
+struct IBClockChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .clock(value) = parameters[.clock] else { fatalError("缺少参数: clock") }
+ guard let data = value.encode else { fatalError("json解析异常: clock") }
+ Logger.log(value.jsonString ?? "")
+
+ return data.bytes
+ }
+}
+struct IBTrainTargetChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .trainTarget(value) = parameters[.trainTarget] else { fatalError("缺少参数: trainTarget") }
+ guard let data = value.encode else { fatalError("json解析异常: trainTarget") }
+ Logger.log(value.jsonString ?? "")
+
+ return data.bytes
+ }
+}
+struct IBSendPhoneChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .sendPhone(value) = parameters[.sendPhone] else { fatalError("缺少参数: sendPhone") }
+ guard let data = value.encode else { fatalError("json解析异常: sendPhone") }
+ Logger.log(value.jsonString ?? "")
+
+ return data.bytes
+ }
+}
+struct IBSendTimeChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .sendTime(value) = parameters[.sendTime] else { fatalError("缺少参数: sendTime") }
+ guard let data = value.encode else { fatalError("json解析异常: sendTime") }
+ Logger.log(value.jsonString ?? "")
+ return data.bytes
+ }
+}
+struct IBWeatherChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .weather(value) = parameters[.weather] else { fatalError("缺少参数: weather") }
+ guard let data = value.encode else { fatalError("json解析异常: weather") }
+ Logger.log(value.jsonString ?? "")
+ return data.bytes
+ }
+}
+struct IBAddressBookChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .addressBook(list) = parameters[.addressBook] else { fatalError("缺少参数: addressBook") }
+ if list.count > 20 { fatalError("参数: addressBook,最多不能超过20个联系人") }
+ var data = [list.count.byte]
+ var des = "[ "
+ for item in list {
+ des += item.description
+ //性别
+ data = data + [item.gender.byte]
+ //姓名
+ if let name = item.name,let value = name.data(using: .utf8),!value.isEmpty {
+ data += [value.count.byte]
+ data += value.bytes
+ }
+ else{
+ data += [0.byte]
+ }
+ //电话号码个数
+ data += [item.phoneNumbers.count.byte]
+ if item.phoneNumbers.count > 2 { fatalError("参数: addressBook,电话号码书数不能超过两个") }
+
+ for phone in item.phoneNumbers {
+ //电话号码
+ if let value = phone.number.data(using: .utf8),!value.isEmpty {
+ data += [value.count.byte]
+ data += value.bytes
+ }
+ else{
+ data += [0.byte]
+ }
+ //归属地
+ if let value = phone.location?.data(using: .utf8),!value.isEmpty {
+ data += [value.count.byte]
+ data += value.bytes
+ }
+ else{
+ data += [0.byte]
+ }
+ //运营商
+ if let value = phone.operator?.data(using: .utf8),!value.isEmpty {
+ data += [value.count.byte]
+ data += value.bytes
+ }
+ else{
+ data += [0.byte]
+ }
+ }
+ }
+ des += " ]"
+ Logger.log(des)
+
+ return data
+ }
+}
+
+// MARK: - 文件传输
+struct IBMp3Checker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .MP3(value) = parameters[.mp3] else { fatalError("缺少参数: MP3") }
+ guard let data = value.encode else { fatalError("json解析异常: MP3") }
+ Logger.log(value.jsonString ?? "")
+ return data.bytes
+ }
+}
+struct IBEpoChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .epo(list) = parameters[.epo] else { fatalError("缺少参数: epo") }
+ var data = [list.count.byte]
+ var des = "[ "
+ for item in list {
+ des += item.description
+ data += [item.UID.byte]
+ if let name = item.fileName.data(using: .utf8),!name.isEmpty {
+ data += [name.count.byte]
+ data += name
+ }
+ data += [item.fileSize.byte]
+ }
+ des += " ]"
+ Logger.log(des)
+ return data
+
+ }
+}
+
+//struct IBCustomDialChecker: IBChecker {
+// static func check(_ parameters: [IBParameter]) -> [UInt8] {
+// guard case let .customDial(value) = parameters[.customDial] else { fatalError("缺少参数: customDial") }
+// Logger.log(value.description)
+// var data = value.UID.byte2 + [value.batteryStyle.byte] + [value.timeStyle.byte] + [value.timeColor.byte]
+// if let list = value.files{
+// data += [list.count.byte]
+// }
+// else{
+// data += [0.byte]
+// }
+// return data
+// }
+//}
+struct IBDialChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .dial(value) = parameters[.dial] else { fatalError("缺少参数: dial") }
+ Logger.log(value.description)
+ var data = value.UID.byte2
+ if let name = value.theme?.data(using: .utf8),!name.isEmpty {
+ data += [name.count.byte]
+ data += name
+ }
+ else{
+ data += [0.byte]
+ }
+ if let list = value.files{
+ data += [list.count.byte]
+ }
+ else{
+ data += [0.byte]
+ }
+ return data
+ }
+}
+struct IBDialFileChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .dialFile(value) = parameters[.dialFile] else { fatalError("缺少参数: dialFile") }
+ Logger.log(value.description)
+ var data = value.UID.byte2 + [value.index.byte] + [value.fileType.byte]
+ if let name = value.fileName.data(using: .utf8),!name.isEmpty {
+ data += [name.count.byte]
+ data += name
+ }
+ else{
+ data += [0.byte]
+ }
+ data += value.size.byte4
+ return data
+ }
+}
+
+struct IBDeleteFileChecker: IBChecker {
+ static func check(_ parameters: [IBParameter]) -> [UInt8] {
+ guard case let .deleteFile(value) = parameters[.deleteFile] else { fatalError("缺少参数: deleteFile") }
+ Logger.log(value.description)
+ var data = [value.uids.count.byte] + [value.fileType.rawValue.byte]
+ for uid in value.uids {
+ data += uid.byte2
+ }
+ return data
+ }
+}
--- /dev/null
+//
+// IBFileSender+Dial.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/12.
+//
+
+import Foundation
+extension IBFileSender{
+// func sendDial(_ info:IBCustomDial){
+// IBSender()
+// .ability(.customDial)
+// .parameters([.customDial(info)])
+// .response {[weak self] result in
+// guard let self = self else {return}
+// if result.isSuccess{
+// loadFile(info)
+// }
+// else{
+// completion(result)
+// self.remove()
+// }
+// }
+// }
+
+// private func loadFile(_ info:IBCustomDial){
+// guard let files = info.files else {
+// completion(.response.code(.success))
+// self.remove()
+// return
+// }
+//
+// var dataList:[IBFileSenderContent] = []
+// for info in files{
+// let (list,size,lrc) = readFile(from: info.filePath)
+// if let list = list {
+// info.size = size
+// total += info.size
+// dataList.append(IBFileSenderContent(dataList: list,lrc: lrc,dialFile: info))
+// }
+// else{
+// completion(.response.code(.failure).message("读取文件失败"))
+// self.remove()
+// return
+// }
+// }
+// self.fileList = dataList
+// sendDialFileInfo()
+// }
+ private func sendDialFileInfo(){
+ guard let file = self.fileList.first,let info = file.dialFile else {
+ completion(.response.code(.success))
+ self.remove()
+ return;
+ }
+ IBSender()
+ .ability(.dialFile)
+ .parameters([.dialFile(info)])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ self.sendContentDial()
+ }
+ else{
+ completion(result)
+ self.remove()
+ }
+ }
+ }
+
+ private func sendContentDial(){
+ if cancelSend {
+ Logger.log("已取消发送")
+ self.remove()
+ return
+ }
+
+ if let file = fileList.first,let data = file.dataList.first{
+ file.dataList.removeFirst()
+ HsFileAgentSdk.shared.fileSender = self
+ let model = IBFileSenderData()
+ model.data = data
+ model.fileWriteHander = { [weak self] result in
+ guard let self = self else { return }
+ self.lastData = nil
+ if result.isSuccess {
+ self.current += data.count
+ Logger.log("\(total) : \(current)")
+ self.progress?(self.total,self.current)
+ self.sendContentDial()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件超时,发送失败!"))
+ self.remove()
+ }
+ }
+ self.lastData = model
+ HsFileAgentSdk.shared.sendToDevice(fileData: data)
+ }
+ else{
+ Logger.log( "文件内容发送结束...")
+ startTimer()
+ }
+ }
+
+ func sendDialLrc(){
+ IBSender()
+ .ability(.fileCheckCode)
+ .parameters([.value(fileList.first!.lrc)])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ self.fileList.removeFirst()
+ self.sendDialFileInfo()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件校验码失败"))
+ self.remove()
+ }
+ }
+ }
+}
+
+extension IBFileSender{
+ func sendDial(_ info:IBDial){
+ IBSender()
+ .ability(.dial)
+ .parameters([.dial(info)])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ loadFile(info)
+ }
+ else{
+ completion(result)
+ self.remove()
+ }
+ }
+ }
+
+ private func loadFile(_ info:IBDial){
+ guard let files = info.files else {
+ completion(.response.code(.success))
+ self.remove()
+ return
+ }
+
+ var dataList:[IBFileSenderContent] = []
+ for info in files{
+ let (list,size,lrc) = readFile(from: info.filePath)
+ if let list = list {
+ info.size = size
+ total += info.size
+ dataList.append(IBFileSenderContent(dataList: list,lrc: lrc,dialFile: info))
+ }
+ else{
+ completion(.response.code(.failure).message("读取文件失败"))
+ self.remove()
+ return
+ }
+ }
+ self.fileList = dataList
+ sendDialFileInfo()
+ }
+}
--- /dev/null
+//
+// IBFileSender+Mp3.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/12.
+//
+
+import Foundation
+
+extension IBFileSender{
+ func sendMp3(_ info:IBSendMp3){
+ let (list,size,lrc) = readFile(from: info.filePath)
+ if let list = list {
+ info.size = size
+ total = info.size
+ let content = IBFileSenderContent(dataList: list,lrc: lrc)
+ self.fileList = [content]
+
+ IBSender()
+ .ability(.mp3)
+ .parameters([.MP3(IBMp3File(data:info))])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ self.sendContentMp3()
+ }
+ else{
+ completion(result)
+ self.remove()
+ }
+ }
+ }
+ else{
+ completion(.response.code(.failure).message("读取文件失败"))
+ self.remove()
+ }
+ }
+ private func sendContentMp3(){
+ if cancelSend {
+ Logger.log("已取消发送")
+ self.remove()
+ return
+ }
+
+ if let file = fileList.first,let data = file.dataList.first{
+ file.dataList.removeFirst()
+ HsFileAgentSdk.shared.fileSender = self
+ let model = IBFileSenderData()
+ model.data = data
+ model.fileWriteHander = { [weak self] result in
+ guard let self = self else { return }
+ self.lastData = nil
+ if result.isSuccess {
+ self.current += data.count
+ Logger.log("\(total) : \(current)")
+ self.progress?(self.total,self.current)
+ self.sendContentMp3()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件超时,发送失败!"))
+ self.remove()
+ }
+ }
+ self.lastData = model
+ HsFileAgentSdk.shared.sendToDevice(fileData: data)
+ }
+ else{
+ Logger.log( "文件内容发送结束...")
+ startTimer()
+ }
+ }
+ func sendMp3Lrc(){
+ IBSender()
+ .ability(.fileCheckCode)
+ .parameters([.value(fileList.first!.lrc)])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ completion(result)
+ self.remove()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件校验码失败"))
+ self.remove()
+ }
+ }
+ }
+}
--- /dev/null
+//
+// IBSender+Ota.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/12.
+//
+
+import Foundation
+extension IBFileSender{
+ func sendOta(_ info:IBSendOta){
+ let (list,size,lrc) = readFile(from: info.filePath)
+ if let list = list {
+ info.size = size
+ total = info.size
+ let content = IBFileSenderContent(dataList: list,lrc: lrc)
+ self.fileList = [content]
+
+ IBSender()
+ .ability(.ota)
+ .parameters([.ota(IBOtaFile(data:info))])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ self.sendContentOta()
+ }
+ else{
+ completion(result)
+ self.remove()
+ }
+ }
+ }
+ else{
+ completion(.response.code(.failure).message("读取文件失败"))
+ self.remove()
+ }
+ }
+ private func sendContentOta(){
+ if cancelSend {
+ Logger.log("已取消发送")
+ self.remove()
+ return
+ }
+
+ if let file = fileList.first,let data = file.dataList.first{
+ file.dataList.removeFirst()
+ HsFileAgentSdk.shared.fileSender = self
+ let model = IBFileSenderData()
+ model.data = data
+ model.fileWriteHander = { [weak self] result in
+ guard let self = self else { return }
+ self.lastData = nil
+ if result.isSuccess {
+ self.current += data.count
+ Logger.log("\(total) : \(current)")
+ self.progress?(self.total,self.current)
+ self.sendContentOta()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件超时,发送失败!"))
+ self.remove()
+ }
+ }
+ self.lastData = model
+ HsFileAgentSdk.shared.sendToDevice(fileData: data)
+ }
+ else{
+ Logger.log( "文件内容发送结束...")
+ startTimer()
+ }
+ }
+ func sendOtaLrc(){
+ IBSender()
+ .ability(.fileCheckCode)
+ .parameters([.value(fileList.first!.lrc)])
+ .response {[weak self] result in
+ guard let self = self else {return}
+ if result.isSuccess{
+ completion(result)
+ self.remove()
+ }
+ else{
+ completion(.response.code(.failure).message("发送文件校验码失败"))
+ self.remove()
+ }
+ }
+ }
+}
--- /dev/null
+//
+// IBFileSender.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/6.
+//
+
+import Foundation
+
+public typealias IBProgressHandler = (_ total:Int, _ current:Int) -> Void
+internal typealias IBFileWriteHandler = (_ result:IBResponse) -> Void
+
+class IBFileSenderData {
+ /// 发送的数据
+ var data:Data = Data()
+ /// 文件写入成功回调
+ var fileWriteHander:IBFileWriteHandler?
+}
+
+ class IBFileSenderContent {
+ var dataList:[Data] = []
+ var lrc = 0
+ var dialFile:IBDialFile?
+
+ init(dataList: [Data], lrc: Int = 0, dialFile: IBDialFile? = nil) {
+ self.dataList = dataList
+ self.lrc = lrc
+ self.dialFile = dialFile
+ }
+}
+
+
+
+public class IBFileSender {
+ var completion: IBCompletionHandler!
+ var progress: IBProgressHandler?
+
+ var otaInfo:IBSendOta?
+ var mp3Info:IBSendMp3?
+ var epoInfo:[IBEpo]?
+// var customInfo:IBCustomDial?
+ var dialInfo:IBDial?
+
+ var fileList:[IBFileSenderContent] = []
+ var total:Int = 0
+ var current:Int = 0
+
+
+ var cancelSend = false
+ var lastData:IBFileSenderData?
+
+ public init() {
+ Self.all.append(self)
+
+ }
+ deinit {
+ Logger.log("deinit IBFileSender")
+ }
+ func sendFileResult(_ success:Bool ){
+ deinitTimer()
+ if (success){
+ Logger.log("手表接收文件成功")
+ sendFileFinish()
+ }
+ else{
+ completion(.response.code(.failure).message("手表接收文件失败"))
+ self.remove()
+ }
+ }
+
+ func sendFileFinish(){
+ if let _ = self.mp3Info{
+ sendMp3Lrc()
+ }
+ else if let _ = self.otaInfo {
+ sendOtaLrc()
+ }
+ else if let _ = self.dialInfo{
+ sendDialLrc()
+ }
+// else if let _ = self.marketInfo{
+// sendDialLrc()
+// }
+ // else if let path = self.epoInfo.filePath {
+ // filePath = path
+ // }
+ }
+}
+
+
+
+extension IBFileSender{
+ func start(){
+ if let info = self.mp3Info{
+ self.sendMp3(info)
+ }
+ else if let info = self.otaInfo {
+ self.sendOta(info)
+ }
+ else if let info = self.dialInfo{
+ self.sendDial(info)
+ }
+// else if let info = self.marketInfo{
+// self.sendDial(info)
+// }
+ // else if let path = self.epoInfo.filePath {
+ // filePath = path
+ // }
+ }
+ func readFile(from path:String) ->([Data]?,Int,Int){
+ let fileURL = URL(fileURLWithPath: path)
+ do {
+ let data = try Data(contentsOf:fileURL)
+ let seg = 495
+ var list = [Data]()
+ for i in stride(from: 0, to: data.count, by: seg) {
+ if i+seg < data.count {
+ list.append(data[i ... i+seg - 1])
+ }
+ else{
+ list.append(data[i ... data.count - 1])
+ }
+ }
+ let lrc = LRC.calculateLRC(data.bytes).int
+ return (list,data.count,lrc)
+ } catch {
+ return (nil,0,0)
+ }
+ }
+}
+
+public extension IBFileSender{
+ @discardableResult
+ /// 发送OTA
+ /// - Parameters:
+ /// - filePath: 文件路径
+ /// - info: ota信息
+ /// - Returns: self
+ func ota(_ info:IBSendOta) ->Self{
+ self.otaInfo = info
+ return self
+ }
+ @discardableResult
+
+
+ /// 发送mp3
+ /// - Parameter info: mp3信息
+ /// - Returns: Self
+ func mp3(_ info:IBSendMp3) ->Self{
+ self.mp3Info = info
+ return self
+ }
+
+// /// 发送自定义表盘
+// /// - Parameter info: 表盘信息
+// /// - Returns: Self
+// func customDial(_ info:IBCustomDial) ->Self{
+// if let list = info.files,list.count > 0{
+// for i in 0...list.count - 1 {
+// list[i].index = i
+// list[i].UID = info.UID
+// }
+// }
+// self.customInfo = info
+// return self
+// }
+ /// 发送表盘
+ /// - Parameter info: 表盘信息
+ /// - Returns: Self
+ func dial(_ info:IBDial) ->Self{
+// IBMarketDial
+
+ if let list = info.files,list.count > 0{
+ for i in 0...list.count - 1{
+ list[i].index = i
+ list[i].UID = info.UID
+ }
+ }
+
+ self.dialInfo = info
+ return self
+ }
+
+ /// 发送EPO文件
+ /// - Parameters:
+ /// - filePath: 文件路径
+ /// - info: epo信息
+ /// - Returns: self
+ // func epo(filePath:URL,info:[IBEpo]) ->Self{
+ // self.filePath = filePath
+ // self.epoInfo = info
+ // return self
+ // }
+
+ /// 进度
+ @discardableResult
+ func progress(_ value: @escaping IBProgressHandler) -> Self {
+ progress = value
+ return self
+ }
+
+ /// 执行与响应处理
+ @discardableResult
+ func response(_ handler: @escaping IBCompletionHandler) -> Self {
+ if Self.all.count > 1{
+ handler(.response.code(.failure).message("当前有文件正在传输中..."))
+ self.remove()
+ return self
+ }
+
+ completion = handler
+ self.start()
+ return self
+ }
+
+
+ /// 撤销当前的文件传输
+ func revokeFileSend(_ handler: @escaping IBCompletionHandler){
+ for item in Self.all{
+ item.cancelSend = true
+ }
+
+ IBSender()
+ .ability(.revokeFileSend)
+ .parameters([.file(.mp3)])
+ .response { result in
+ handler(result)
+ self.remove()
+ }
+ }
+}
+
+extension IBFileSender {
+ static var all: [IBFileSender] = []
+ func remove() {
+ Self.all.removeAll { $0 === self }
+ }
+}
+
+extension IBFileSender {
+ static var timer: Timer?
+ func startTimer() {
+ deinitTimer()
+
+ Self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false, block: { [unowned self] _ in
+ deinitTimer()
+ self.completion(.response.code(.failure).message("发送文件失败"))
+ self.remove()
+ })
+ Logger.log("IBFileSender创建定时器")
+ }
+
+ func deinitTimer() {
+ guard let _ = Self.timer else { return }
+ Self.timer?.invalidate()
+ Self.timer = nil
+
+ Logger.log("IBFileSender销毁定时器")
+
+ }
+}
--- /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
+//
+// IBNotify.swift
+//
+//
+// Created by Harvey on 2020/1/5.
+// Copyright © 2020 姚作潘/Harvey. All rights reserved.
+//
+
+import Foundation
+
+fileprivate let center = NotificationCenter.default
+
+fileprivate class Weakly: NSObject {
+ weak var owner: NSObjectProtocol?
+ var delegateObservers: [NSObjectProtocol]
+ init(_ owner: NSObjectProtocol, delegateObservers: [NSObjectProtocol] = []) {
+ self.owner = owner
+ self.delegateObservers = delegateObservers
+ }
+}
+
+extension IBNotify {
+
+ fileprivate struct Keeper {
+ static var observers: [Weakly] = []
+ static func remove(_ observer: NSObjectProtocol) {
+ observers
+ .filter { $0.owner === observer || isNil($0.owner) }
+ .forEach {
+ $0.delegateObservers.forEach { center.removeObserver($0) }
+ }
+ observers.removeAll { isNil($0.owner) || $0.owner === observer }
+ }
+
+ static func append(_ delegateObserver: NSObjectProtocol, owner: NSObjectProtocol) {
+ guard let weakly = observers.filter({ $0.owner === owner }).first else {
+ observers.append(Weakly(owner, delegateObservers: [delegateObserver]))
+ return
+ }
+ weakly.delegateObservers.append(delegateObserver)
+ }
+ }
+
+ public struct Name {
+ fileprivate let rawValue: String
+ init(_ rawValue: String) {
+ self.rawValue = rawValue
+ }
+ }
+}
+
+public class IBNotify {
+
+ init() {}
+
+ private(set) var name: Notification.Name!
+ @discardableResult
+ func name(_ value: IBNotify.Name) -> Self {
+ name = Notification.Name(rawValue: value.rawValue)
+ return self
+ }
+
+ private(set) var observer: NSObjectProtocol!
+ @discardableResult
+ func observer(_ value: NSObjectProtocol) -> Self {
+ observer = value
+ return self
+ }
+
+ private(set) var queue: OperationQueue = .main
+ @discardableResult
+ func queue(_ value: OperationQueue) -> Self {
+ queue = value
+ return self
+ }
+
+ private(set) var object: Any?
+ @discardableResult
+ func object(_ value: Any) -> Self {
+ object = value
+ return self
+ }
+
+ func subscribe(_ value: @escaping (Notification) -> Void) {
+ if isNil(observer) || isNil(name) {
+ fatalError("observer or name must be not nil")
+ }
+
+ let delegateObserver = center
+ .addObserver(forName: name,
+ object: object,
+ queue: queue,
+ using: value)
+ Keeper.append(delegateObserver, owner: observer)
+ }
+
+ static func remove(_ observer: NSObjectProtocol) {
+ center.removeObserver(observer)
+ Keeper.remove(observer)
+ }
+}
+
+extension IBNotify {
+ static func post(_ name: IBNotify.Name,
+ object: Any? = nil,
+ userInfo: [AnyHashable : Any] = [:]) {
+ center.post(name: Notification.Name(rawValue: name.rawValue),
+ object: object, userInfo: userInfo)
+ }
+}
+
+//public extension IBManager {
+//
+// /// 订阅通知
+// static func subscribe(_ name: IBNotify.Name,
+// observer: NSObjectProtocol!,
+// receive: @escaping (_ object: Any?, _ userInfo: [String: Any]) -> Void) {
+// IBNotify()
+// .name(name)
+// .observer(observer)
+// .subscribe { notification in
+// receive(notification.object, notification.userInfo as? [String: Any] ?? [:])
+// }
+// }
+//
+// /// 移除订阅通知的观察者
+// static func remove(_ observer: NSObjectProtocol) {
+// IBNotify.remove(observer)
+// }
+//}
+
+public extension IBNotify.Name {
+ /// 比如电量、信号强度
+// static let deviceInfoUpdate = IBNotify.Name("IOTBluetooth.DeviceInfoUpdate")
+
+ /// 连接设备发生变动,一种情况断开连接,一种是切换设备
+// static let connectedDeviceDidChange = IBNotify.Name("IOTBluetooth.DeviceConnected")
+
+ /// 鉴权状态改变,通过object 获取状态,0准备鉴权。1等待手表同意连接。 2等待手机确认配对请求。3鉴权成功。4鉴权失败。
+// static let authStateDidChange = IBNotify.Name("IOTBluetooth.AuthStateDidChange")
+
+ /// 发现设备
+// static let deviceHasFound = IBNotify.Name("IOTBluetooth.DeviceHasFound")
+
+
+ /// 连接状态发生改变。通过object 获取状态,0断开连接。1连上设备。
+ static let connectStateDidChange = IBNotify.Name("IOTBluetooth.ConnectStateDidChange")
+
+ /// 远程拍照动作。通过object获取状态。2进行手势拍照,3退出
+ static let takePhoto = IBNotify.Name("IOTBluetooth.TakePhoto")
+
+ /// 手表请求天气数据
+ static let weather = IBNotify.Name("IOTBluetooth.weather")
+
+ /// 找到设备
+ static let didFindDevice = IBNotify.Name("IOTBluetooth.FindDevice")
+
+ /// 训练状态变化,通过object获取状态IBTarinState对象
+ static let trainStateDidChanged = IBNotify.Name("IOTBluetooth.TrainStateChanged")
+
+ /// 获取手表推送的训练实时数据,通过object获取状态IBTrainingData对象
+ static let trainingData = IBNotify.Name("IOTBluetooth.TrainingData")
+
+ /// 手表发送文件已接收完成
+ internal static let fileReceiveFinish = IBNotify.Name("IOTBluetooth.TileReceiveFinish")
+}
+
+extension IBNotify {
+ /// 处理手表推送过来的通知
+ static func handle(_ data: Data) -> Bool {
+ //拍照
+ if IBAbility.takePhoto.isAnswer(data) {
+ let result = IBGeneralParser.dataPacket(data)
+ let code = result[0].int
+ if code == 2 || code == 3 {
+ post(.takePhoto, object:code)
+ }
+ return true
+ }
+ //找到设备
+ if IBAbility.find.isAnswer(data) {
+ var bytes = data.bytes
+ bytes.removeLast()
+ let code = bytes.last!.int
+ if code == 2 {
+ post(.didFindDevice)
+ }
+ return true
+ }
+ //请求天气
+ if data.bytes[1] == 0x05 && data.bytes[2] == 0x03 {
+ post(.weather)
+ return true
+ }
+ //表盘安装状态
+ if(data.bytes[1] == 0x05 && data.bytes[2] == 0x04){
+ let result = IBGeneralParser.dataPacket(data)
+ let code = result.int
+ let desc = ["安装失败","安装成功"]
+ Logger.log("表盘安装状态:",desc[code])
+ return true
+ }
+ //手表询问APP AI模型是否可用
+ if(data.bytes[1] == 0x05 && data.bytes[2] == 0x06){
+// IBAiManager.shared.aiValid(data)
+ return true
+ }
+ //AI录音已准备好
+ if(data.bytes[1] == 0x05 && data.bytes[2] == 0x05){
+// IBAiManager.shared.aiRequest(data)
+ return true
+ }
+ //文件已接收完成
+ if data.bytes[1] == 0x05 && data.bytes[2] == 0x20 {
+ let result = IBGeneralParser.dataPacket(data)
+ HsFileAgentSdk.shared.fileSender?.sendFileResult(result[0].int == 1)
+ return true
+ }
+ //训练状态变化
+ if data.bytes[1] == 0x05 && data.bytes[2] == 0x50 {
+ let result = IBGeneralParser.dataPacket(data)
+ let model = IBTarinState(result.data)
+ post(.trainStateDidChanged, object: model)
+ return true
+ }
+ //训练实时数据
+ if IBAbility.currentHeartRate.isAnswer(data) {
+ let dataPacket = IBGeneralParser.dataPacket(data)
+ if let model = IBTrainingData(dataPacket.data) {
+ post(.trainingData, object: model)
+ }
+ return true
+ }
+ //配对
+ if IBPairing.checkPairing(data){
+ return true
+ }
+ return false
+ }
+
+
+
+
+}
--- /dev/null
+//
+// IBPairling.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/7/15.
+//
+
+import Foundation
+
+class IBPairing{
+ static func checkPairing(_ data:Data)->Bool{
+ if data.bytes[1] == 0x05 && data.bytes[2] == 0x11 {
+ let result = IBGeneralParser.dataPacket(data)
+ let code = result[0].int
+ let desc = ["拒绝配对","确认配对","确认解绑其他手机","其他"]
+ Logger.log("收到确认配对消息:",desc[code])
+ return true
+ }
+ if data.bytes[1] == 0x05 && data.bytes[2] == 0x1 {
+ //解绑
+ let result = IBGeneralParser.dataPacket(data)
+ let code = result[0].int
+ let desc = ["拒绝解绑","确认解绑"]
+ Logger.log("收到确认解绑消息 ",desc[code])
+ return true
+ }
+ if data.bytes[1] == 0x05 && data.bytes[2] == 0x10 {
+ //配对结果
+ var result = IBGeneralParser.dataPacket(data)
+ let code = result[0].int
+ if code == 1{
+ result.removeFirst()
+ UserDefaults.standard.setValue(result, forKey: "IOTBletooth.mac")
+ UserDefaults.standard.synchronize()
+
+ Logger.log("收到配对成功消息",result)
+ //发送时间
+ IBPairing.sendTime()
+ }
+ else{
+ Logger.log("收到配对失败消息")
+// IBManager.completeHandler?(false)
+// IBManager.completeHandler = nil
+ }
+
+ return true
+ }
+ return false
+ }
+
+ static func sendTime(){
+ let date = Date()
+ let model = IBSendTime()
+ model.time = Int(date.timeIntervalSince1970)
+ model.week = date.weekDay.rawValue - 1
+ model.year = date.year - 2000
+ model.month = date.month
+ model.day = date.day
+ model.hour = date.hour
+ model.minute = date.minute
+ model.second = date.second
+
+ IBSender()
+ .ability(IBAbility.sendTime)
+ .parameters([.sendTime(model)])
+ .response { result in
+ Logger.log(result)
+// IBManager.completeHandler?(true)
+// IBManager.completeHandler = nil
+ }
+ }
+}
--- /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"
+ case epo = "epo"
+ case ota = "ota"
+ case mp3 = "mp3"
+ case dial = "dial"
+ case dialFile = "dialFile"
+ case deleteFile = "deleteFile"
+ case fetchFils = "fetchFils"
+ case aiValid = "aiValid"
+ case aiResponse = "aiResponse"
+}
+
+/// 参数
+public enum IBParameter {
+ /// 表盘ID
+ case dialID(Int)
+ /// 1=(开始、启用、开启),0=(停止、禁用、关闭)
+ case action(Int)
+ /// 运动模式
+ case motionMode(IBMotionMode)
+ /// 语言
+ case language(IBLanguage)
+ /// 通用值设置
+ case value(Int)
+ /// 文件
+ case file(IBFile)
+ /// 训练目标
+ case targetStep(IBTargetStep)
+ /// 睡眠
+ case sleep(IBSleep)
+ /// 喝水
+ case water(IBWater)
+ /// 设置闹钟
+ case clock(IBClock)
+ /// 设置心率
+ case heartRate(IBHeartRate)
+ /// 血氧
+ case bloodOxygen(IBBloodOxygen)
+ /// 设置屏幕
+ case screen(IBScreenConfig)
+ /// 设置声音和震动
+ case vibrateSound(IBVibrateConfig)
+ /// 设置目标设置
+ case otherTarget(IBOtherTarget)
+ /// 设置训练目标
+ case trainTarget(IBTrainTarget)
+ /// 发送手机信息
+ case sendPhone(IBPhoneInfo)
+ /// 发送当前时间
+ case sendTime(IBSendTime)
+ /// 设置辅助功能
+ case auxiliary(IBAuxiliary)
+ /// 设置用户数据
+ case userInfo(IBUserInfo)
+ /// 发送天气数据
+ case weather(IBWeather)
+ /// 发送通讯录
+ case addressBook([IBAddressBook])
+ /// 发送EPO文件信息
+ case epo([IBEpo])
+ /// 发送OTA文件信息
+ case ota(IBOtaFile)
+ /// 发送mp3文件信息
+ case MP3(IBMp3File)
+ /// 发送表盘信息汇总
+ case dial(IBDial)
+ /// 发送单个表盘文件信息
+ case dialFile(IBDialFile)
+ /// 删除指定文件
+ case deleteFile(IBDeleteFile)
+ /// 获取指定类型文件目录
+ case fetchFils(IBFile)
+ /// 发送Ai是否可用回执
+ case aiValid(IBAiValid)
+ /// 发送Ai回应内容
+ case aiResponse(IBAiResponse)
+
+ var key: IBKey {
+ switch self {
+ case .dialID(_): return .dialID
+ case .action(_): return .action
+ case .motionMode(_): return .motionMode
+ case .language(_): return .language
+ case .value(_): return .value
+ case .file(_): return .file
+ case .targetStep(_): return .targetStep
+ case .sleep(_): return .sleep
+ case .water(_): return .water
+ case .clock(_): return .clock
+ case .heartRate(_): return .heartRate
+ case .bloodOxygen(_): return .bloodOxygen
+ case .screen(_): return .screen
+ case .vibrateSound(_): return .vibrateSound
+ case .auxiliary(_): return .auxiliary
+ case .otherTarget(_): return .otherTarget
+ case .trainTarget(_): return .trainTarget
+ case .sendPhone(_): return .sendPhone
+ case .sendTime(_): return .sendTime
+ case .userInfo(_): return .userInfo
+ case .weather(_): return .weather
+ case .addressBook(_): return .addressBook
+ case .epo(_): return .epo
+ case .ota(_): return .ota
+ case .MP3(_): return .mp3
+ case .dial(_): return .dial
+ case .dialFile(_): return .dialFile
+ case .deleteFile(_): return .deleteFile
+ case .fetchFils(_): return .fetchFils
+ case .aiValid(_): return .aiValid
+ case .aiResponse(_): return .aiResponse
+ }
+ }
+}
+
+public enum IBMotionMode: Int {
+ /// 健走
+ case walking = 0
+ /// 跑步
+ case running
+ /// 骑行
+ case ride
+}
+
+public enum IBFile: Int,Codable {
+ /// OTA 文件
+ case ota = 1
+ /// MP3 文件
+ case mp3
+ /// 表盘信息
+ case dial
+ /// OTA(W4)
+ case ota_w4
+ ///EPO
+ case epo
+}
+//0:运动
+//1:心率
+//2:血氧监测
+//3:训练
+//4:训练记录
+//5:指南针
+//6:天气
+//7:音乐
+//8:电话
+//9:录音
+//10:秒表
+//11:计时器
+//12:闹钟
+//13:蓝牙
+//14:睡眠
+//15:设置
+public enum IBAuxiliary: Int {
+ /// 运动
+ case sport = 0
+ /// 心率
+ case heart
+ /// 血氧监测
+ case bloodOxygen
+ /// 训练
+ case train
+ /// 训练记录
+ case tarinRecord
+ /// 指南针
+ case compass
+ /// 天气
+ case weather
+ /// 音乐
+ case music
+ /// 电话
+ case mobile
+ /// 录音
+ case record
+ /// 秒表
+ case secondTimer
+ /// 计时器
+ case timer
+ /// 闹钟
+ case clock
+ /// 蓝牙
+ case bluetooth
+ /// 睡眠
+ case sleep
+ /// 设置
+ case setting
+}
--- /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 {
+
+ let code = dataPacket(data).int
+ let desc = ["指令校验失败", "已安装", "未安装"]
+ return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code)
+ }
+}
+
+/// 通用解析(1 Byte)
+struct IBGeneralParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+// guard data.isEmpty == false else { return IBSingleByteSimulateParser.parse() }
+
+ let code = dataPacket(data).int
+ return .response.code(code == 1 ? .success:.failure)
+ }
+}
+struct IBTakePhotoParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let code = dataPacket(data).int
+ let desc = ["指令校验失败", "操作成功", "进行手势拍照","退出"]
+ return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code)
+ }
+}
+struct IBWaterParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+// guard data.isEmpty == false else { return IBSingleByteSimulateParser.parse() }
+
+ let code = dataPacket(data).int
+ let desc = code == 0 ? "关闭":"开启"
+ return .response.code(.success).value(code).message(desc)
+ }
+}
+struct IBFindParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let code = dataPacket(data).int
+ let desc = ["指令校验失败", "操作成功", "手表已找到,停止查找"]
+ return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code)
+ }
+}
+struct IBSendPhoneParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let code = dataPacket(data).int
+ let desc = ["指令校验失败","手表未与任何手机绑定", "手表已与该手机绑定", "手表已与其他手机绑定"]
+ return .response.code(code != 0 ? .success:.failure).message(desc[code]).value(code)
+ }
+}
+
+
+struct IBWatchStateParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ var bytes = data.bytes
+ bytes.removeLast()
+ let code = bytes.last!.int
+ let desc = [0:"空闲状态", 9:"USB存储状态", 10:"运动训练状态"]
+ return .response.code(.success).value(code).message(desc[code] ?? "")
+ }
+}
+
+
+struct IBDiskParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let code = dataPacket[0].int
+ if code == 1 {
+ var value: Int = 0
+ value = dataPacket.suffix(from: 1).map({ $0 }).int
+ return .response.code(.success).value(value)
+ }
+ else {
+ return .response.code(.failure)
+ }
+ }
+}
+
+struct IBTrainingDataParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ if dataPacket.count == 0 {
+ return .response.code(.success)
+ }
+ guard let model = IBTrainingData(dataPacket.data) else { return .response.code(.parseError) }
+ return .response.code(.success).value(model)
+ }
+}
+struct IBHeartRateParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBHeartRate(dataPacket.data)
+ Logger.log(model.description)
+ return .response.code(.success).value(model)
+ }
+}
+struct IBBloodOxygenParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBBloodOxygen(dataPacket.data)
+ Logger.log(model.description)
+ return .response.code(.success).value(model)
+ }
+}
+
+struct IBSyncTrainParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ guard let model = JSON.decode(IBHistoryBaseTraining.self, from: dataPacket.data) else { return .response.code(.parseError) }
+ return .response.code(.success).items(model.list)
+
+// let dataPacket = baseData.jsonString()!
+// guard let model = JSON.decode(IBHistoryBaseTraining.self, from: dataPacket.data!) else { return .response.code(.parseError) }
+// Logger.log(model.jsonString ?? "")
+// return .response.code(.success).items(model.list)
+ }
+}
+struct IBSyncHeartRateParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBHistoryHeartRate(dataPacket.data)
+// Logger.log(model.list)
+ return .response.code(.success).items(model.list)
+ }
+}
+struct IBSyncBloodOxygenParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBHistoryBloodOxygen(dataPacket.data)
+// Logger.log(model.list)
+ return .response.code(.success).items(model.list)
+ }
+}
+struct IBSyncStepParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBHistoryStep(dataPacket.data)
+// Logger.log(model.list)
+ return .response.code(.success).items(model.list)
+ }
+}
+struct IBSyncBurningParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBHistoryBurning(dataPacket.data)
+// Logger.log(model.list)
+ return .response.code(.success).items(model.list)
+ }
+}
+struct IBSyncStandParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBHistoryStand(dataPacket.data)
+// Logger.log(model.list)
+ return .response.code(.success).items(model.list)
+ }
+}
+struct IBSyncCalorieParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBHistoryCalorie(dataPacket.data)
+// Logger.log(model.list)
+ return .response.code(.success).items(model.list)
+ }
+}
+struct IBSyncDistanceParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBHistoryDistance(dataPacket.data)
+// Logger.log(model.list)
+ return .response.code(.success).items(model.list)
+ }
+}
+struct IBSyncSleepParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBHistorySleep(dataPacket.data)
+// Logger.log(model.list)
+ return .response.code(.success).items(model.list)
+ }
+}
+struct IBSyncGpsParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBHistoryGps(dataPacket.data)
+// Logger.log(model.list)
+ return .response.code(.success).items(model.list)
+ }
+}
+struct IBFetchFilesParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ guard let model = IBFetchFile(dataPacket.data) else { return .response.code(.parseError) }
+ Logger.log(model.jsonString ?? "")
+ return .response.code(.success).value(model)
+ }
+}
+
+
+struct IBAblutParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ guard let model = JSON.decode(IBAbout.self, from: dataPacket.data) else { return .response.code(.parseError) }
+ Logger.log(model.jsonString ?? "")
+ return .response.code(.success).value(model)
+ }
+}
+struct IBClockParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ guard let model = JSON.decode(IBClock.self, from: dataPacket.data) else { return .response.code(.parseError) }
+ Logger.log(model.jsonString ?? "")
+ return .response.code(.success).items(model.alarms)
+ }
+}
+struct IBSetClockParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+// guard data.isEmpty == false else { return IBClockSimulateParser.parse() }
+ let code = dataPacket(data).int
+ return .response.code(code == 1 ? .success:.failure)
+ }
+}
+
+struct IBFetchDialsParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let num = dataPacket[0].int
+
+ var list:[Int] = []
+ for i in 0..<num {
+ let value = dataPacket.data[2*i + 1...2*i + 2].int
+ list.append(value)
+ }
+ return .response.code(.success).items(list)
+ }
+}
+
+struct IBTargetStepParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ guard let model = IBTargetStep(dataPacket.data) else { return .response.code(.parseError) }
+
+ Logger.log(model.description)
+ return .response.code(.success).value(model)
+ }
+}
+struct IBSleepParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBSleep(dataPacket.data)
+ Logger.log(model.description)
+ return .response.code(.success).value(model)
+ }
+}
+struct IBScreenParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBScreen(dataPacket.data)
+ Logger.log(model.description)
+ return .response.code(.success).value(model)
+ }
+}
+struct IBVibrateSoundParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBVibrateSound(dataPacket.data)
+ Logger.log(model.description)
+ return .response.code(.success).value(model)
+ }
+}
+struct IBOtherParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBVibrateSound(dataPacket.data)
+ Logger.log(model.description)
+ return .response.code(.success).value(model)
+ }
+}
+struct IBOtherTargetParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data)
+ let model = IBOtherTarget(dataPacket.data)
+ Logger.log(model.description)
+ return .response.code(.success).value(model)
+ }
+}
+
+struct IBfetchAiAudioParser: IBParser {
+ static func parse(_ data: Data) -> IBResponse {
+ let dataPacket = dataPacket(data).data
+ return .response.code(.success).value(dataPacket)
+ }
+}
--- /dev/null
+//
+// IBReceiveManager.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/7/15.
+//
+
+import Foundation
+import CoreBluetooth
+
+
+@objc protocol IBReceiveManagerDelegate {
+ func receiveManagerDidReceiveFullData(characteristic: CBCharacteristic,_ data:Data)
+}
+
+class IBReceiveManager{
+ var delegate:IBReceiveManagerDelegate?
+ static let shared = IBReceiveManager()
+
+ var headData = Data()
+ var lastData = Data()
+ var totalData = Data()
+
+
+ func receice(characteristic: CBCharacteristic){
+ guard let data = characteristic.value else { return }
+ if(data.count == 2 && data.int == 0){
+// Logger.log("收到0000指令异常")
+ return
+ }
+
+ if data.count >= 18,data[0] == 0x72{
+ let mark = characteristic.isNotifying ? "Notify Read" : "Read"
+ let uuid = characteristic.uuid.uuidString
+ let pkgTotal = data[3...6].int
+ let dataTotal = data[7...10].int
+ let pkgIndex = data[11...14].int
+ let pkgLen = data[15...16].int
+
+ Logger.log(mark, uuid, "->", "pkgTotal = \(pkgTotal),dataTotal = \(dataTotal),pkgIndex = \(pkgIndex),pkgLen = \(pkgLen)",data.joined(" "))
+
+// if data[1] == 0x01,data[2] == 0x34{
+// //ai语音
+ if pkgIndex == 1{
+ headData = data.prefix(17)
+ lastData = data.suffix(1)
+ }
+
+ totalData = totalData + data.suffix(from: 17).prefix(pkgLen)
+ if pkgTotal == pkgIndex{
+ Logger.log("total = \(dataTotal),receive = \(totalData.count)")
+ let fullData = headData + totalData + lastData
+ didGetFullData(characteristic: characteristic, data: fullData)
+ }
+// }
+// else{
+// didGetFullData(characteristic: characteristic, data: data)
+// }
+ }
+ else{
+ Logger.log( data.joined(" "))
+ didGetFullData(characteristic: characteristic, data: data)
+ }
+ }
+
+ func didGetFullData(characteristic: CBCharacteristic,data:Data){
+// Logger.log( data.joined(" "))
+
+ delegate?.receiveManagerDidReceiveFullData(characteristic: characteristic, data)
+ totalData = Data()
+
+ if characteristic.uuid.uuidString == readCharacteristicUUID {
+ let info = IBWatchInfo(data)
+ sendDeviceInfo()
+ }
+
+ if IBNotify.handle(data) {
+// Logger.log("通知消息")
+ return
+ }
+ }
+
+ func sendDeviceInfo(){
+ let mac = UserDefaults.standard.object(forKey: "IOTBletooth.mac") as? [Int] ?? []
+ let model = IBPhoneInfo()
+ model.mac = mac
+ IBSender()
+ .ability(IBAbility.sendPhone)
+ .parameters([.sendPhone(model)])
+ .response { result in
+ Logger.log(result)
+ if result.code != .success{
+ //通知连接失败
+
+ return
+ }
+ guard let value = result.value as? Int else {return}
+ if value == 2 {
+ //通知连接成功
+
+ }
+ else if value == 1{
+ //等待手表同意连接
+ Logger.log("等待手表同意连接")
+ }
+ else if value == 3{
+ //手表已与其他手机绑定
+
+ }
+ }
+ }
+}
+
--- /dev/null
+//
+// IBSender.swift
+// IOTBluetooth
+//
+// Created by yaozuopan on 2024/3/15.
+//
+
+import Foundation
+
+public typealias IBCompletionHandler = (_ result: IBResponse) -> Void
+
+
+/// 蓝牙消息发送类
+public class IBSender {
+ public init() {
+ Self.all.append(self)
+ }
+
+ var ability: IBAbility!
+ var parameters: [IBParameter] = []
+ var completion: IBCompletionHandler!
+
+ var dataPacket: [[UInt8]] {
+ var totalData: [UInt8] = []
+ if isValue(ability.checker) {
+ totalData = ability.checker.check(parameters)
+ }
+
+ let list = splitData(totalData)
+ if list.count > 1{
+ var newList = [[UInt8]]()
+ /// 分包数
+ let packageCount = list.count
+ /// 数据包总长度
+ let dataTotalLength = totalData.count
+ /// 数据包长度
+ let dataLength = list[0].count
+ /// 数据包索引
+ let index = 1
+
+ var bytes: [UInt8] = [0x73, ability.type.rawValue, ability.id]
+ bytes = bytes + packageCount.byte4 + dataTotalLength.byte4 + index.byte4 + dataLength.byte2
+ bytes = bytes + list[0] + [LRC.calculateLRC(list[0])]
+
+ newList.append(bytes)
+ newList.append(contentsOf: list[1...list.count - 1])
+
+ return newList
+ }
+ else{
+ /// 分包数
+ let packageCount = 1
+ /// 数据包总长度
+ let dataTotalLength = totalData.count
+ /// 数据包长度
+ let dataLength = totalData.count
+ /// 数据包索引
+ let index = 1
+
+ var bytes: [UInt8] = [0x73, ability.type.rawValue, ability.id]
+ bytes = bytes + packageCount.byte4 + dataTotalLength.byte4 + index.byte4 + dataLength.byte2
+
+ if totalData.count > 0 {
+ bytes = bytes + totalData
+ bytes = bytes + [LRC.calculateLRC(totalData)]
+ } else {
+ bytes = bytes + [0x00]
+ }
+
+ return [bytes]
+ }
+ }
+
+ private func splitData(_ listData:[UInt8])->[[UInt8]]{
+ let data = listData.data
+ var list = [[UInt8]]()
+ var index = 0
+ let seg = 492
+ while index < data.count {
+ if index < data.count - seg {
+ let sub:[UInt8] = data[index...index + seg - 1].bytes
+ list.append(sub)
+ index += sub.count
+ }
+ else{
+ let sub:[UInt8] = data[index...data.count - 1].bytes
+ index += sub.count
+ list.append(sub)
+ }
+ }
+ return list
+ }
+
+
+ func isAnswer(_ data: Data) -> Bool {
+ return ability.isAnswer(data)
+ }
+
+ func parse(_ data: Data) -> IBResponse {
+ return ability.parser.parse(data)
+ }
+
+ deinit {
+// Logger.log("deinit IBSender")
+ }
+}
+
+public extension IBSender {
+
+ /// 功能接口
+ @discardableResult
+ func ability(_ value: IBAbility) -> Self {
+ ability = value
+ return self
+ }
+
+ /// 参数
+ @discardableResult
+ func parameters(_ value: [IBParameter]) -> Self {
+ parameters = value
+ return self
+ }
+
+ /// 执行与响应处理
+ @discardableResult
+ func response(_ handler: @escaping IBCompletionHandler) -> Self {
+ completion = handler
+// IBBluetooth.shared.send(self)
+ return self
+ }
+}
+
+extension IBSender {
+ static var all: [IBSender] = []
+
+ /// 从缓存实例集合中移除该实例
+ func remove() {
+ Self.all.removeAll { $0 === self }
+ }
+}
--- /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
+//
+// 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
+//
+// IBAiResponse.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/27.
+//
+
+import Foundation
+/// Ai回应模型
+public class IBAiResponse {
+ /// 类型,0:AI回应错误 1:AI录音解析内容 2:AI回应内容(类型为0时,只发类型即可,后面字节均不发)
+ var type: Int = 0
+ /// 内容
+ public var value: String = ""
+
+ public init() { }
+
+ public var description: String {
+ return "IBAiResponse(type: \(type), value:\(value))"
+ }
+}
--- /dev/null
+//
+// IBAiValid.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/8/2.
+//
+
+import Foundation
+/// Ai是否可用模型
+public class IBAiValid {
+ /// 0:不可用(后面不带key)1:可用
+ public var isValid: Bool = true
+ /// key (ChatGPT后面带key,其他不带)
+ public var key: String?
+
+ public init() { }
+
+ public var description: String {
+ return "IBAiValid(isValid: \(isValid), key:\(key ?? ""))"
+ }
+}
--- /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() {
+// for i in 0...4{
+// let model = IBClockInfo()
+// model.id = i
+// model.hour = i*3
+// model.minute = i*10
+// model.enable = i%2
+// model.repeat = [1,2,3,4,5,6,7]
+// alarms.append(model)
+// }
+ }
+}
+
+/// 闹钟信息模型
+public class IBClockInfo: Codable {
+ /// 闹钟id 范围为0~4. (即闹钟暂定最多设置为5个)
+ public var id: Int = 0
+ /// 小时
+ public var hour: Int = 0
+ /// 分钟
+ public var minute: Int = 0
+ /// 0关闭闹钟,1开启闹钟
+ public var enable: Int = 0
+ /// 闹钟重复列表
+ ///
+ /// - 0 不重复,响一次
+ /// - 1 周一
+ /// - 2 周二
+ /// - ......
+ /// - 7 周日
+ public var `repeat`: [Int] = [0]
+
+// enum CodingKeys: String, CodingKey {
+// case id = "id"
+// case hour = "hour"
+// case minute = "minute"
+// case enable = "enable"
+// case `repeat` = "repeat"
+// }
+
+ public init() { }
+}
--- /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
+//
+// IBDeleteFile.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/6.
+//
+
+import Foundation
+
+/// 删除文件模型
+public class IBDeleteFile:Codable {
+ /// 文件类型,2:MP3 3:表盘
+ public var fileType: IBFile = .mp3
+ /// 文件UID,文件唯一编号。一次最多5个
+ public var uids: [Int] = []
+
+ public init() { }
+
+ public var description: String {
+ return "IBDeleteFile(fileType: \(fileType), uids:\(uids))"
+ }
+}
--- /dev/null
+//
+// IBDail.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/6.
+//
+
+import Foundation
+/// 表盘文件汇总信息模型(自定义)
+//internal class IBCustomDial {
+// /// UID,0~4:自定义表盘UID(不可变更)
+// var UID: Int = 0
+// /// 电池图标样式, 0:无图标 1:黑色图标 2:白色图标
+// var batteryStyle: Int = 0
+// /// 时间样式 0:时间在下方 1:时间在上方
+// var timeStyle: Int = 0
+// /// 时间字体颜色 0:亮白 1:黑 2:暗白 3:绿 4:橙 5:蓝
+// var timeColor: Int = 0
+// /// 表盘文件
+// var files: [IBDialFile]?
+//
+// init() { }
+//
+// var description: String {
+// return "IBCustomDail(UID: \(UID), batteryStyle:\(batteryStyle), timeStyle:\(timeStyle), timeColor:\(timeColor))"
+// }
+//}
+//
+///// 表盘文件汇总信息模型(表盘市场)
+//internal class IBMarketDial {
+// /// UID,0x05~0xffff:表盘市场UID
+// var UID: Int = 5
+// /// 主题名称,名称长度,0~255
+// var theme: String?
+// /// 主题名称长度
+// internal var themeLength: Int = 0
+// /// 表盘文件
+// var files: [IBDialFile]?
+//
+//
+// init() { }
+//
+// var description: String {
+// return "IBMarketDail(UID: \(UID), themeLength:\(themeLength), theme:\(theme ?? ""))"
+// }
+//}
+
+/// 表盘文件汇总信息模型(目前只支持表盘市场)
+public class IBDial {
+ /// 自定义表盘:0~4
+ /// 表盘市场 : 0x05~0xffff
+ public var UID: Int = 5
+
+ /// 主题名称,名称长度,0~255 (仅表盘市场有该项)
+ public var theme: String?
+ /// 主题名称长度
+ internal var themeLength: Int = 0
+
+ /// 电池图标样式, 0:无图标 1:黑色图标 2:白色图标( 仅 定义表盘有该项)
+ public var batteryStyle: Int?
+ /// 时间样式 0:时间在下方 1:时间在上方 ( 仅自定义表盘有该项)
+ public var timeStyle: Int?
+ /// 时间字体颜色 0:亮白 1:黑 2:暗白 3:绿 4:橙 5:蓝 ( 仅自定义表盘有该项)
+ public var timeColor: Int?
+
+ /// 表盘文件
+ public var files: [IBDialFile]?
+
+
+ public init() { }
+
+ public var description: String {
+ return "IBMarketDail(UID: \(UID), themeLength:\(themeLength), theme:\(theme ?? ""))"
+ }
+}
+
+/// 单个表盘文件信息模型
+public class IBDialFile {
+ /// 文件路径
+ public var filePath: String = ""
+ /// 文件名称
+ public var fileName: String = ""
+
+ /// UID,0x05~0xffff:表盘市场UID
+ internal var UID: Int = 0
+ /// 文件编号,1~255,即该主题表盘中的第几个文件,最大为表盘文件汇总信息中的文件数目
+ internal var index: Int = 0
+ /// 文件类型,0:表盘jSON文件(压缩后统一命名为local_page.lzma)1:图片文件(除缩略图外的其他图片)(背景图压缩后统一命名为main_bg.lzma)2:缩略图:(压缩后统一命名为thumbnail.lzma)
+ internal var fileType: Int = 1
+ /// 文件名称长度,6~255,不能为0,文件后缀统一为.lzma
+ internal var nameLength: Int{
+ return fileName.count
+ }
+ /// 文件大小,0x01~0xffffffff
+ internal var size: Int = 1
+
+
+ public init() { }
+
+
+ public var description: String {
+ return "IBDialFile(UID: \(UID), index:\(index),fileType: \(fileType), nameLength:\(nameLength), filePath:\(filePath), fileName:\(fileName), size:\(size))"
+ }
+}
--- /dev/null
+//
+// IBEpo.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/7.
+//
+
+import Foundation
+/// IBEpo信息模型
+public class IBEpo {
+ /// 文件编号,1~255
+ public var UID: Int = 5
+ /// 文件名称长度,6~255,不能为0,文件后缀统一为.lzma
+ public var nameLength: Int = 6
+ /// 文件名称,名称字节数为前面名称长度指定,UTF-8编码
+ public var fileName: String = ""
+ /// 文件路径
+ public var filePath: String = ""
+ /// 文件大小,单位byte
+ var fileSize: Int = 0
+
+ public init() { }
+
+ public var description: String {
+ return "IBEpo(UID: \(UID), nameLength:\(nameLength),fileName: \(fileName),filePath: \(filePath),fileSize: \(fileSize))"
+ }
+}
--- /dev/null
+//
+// IBFetchFile.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/8.
+//
+
+import Foundation
+/// 获取指定类型文件目录模型
+public class IBFetchFile:Codable {
+ /// 类型 2:MP3, 3:表盘
+ var type:IBFile = .mp3
+ /// 表盘
+ var dialList:[IBFetchDialItem]?
+ /// mp3
+ var mp3List:[IBFetchMp3Item]?
+
+ init?(_ data: Data) {
+ guard let dict = data.jsonObject() as? [String: Any] else { return nil }
+ guard let type = dict["type"] as? Int,let fileType = IBFile(rawValue: type) else { return nil }
+ self.type = fileType
+
+ if let listDict = dict["list"] as? [[String: Any]] {
+ if self.type == .mp3 {
+ var list = [IBFetchMp3Item]()
+ for item in listDict {
+ let model = IBFetchMp3Item()
+ model.uid = item["uid"] as? Int ?? 0
+ model.name = item["name"] as? String ?? ""
+ model.size = item["size"] as? Int ?? 0
+ model.artist = item["artist"] as? String ?? ""
+ model.duration = item["duration"] as? Int ?? 0
+ list.append(model)
+ }
+ mp3List = list
+ }
+ else if self.type == .dial{
+ var list = [IBFetchDialItem]()
+ for item in listDict {
+ let model = IBFetchDialItem()
+ model.uid = item["uid"] as? Int ?? 0
+ model.name = item["name"] as? String ?? ""
+ model.size = item["size"] as? Int ?? 0
+ list.append(model)
+ }
+ dialList = list
+ }
+ }
+ }
+
+ public init() { }
+}
+
+/// 获取指定类型文件表盘模型
+public class IBFetchDialItem:Codable {
+ /// 表盘在手表中唯一编号
+ var uid:Int = 0
+ /// 表盘名字
+ var name:String = ""
+ /// 文件大小,单位byte
+ var size:Int = 0
+
+ public init() { }
+}
+
+/// 获取指定类型文件MP3模型
+public class IBFetchMp3Item:Codable {
+ /// 歌曲在手表中唯一编号
+ var uid:Int = 0
+ /// 歌曲名字
+ var name:String = ""
+ /// 文件大小,单位byte
+ var size:Int = 0
+ /// 作者
+ var artist:String = ""
+ /// 歌曲时长,单位毫秒
+ var duration:Int = 0
+
+ public init() { }
+}
--- /dev/null
+//
+// IBHeartRate.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/5/16.
+//
+
+import Foundation
+/// 心率设置模型
+public class IBHeartRate {
+ /// 监测频率
+ public var rate: IBHeartRateFrequency = .min1
+ /// 心率提醒 0:关闭 1:开启。 为0时后面区间阈值无需设置。
+ public var state: Int = 1
+ /// 心率区间阈值 高于或低于 0:低于 1:高于
+ public var higherOrLower: Int = 0
+ /// 心率区间阈值 30~200
+ public var value: Int = 100
+
+ public init() { }
+
+ init(_ data: Data) {
+ rate = IBHeartRateFrequency(rawValue:data[0].int) ?? .none
+ state = data[1].int
+ higherOrLower = data[2].int
+ value = data[3].int
+ }
+
+ var description: String {
+ return "IBHeartRate(rate: \(rate), state:\(state), higherOrLower:\(higherOrLower), value:\(value))"
+ }
+}
+/// 提醒频率
+public enum IBHeartRateFrequency: Int {
+ /// 关闭
+ case none = 0
+ /// 1分钟
+ case min1
+ /// 5分钟
+ case min5
+ /// 10分钟
+ case min10
+}
--- /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[i...i+3].int
+ model.value = data[i+4...i+7].float
+ historyList.append(model)
+ }
+ }
+ list = historyList
+ }
+}
+
+/// 卡路里历史记录
+public class IBHistoryCalorieItem {
+ /// 时间戳
+ public var time: Int = 0
+ /// 单位kcal
+ public var value: Float = 0
+
+ public init() { }
+}
--- /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].float
+ historyList.append(model)
+ }
+ }
+ list = historyList
+ }
+}
+
+/// 距离历史记录
+public class IBHistoryDistanceItem {
+ /// 时间戳
+ public var time: Int = 0
+ /// 单位km
+ public var value: Float = 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 times = data[0].int
+ let contentData:Data = data.suffix(from: 1).map({ $0 }).data
+ var index = 0
+ for _ in 0..<times {
+ if contentData.count > index + 1 {
+ let len = contentData[index...index+1].int
+ if contentData.count > index + len {
+ let itemData = contentData[index+2 ... index+1+len].map({ $0 }).data
+ let item = IBHistorySleepItem()
+ item.sleepId = itemData[0].int
+ item.type = itemData[1].int
+ item.startTime = itemData[2...5].int
+ item.endTime = itemData[6...9].int
+ item.totalSleep = IBSleepTime(hour: itemData[10].int, minute: itemData[11].int)
+ if(item.type == 1){
+ item.deepSleep = IBSleepTime(hour: itemData[12].int, minute: itemData[13].int)
+ item.lightSleep = IBSleepTime(hour: itemData[14].int, minute: itemData[15].int)
+ item.remSleep = IBSleepTime(hour: itemData[16].int, minute: itemData[17].int)
+ item.awake = IBSleepTime(hour: itemData[18].int, minute: itemData[19].int)
+
+ var subIndex = 20
+ var list:[IBSleepState] = []
+ while itemData.count >= subIndex + 5{
+ let state = IBSleepState(time: itemData[subIndex...subIndex+3].int, state: itemData[subIndex+4].int)
+ list.append(state)
+ subIndex = subIndex + 5
+ }
+ item.states = list
+ }
+ historyList.append(item)
+ }
+ index += 2 + len
+ }
+ }
+ list = historyList
+ }
+}
+
+/// 睡眠历史记录
+public class IBHistorySleepItem {
+ /// 睡眠ID,0~255,表示第几次睡眠
+ public var sleepId: Int = 0
+ /// 睡眠类型,0:小睡(无总时长之后的所有项目)1:睡眠
+ public var type: Int = 0
+ /// 入睡时间戳,Unix时间戳
+ public var startTime: Int = 0
+ /// 出睡时间戳,Unix时间戳
+ public var endTime: Int = 0
+ /// 总时长
+ public var totalSleep: IBSleepTime = IBSleepTime(hour: 0, minute: 0)
+ /// 深睡时长
+ public var deepSleep: IBSleepTime?
+ /// 浅睡时长
+ public var lightSleep: IBSleepTime?
+ /// 快速动眼时长
+ public var remSleep: IBSleepTime?
+ /// 清醒时长
+ public var awake: IBSleepTime?
+ /// 睡眠状态
+ public var states:[IBSleepState]?
+
+
+ public init() { }
+}
+
+/// 时间
+public struct IBSleepTime {
+ /// 时
+ public var hour: Int
+ /// 分 0~59
+ public var minute: Int
+
+ public init(hour: Int, minute: Int) {
+ self.hour = hour
+ self.minute = minute
+ }
+
+ var description: String {
+ return "IBSleepTime(hour: \(hour), minute:\(minute)"
+ }
+}
+
+
+/// 睡眠状态
+public struct IBSleepState {
+ /// Unix时间戳
+ public var time: Int = 0
+ /// 状态,0:进入睡眠 1:浅层睡眠 2:深层睡眠 3:清醒 4:结束睡眠 7:未出睡最后一笔睡眠数据(资料上报结束标志) 9:出睡最后一笔睡眠数据(资料上报结束标志) 12:快速眼动 13:取消入睡通知(睡眠不足 60 分钟出睡上报状态) 14:结束小睡
+ public var state: Int = 0
+
+ public init(time: Int, state: Int) {
+ self.time = time
+ self.state = state
+ }
+}
--- /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
+//
+// IBMp3.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/6.
+//
+
+import Foundation
+
+/// 发送mp3文件模型
+public class IBSendMp3:Codable {
+ /// 文件路径
+ public var filePath: String = ""
+ /// 歌曲名字
+ public var name: String = ""
+ /// 文件大小,单位byte
+ internal var size: Int = 0
+
+
+ public init() { }
+}
+
+
+/// mp3文件模型
+public class IBMp3File:Codable {
+ /// 歌曲名字
+ var name: String = ""
+ /// 文件类型,2:MP3
+ var type: Int = 2
+ /// 文件大小,单位byte
+ var size: Int = 0
+
+ internal init(data:IBSendMp3) {
+ self.name = data.name
+ self.size = data.size
+ }
+}
+
--- /dev/null
+//
+// IBOta.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/6/6.
+//
+
+import Foundation
+/// 发送OTA文件模型
+public class IBSendOta:Codable {
+ /// 文件路径
+ public var filePath: String = ""
+ /// 文件大小,单位byte
+ internal var size: Int = 0
+ /// major是主版本号,mid是中间版本号,minor是次版本号,用于与手表当前版本比较,新于当前版本才可升级
+ public var major: Int = 0
+ /// mid是中间版本号
+ public var mid: Int = 0
+ /// minor是次版本号
+ public var minor: Int = 0
+ /// 是否强制升级,0为非强制,1为强制
+ public var isForced: Int = 0
+
+ public init() { }
+}
+/// OTA文件模型
+public class IBOtaFile:Codable {
+ /// 文件类型,1:OTA
+ internal var type: Int = 1
+ /// 文件大小,单位byte
+ internal var size: Int = 0
+ /// major是主版本号,mid是中间版本号,minor是次版本号,用于与手表当前版本比较,新于当前版本才可升级
+ internal var major: Int = 0
+ /// mid是中间版本号
+ internal var mid: Int = 0
+ /// minor是次版本号
+ internal var minor: Int = 0
+ /// 是否强制升级,0为非强制,1为强制
+ internal var isForced: Int = 0
+
+ internal init(data:IBSendOta) {
+ self.size = data.size
+ self.major = data.major
+ self.mid = data.mid
+ self.minor = data.minor
+ self.isForced = data.isForced
+ }
+}
--- /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
+//
+// 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 IBScreenConfig {
+ /// 类型 0,亮度调节; 1,亮屏时长,抬腕亮屏
+ public var type: Int = 0
+
+ /// 亮度 [51, 255], type为0必须设置
+ public var brightness: Int?
+
+ /// 亮屏时长, type为1必须设置
+ public var duration: IBDuration?
+
+ /// 抬腕亮屏(0=关闭, 1=开启), type为1必须设置
+ public var wrist: Int?
+
+ public init() { }
+
+ var description: String {
+ var str = "IBScreenConfig(type: \(type),"
+ if let brightness = brightness{
+ str += "brightness: \(brightness ))"
+ }
+ if let duration = duration {
+ str += "duration: \(duration )"
+ }
+ if let wrist = wrist {
+ str += "wrist: \(wrist ))"
+ }
+ return str
+ }
+}
+
+/// 屏幕参数
+public class IBScreen {
+ /// 亮度 [0, 255]
+ public var brightness: Int = 10
+
+ /// 亮屏时长
+ public var duration: IBDuration = .second10
+
+ /// 抬腕亮屏(0=关闭, 1=开启)
+ public var wrist: Int = 1
+
+ public init() { }
+
+ init(_ data: Data) {
+ brightness = data[0].int
+ duration = IBDuration(rawValue: data[1].int) ?? .second5
+ wrist = data[2].int
+ }
+ var description: String {
+ return "IBScreen(brightness: \(brightness), duration:\(duration), wrist:\(wrist))"
+ }
+}
+
+public enum IBDuration: Int {
+ /// 5 秒
+ case second5 = 0
+ /// 10 秒
+ case second10
+ /// 15 秒
+ case second15
+ /// 30 秒
+ case second30
+ /// 1 分钟
+ case minute
+ /// 5 分钟
+ case minute5
+}
--- /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 IBVibrateConfig {
+ /// 类型 0系统音量调节,1铃声音量调节,3铃声开关,震动开关
+ public var type: Int = 0
+ /// 系统音量调节0~100(type为0设置)
+ public var systemValue: Int?
+
+ /// 铃声音量调节0~100(type为1设置)
+ public var ringsValue: Int?
+
+ /// 静音开关0:关闭 1:开启(type为3设置)
+ public var ringsState: Int?
+
+ /// 震动设置 0:关闭 1:弱 2:中 3:强 (type为3设置)
+ public var vibrateState: Int?
+
+ public init() { }
+
+ var description: String {
+ return "IBVibrateConfig(type: \(type),systemValue: \(systemValue), ringsValue:\(ringsValue), ringsState:\(ringsState), vibrateState:\(vibrateState))"
+ }
+}
+
+/// 声音震动模型
+public class IBVibrateSound {
+ /// 系统音量调节0~100
+ public var systemValue: Int = 0
+
+ /// 铃声音量调节0~100
+ public var ringsValue: Int = 0
+
+ /// 媒体音量调节0~100
+ public var mediaValue: Int = 0
+
+ /// 静音开关0:关闭 1:开启
+ public var ringsState: Int = 0
+
+ /// 震动设置 0:关闭 1:弱 2:中 3:强
+ public var vibrateState: Int = 0
+
+ public init() { }
+
+ init(_ data: Data) {
+ systemValue = data[0].int
+ ringsValue = data[1].int
+ mediaValue = data[2].int
+ ringsState = data[3].int
+ vibrateState = data[4].int
+ }
+
+ var description: String {
+ return "IBVibrateSound(systemValue: \(systemValue), ringsValue:\(ringsValue), mediaValue:\(mediaValue), ringsState:\(ringsState), vibrateState:\(vibrateState))"
+ }
+}
+
--- /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) }
+}
+
+
+extension Data{
+ var float: Float {
+ var floatValue: Float = 0.0
+ let size = MemoryLayout<Float>.size
+ guard self.count >= size else { return 0 }
+
+ self.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
+ _ = memcpy(&floatValue, buffer.baseAddress!, size)
+ }
+
+ return floatValue
+ }
+}
--- /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 010101
+ static let long2: Format = .init("yyyy-MM-dd HHmmss")
+
+ /// e.g. 2020-01-01 01:01:01.088
+ static let full: Format = .init("yyyy-MM-dd HH:mm:ss.SSS")
+
+ /// e.g. 2020年01月01日 01:01:01.088
+ static let full1: Format = .init("yyyy年MM月dd日 HH:mm:ss.SSS")
+ }
+
+ /// 格式化成本地时间
+ func format(_ format: Date.Format = .middle) -> String {
+ Self.formatter.dateFormat = format.rawValue
+ return Self.formatter.string(from: self)
+ }
+
+ enum Unit {
+ case minute(Int)
+ case hour(Int)
+ case day(Int)
+
+ /// 转换成秒
+ var second: TimeInterval {
+ switch self {
+ case .minute(let minute): return 60.0 * Double(minute)
+ case .hour(let hour): return 60.0 * 60.0 * Double(hour)
+ case .day(let day): return 60.0 * 60.0 * 24.0 * Double(day)
+ }
+ }
+ }
+}
+
+let today: Date = .init()
+
+/// 本地化
+extension Date {
+ /// 星期几
+ enum Week: Int {
+ /// 星期日
+ case sun = 1
+ /// 星期一
+ case mon
+ /// 星期二
+ case tue
+ /// 星期三
+ case wed
+ /// 星期四
+ case thur
+ /// 星期五
+ case fri
+ /// 星期六
+ case sat
+ }
+
+// /// 当天的第一秒
+// var fromTimeInterval: TimeInterval {
+// let today = format(.short)
+// return today.timeIntervalSince1970(.short)
+// }
+//
+// /// 当天最后一秒
+// var toTimeInterval: TimeInterval { fromTimeInterval + Unit.day(1).second }
+
+ /// 哪一年
+ var year: Int { component(.year) }
+
+ /// 哪个月
+ var month: Int { component(.month) }
+
+ /// 当月的第几天
+ var day: Int { component(.day) }
+
+ /// 当月的第几天
+ var hour: Int { component(.hour)}
+
+ /// 当月的第几天
+ var minute: Int { component(.minute)}
+
+ /// 当月的第几天
+ var second: Int { component(.second)}
+
+ /// 当月的第几周
+ var weekOfMonth: Int { component(.weekOfMonth) }
+
+ /// 今年的第几周
+ var weekOfYear: Int { component(.weekOfYear) }
+
+ /// 星期几
+ var weekDay: Week { Week(rawValue: component(.weekday))! }
+
+ /// 假设当天是周五,表示本月的第几个周五
+ var weekdayOrdinal: Int { component(.weekdayOrdinal) }
+
+ private func component(_ component: Calendar.Component) -> Int {
+ return Calendar.autoupdatingCurrent.component(component, from: self)
+ }
+}
--- /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
+//
+// IBTools.swift
+// IOTBluetooth
+//
+// Created by incar on 2024/7/17.
+//
+
+import Foundation
+
+class IBTools{
+ static func saveToFile(data:Data)->String?{
+ let filePath = NSTemporaryDirectory() + "temp.pcm"
+ print(filePath)
+
+ let url = URL(fileURLWithPath: filePath)
+ do {
+ try data.write(to: url, options: .atomic)
+ return filePath
+ }
+ catch{
+ print(error)
+ return nil
+ }
+ }
+
+ static func readFile(from path:String) ->Data?{
+ let fileURL = URL(fileURLWithPath: path)
+ do {
+ let data = try Data(contentsOf:fileURL)
+ return data
+ } catch {
+ return nil
+ }
+ }
+}
--- /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
+
+/// 日记管理
+public class Logger {
+ static var isDebug: Bool = false
+
+ fileprivate static let shared = Logger()
+ private var fileHandle:FileHandle?
+ private init() {
+ let path = getPath()
+ fileHandle = FileHandle(forWritingAtPath: path)!
+// fileHandle?.seekToEndOfFile()
+ }
+
+ fileprivate var dateString: String { Date().format(.full) }
+
+ static var logs: [String] = []
+
+
+ private func getPath()->String{
+// let path = NSHomeDirectory() + "/Documents/IOTBluetooth/" + "\(Date().format(.middle)).log"
+ let foldPath = NSHomeDirectory() + "/Documents/IOTBluetooth/"
+ let fileManager = FileManager.default
+ if !fileManager.fileExists(atPath: foldPath) {
+ do {
+ try FileManager.default.createDirectory(atPath: foldPath, withIntermediateDirectories: true, attributes: nil)
+ } catch {
+ return ""
+ }
+ }
+ let path = foldPath + "\(Date().format(.long2)).log"
+ if !FileManager.default.fileExists(atPath: path) {
+ FileManager.default.createFile(atPath: path, contents: nil, attributes: nil)
+ }
+ return path
+ }
+}
+
+public extension Logger {
+ static func log(_ logs: Any..., line: Int = #line, separator: String = " ", terminator: String = "\n") {
+ objc_sync_enter(Logger.shared)
+ let logString = Logger.shared.dateString + " [IOTBluetooth-\(String(format: "%03d", line))]: " + logs.map({ "\($0)" }).joined(separator: separator) + terminator
+ if Logger.isDebug {
+ print(Logger.shared.dateString, terminator: " [IOTBluetooth-\(String(format: "%03d", line))]: ")
+ print(logs, separator: separator, terminator: terminator)
+ Self.logs.append(logString)
+ }
+ Logger.shared.saveLog(log: logString)
+ objc_sync_exit(Logger.shared)
+
+
+ }
+
+ private func saveLog(log:String){
+ fileHandle?.write(log.data(using: .utf8)!)
+// if #available(iOS 13.0, *) {
+// try? fileHandle.close()
+// } else {
+// // Fallback on earlier versions
+// }
+ }
+}
--- /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) }
+}
--- /dev/null
+Incident Identifier: 3411013E-3426-431E-96C5-F1BE8887879B
+Distributor ID: com.apple.TestFlight
+Hardware Model: iPhone15,4
+Process: f002 [785]
+Path: /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/f002
+Identifier: com.fengeek.f002
+Version: 3.4.27 (50)
+AppStoreTools: 16B39
+AppVariant: 1:iPhone15,4:18
+Beta: YES
+Code Type: ARM-64 (Native)
+Role: Foreground
+Parent Process: launchd [1]
+Coalition: com.fengeek.f002 [677]
+
+Date/Time: 2024-11-04 16:05:00.8204 +0800
+Launch Time: 2024-11-04 15:36:23.8518 +0800
+OS Version: iPhone OS 18.0.1 (22A3370)
+Release Type: User
+Baseband Version: 2.16.06
+Report Version: 104
+
+Exception Type: EXC_BREAKPOINT (SIGTRAP)
+Exception Codes: 0x0000000000000001, 0x000000010a5abaa0
+Triggered by Thread: 16
+
+
+Thread 0 name:
+Thread 0:
+0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1)
+1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201)
+2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0)
+3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323)
+4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617)
+5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001)
+6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414)
+7 GraphicsServices 0x00000001d605e1c4 GSEventRunModal + 164 (GSEvent.c:2196)
+8 UIKitCore 0x000000018d11e2c0 -[UIApplication _run] + 816 (UIApplication.m:3825)
+9 UIKitCore 0x000000018d1ccddc UIApplicationMain + 340 (UIApplication.m:5477)
+10 f002 0x00000001058fc5e4 main + 64 (AppDelegate.swift:20)
+11 dyld 0x00000001afd9cd34 start + 2724 (dyldMain.cpp:1334)
+
+Thread 1 name:
+Thread 1:
+0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1)
+1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201)
+2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0)
+3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323)
+4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617)
+5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001)
+6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414)
+7 Foundation 0x000000018925f000 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:373)
+8 Foundation 0x000000018925ee50 -[NSRunLoop(NSRunLoop) runUntilDate:] + 64 (NSRunLoop.m:420)
+9 UIKitCore 0x000000018d131714 -[UIEventFetcher threadMain] + 420 (UIEventFetcher.m:1241)
+10 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991)
+11 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+12 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 2 name:
+Thread 2:
+0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1)
+1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201)
+2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0)
+3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323)
+4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617)
+5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001)
+6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414)
+7 Foundation 0x000000018925f000 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:373)
+8 Foundation 0x000000018925eed4 -[NSRunLoop(NSRunLoop) run] + 64 (NSRunLoop.m:398)
+9 f002 0x0000000106329568 +[UMSocialTaskOperation networkRequestThreadEntryPoint:] + 184
+10 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991)
+11 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+12 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 3 name:
+Thread 3:
+0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1)
+1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201)
+2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0)
+3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323)
+4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617)
+5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001)
+6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414)
+7 CoreFoundation 0x000000018a634850 CFRunLoopRun + 64 (CFRunLoop.c:3440)
+8 f002 0x0000000106108214 +[JCORECacheThread run] + 180 (JCORECacheThread.m:31)
+9 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991)
+10 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+11 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 4 name:
+Thread 4:
+0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1)
+1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201)
+2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0)
+3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323)
+4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617)
+5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001)
+6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414)
+7 Foundation 0x000000018925f000 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:373)
+8 Foundation 0x000000018925eed4 -[NSRunLoop(NSRunLoop) run] + 64 (NSRunLoop.m:398)
+9 f002 0x00000001060914c4 +[JCOREQueueManager threadEntryPoint:] + 216 (JCOREQueueManager.m:32)
+10 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991)
+11 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+12 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 5 name:
+Thread 5:
+0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1)
+1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201)
+2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0)
+3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323)
+4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617)
+5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001)
+6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414)
+7 CoreFoundation 0x000000018a634850 CFRunLoopRun + 64 (CFRunLoop.c:3440)
+8 f002 0x00000001060a2590 +[JCORENetworkThread run] + 180 (JCORENetworkController.m:40)
+9 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991)
+10 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+11 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 6:
+0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1)
+1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)
+2 f002 0x000000010735f084 native_event_wait + 208
+3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228
+4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228
+5 f002 0x00000001073757c0 socket_main + 224
+6 f002 0x00000001073605d0 ThreadEntryHook + 548
+7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 7:
+0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1)
+1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)
+2 f002 0x000000010735f084 native_event_wait + 208
+3 f002 0x00000001073254ec dns_main + 108
+4 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+5 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 8:
+0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1)
+1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)
+2 f002 0x000000010735f084 native_event_wait + 208
+3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228
+4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228
+5 f002 0x000000010738d27c iFlylua_engine_main + 2832
+6 f002 0x00000001073605d0 ThreadEntryHook + 548
+7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 9:
+0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1)
+1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)
+2 f002 0x000000010735f084 native_event_wait + 208
+3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228
+4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228
+5 f002 0x000000010738d27c iFlylua_engine_main + 2832
+6 f002 0x00000001073605d0 ThreadEntryHook + 548
+7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 10:
+0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1)
+1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)
+2 f002 0x000000010735f084 native_event_wait + 208
+3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228
+4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228
+5 f002 0x000000010738d27c iFlylua_engine_main + 2832
+6 f002 0x00000001073605d0 ThreadEntryHook + 548
+7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 11:
+0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1)
+1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)
+2 f002 0x000000010735f084 native_event_wait + 208
+3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228
+4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228
+5 f002 0x000000010738d27c iFlylua_engine_main + 2832
+6 f002 0x00000001073605d0 ThreadEntryHook + 548
+7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 12:
+0 libsystem_kernel.dylib 0x00000001da412030 __psynch_cvwait + 8 (:-1)
+1 libsystem_pthread.dylib 0x0000000211962a50 _pthread_cond_wait + 1204 (pthread_cond.c:862)
+2 f002 0x000000010735f084 native_event_wait + 208
+3 f002 0x000000010735f3c0 internal_MSPThread_WaitMessages + 228
+4 f002 0x000000010735f2b4 MSPThread_WaitMessages + 228
+5 f002 0x000000010738d27c iFlylua_engine_main + 2832
+6 f002 0x00000001073605d0 ThreadEntryHook + 548
+7 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+8 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 13 name:
+Thread 13:
+0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1)
+1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201)
+2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0)
+3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323)
+4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617)
+5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001)
+6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414)
+7 CFNetwork 0x000000018bb40f44 +[__CFN_CoreSchedulingSetRunnable _run:] + 416 (CoreSchedulingSet.mm:1473)
+8 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991)
+9 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+10 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 14 name:
+Thread 14:
+0 libsystem_kernel.dylib 0x00000001da4142d4 __select + 8 (:-1)
+1 CoreFoundation 0x000000018a6324c0 __CFSocketManager + 704 (CFSocket.c:1341)
+2 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+3 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 15:
+0 libsystem_kernel.dylib 0x00000001da412250 __semwait_signal + 8 (:-1)
+1 libsystem_c.dylib 0x00000001922cba78 nanosleep + 220 (nanosleep.c:104)
+2 libsystem_c.dylib 0x0000000192329550 sleep + 52 (sleep.c:62)
+3 StarburstConnectKit 0x000000010a63705c _mqtt_process_thread + 56
+4 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+5 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 16 Crashed:
+0 StarburstConnectKit 0x000000010a5abaa0 MqttFileManager.outputProgress() + 980
+1 StarburstConnectKit 0x000000010a5a9110 MqttFileManager.onMessageArrival(topic:payload:) + 1180
+2 StarburstConnectKit 0x000000010a520824 AliCloudIotWatchManager_C.onMessageArrival(topic:payload:) + 440
+3 StarburstConnectKit 0x000000010a559d94 closure #2 in IotMqttManager_C.setupSdkWrapperCallback() + 416
+4 StarburstConnectKit 0x000000010a559e4c thunk for @escaping @callee_guaranteed (@guaranteed String, @guaranteed String) -> () + 88
+5 StarburstConnectKit 0x000000010a49d718 __30-[MqttManager_C setupCallback]_block_invoke_2 + 568
+6 StarburstConnectKit 0x000000010a636c94 _mqtt_default_recv_handler + 324
+7 StarburstConnectKit 0x000000010a63f158 _core_mqtt_pub_handler + 1464
+8 StarburstConnectKit 0x000000010a63e1b4 aiot_mqtt_recv + 1288
+9 StarburstConnectKit 0x000000010a63709c _mqtt_recv_thread + 40
+10 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+11 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 17:
+0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1)
+1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201)
+2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0)
+3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323)
+4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617)
+5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001)
+6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414)
+7 ASIHTTPRequest 0x00000001091f5670 +[ASIHTTPRequest runRequests] + 140 (ASIHTTPRequest.m:4797)
+8 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991)
+9 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+10 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 18 name:
+Thread 18:
+0 libsystem_kernel.dylib 0x00000001da40c728 mach_msg2_trap + 8 (:-1)
+1 libsystem_kernel.dylib 0x00000001da40fe38 mach_msg2_internal + 80 (mach_msg.c:201)
+2 libsystem_kernel.dylib 0x00000001da40fd50 mach_msg_overwrite + 424 (mach_msg.c:0)
+3 libsystem_kernel.dylib 0x00000001da40fb9c mach_msg + 24 (mach_msg.c:323)
+4 CoreFoundation 0x000000018a5c97cc __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2617)
+5 CoreFoundation 0x000000018a5c8e78 __CFRunLoopRun + 1212 (CFRunLoop.c:3001)
+6 CoreFoundation 0x000000018a5c85b8 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3414)
+7 Foundation 0x000000018925f000 -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212 (NSRunLoop.m:373)
+8 StarburstSdk 0x000000010bf22c34 -[SRRunLoopThread main] + 216
+9 Foundation 0x0000000189270158 __NSThread__start__ + 724 (NSThread.m:991)
+10 libsystem_pthread.dylib 0x000000021196537c _pthread_start + 136 (pthread.c:931)
+11 libsystem_pthread.dylib 0x0000000211960494 thread_start + 8 (:-1)
+
+Thread 19:
+0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1)
+
+Thread 20:
+0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1)
+
+Thread 21 name:
+Thread 21:
+0 libsystem_kernel.dylib 0x00000001da412250 __semwait_signal + 8 (:-1)
+1 libsystem_c.dylib 0x00000001922cba78 nanosleep + 220 (nanosleep.c:104)
+2 libsystem_c.dylib 0x0000000192329550 sleep + 52 (sleep.c:62)
+3 f002 0x00000001062c7c40 __48-[BLYLogicManager abortAfterSendingReportIfNeed]_block_invoke + 36
+4 libdispatch.dylib 0x0000000192270370 _dispatch_call_block_and_release + 32 (init.c:1549)
+5 libdispatch.dylib 0x00000001922720d0 _dispatch_client_callout + 20 (object.m:576)
+6 libdispatch.dylib 0x0000000192283f60 _dispatch_root_queue_drain + 860 (queue.c:7331)
+7 libdispatch.dylib 0x0000000192284590 _dispatch_worker_thread2 + 156 (queue.c:7399)
+8 libsystem_pthread.dylib 0x0000000211963c40 _pthread_wqthread + 228 (pthread.c:2709)
+9 libsystem_pthread.dylib 0x0000000211960488 start_wqthread + 8 (:-1)
+
+Thread 22:
+0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1)
+
+Thread 23:
+0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1)
+
+Thread 24:
+0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1)
+
+Thread 25:
+0 libsystem_pthread.dylib 0x0000000211960480 start_wqthread + 0 (:-1)
+
+
+Thread 16 crashed with ARM Thread State (64-bit):
+ x0: 0xe000000000000000 x1: 0x0000000000000000 x2: 0x00000001edacf940 x3: 0x0000000188faad4c
+ x4: 0x00000001edaec480 x5: 0x000000016b40e688 x6: 0x000000016b40e680 x7: 0x0000000000000001
+ x8: 0x000000007fc00000 x9: 0x000000007f800000 x10: 0x0000000000000000 x11: 0xc000000000000000
+ x12: 0x0000000000000003 x13: 0x000000010a5abad8 x14: 0x000000010a5ab750 x15: 0x0000000000000038
+ x16: 0x000000018900926c x17: 0x871080018a57b310 x18: 0x0000000000000000 x19: 0x00000003035ea520
+ x20: 0x00000003035ea520 x21: 0x0000000000000018 x22: 0x40000003004f73c0 x23: 0x00000003035ea550
+ x24: 0x000000016b40e6d0 x25: 0x40000003035fd260 x26: 0xc00000000000003f x27: 0x0000000303aaf3a0
+ x28: 0x000000010a709650 fp: 0x000000016b40e6c0 lr: 0x000000010a5a9110
+ sp: 0x000000016b40e620 pc: 0x000000010a5abaa0 cpsr: 0x40001000
+ esr: 0x56000080 Address size fault
+
+
+Binary Images:
+ 0x104ca8000 - 0x107e3bfff f002 arm64 <0320a73500ea3635a097cc991f7d6a36> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/f002
+ 0x10907c000 - 0x109087fff libobjc-trampolines.dylib arm64e <ece8465d62ec35e7810fcdedfc9817bf> /private/preboot/Cryptexes/OS/usr/lib/libobjc-trampolines.dylib
+ 0x109168000 - 0x10916ffff DeviceUtil arm64 <5b7002a6e4ae393898153b49c2f30a09> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/DeviceUtil.framework/DeviceUtil
+ 0x109180000 - 0x109187fff KeychainItemWrapper_hicool arm64 <0aecce381ae03c38a0d9259834776667> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/KeychainItemWrapper_hicool.framework/KeychainItemWrapper_hicool
+ 0x109194000 - 0x1091affff CocoaLumberjack arm64 <210f1a7f805d396b806eeebf33043c82> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/CocoaLumberjack.framework/CocoaLumberjack
+ 0x1091dc000 - 0x10920ffff ASIHTTPRequest arm64 <19c5ad39057336ea8628b1bd076e7edc> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/ASIHTTPRequest.framework/ASIHTTPRequest
+ 0x109268000 - 0x10926ffff Reachability arm64 <b3fe996b41723d8a82ce91fd8981fd82> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/Reachability.framework/Reachability
+ 0x109290000 - 0x10929ffff FMDB arm64 <0a4574e0c56939f8a3e37d31638c7f8b> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/FMDB.framework/FMDB
+ 0x1092b8000 - 0x1092c7fff MBProgressHUD arm64 <1b35240fbb303e9ebe22109e2e663b29> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/MBProgressHUD.framework/MBProgressHUD
+ 0x1092dc000 - 0x1092ebfff Masonry arm64 <4323d49838413f8586cd40ba8186f27d> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/Masonry.framework/Masonry
+ 0x1093b8000 - 0x1093dffff HandyJSON arm64 <14b18b15e89e3d93a186a51d7560c99a> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/HandyJSON.framework/HandyJSON
+ 0x109428000 - 0x10943ffff MJRefresh arm64 <db249bcb2ca637a191c14d4fab5123a6> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/MJRefresh.framework/MJRefresh
+ 0x109464000 - 0x10946bfff RxRelay arm64 <7fa90ff5197b3f7ebdc082a2ce376a9b> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/RxRelay.framework/RxRelay
+ 0x109480000 - 0x10948bfff WebViewJavascriptBridge arm64 <56de5e334eb230ff8b976207dc0cf3e1> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/WebViewJavascriptBridge.framework/WebViewJavascriptBridge
+ 0x1094bc000 - 0x109517fff Alamofire arm64 <c40efa1cf80f308cb7dd59699254e028> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/Alamofire.framework/Alamofire
+ 0x1095c0000 - 0x1095cffff FOTA arm64 <fea11022a0af3733afd150a3b9abfeee> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/FOTA.framework/FOTA
+ 0x109608000 - 0x109623fff SDWebImage arm64 <fdb627c752b533d7917f0117d2cc8419> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/SDWebImage.framework/SDWebImage
+ 0x10966c000 - 0x1096d3fff Logan arm64 <b129d117e7173593923c20b10e163129> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/Logan.framework/Logan
+ 0x109718000 - 0x10976bfff OpusKit arm64 <b45903d21a183aadbf954f61c724b08d> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/OpusKit.framework/OpusKit
+ 0x1097c4000 - 0x1097c7fff iCloudDriveFileProviderOverride arm64e <d4ce523a097a3f62bb14fcf042d81471> /System/Library/Frameworks/FileProvider.framework/OverrideBundles/iCloudDriveFileProviderOverride.bundle/iCloudDriveFileProviderOverride
+ 0x1097d8000 - 0x1097effff DequeModule arm64 <1ee643420f0e35c2bcc4d7f17ba3246d> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/DequeModule.framework/DequeModule
+ 0x109890000 - 0x1098c7fff DeviceManager arm64 <01f9ebcc6b43309dbfcf690e05e759ce> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/DeviceManager.framework/DeviceManager
+ 0x1098f8000 - 0x10996bfff RxCocoa arm64 <700265132423300abd58e67fec50fec4> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/RxCocoa.framework/RxCocoa
+ 0x109a6c000 - 0x109ae3fff SQLite arm64 <213c6061424036b7a3ae409de50876b0> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/SQLite.framework/SQLite
+ 0x109c6c000 - 0x109d1bfff RxSwift arm64 <ceae098f6c2a343794d26eb9487546a1> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/RxSwift.framework/RxSwift
+ 0x109e74000 - 0x109e9ffff CocoaAsyncSocket arm64 <c4c8efc049c13552aeaa41f6e1f1226e> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/CocoaAsyncSocket.framework/CocoaAsyncSocket
+ 0x109ff8000 - 0x109ffffff FileProviderOverride arm64e <39396754b75931aab410d42869570a7f> /System/Library/Frameworks/FileProvider.framework/OverrideBundles/FileProviderOverride.bundle/FileProviderOverride
+ 0x10a010000 - 0x10a093fff FIILBluetoothSDK arm64 <57216117281d3a8097dc3b77cd08f8c1> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/FIILBluetoothSDK.framework/FIILBluetoothSDK
+ 0x10a488000 - 0x10a6bffff StarburstConnectKit arm64 <4d919c9bb7ef35b89df0c74d01609e6c> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/StarburstConnectKit.framework/StarburstConnectKit
+ 0x10aaac000 - 0x10ab9bfff Airoha1530 arm64 <a47bd5e379c537ae84bc4ac66fa6e5b8> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/Airoha1530.framework/Airoha1530
+ 0x10bf0c000 - 0x10c08bfff StarburstSdk arm64 <a51beaeaf7543ef5adc3e8c97a5b19ff> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/StarburstSdk.framework/StarburstSdk
+ 0x10c654000 - 0x10c86ffff SpeechSDK arm64 <e2bc2e37ff613ef993e97d4e899f89af> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/SpeechSDK.framework/SpeechSDK
+ 0x10ca00000 - 0x10ca0bfff NativePeq arm64 <19f476b9c5af33bdb0d97bf148547821> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/NativePeq.framework/NativePeq
+ 0x110660000 - 0x110687fff NativeAnc arm64 <572dbe6843623f2e8138893fbba3528b> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/NativeAnc.framework/NativeAnc
+ 0x116708000 - 0x116e5ffff AirohaUnifyLibrary arm64 <ca82cdc158f63e669fcb12af824a25bc> /private/var/containers/Bundle/Application/CE756EE5-0E65-4D5D-BAF6-684565D7958B/f002.app/Frameworks/AirohaUnifyLibrary.framework/AirohaUnifyLibrary
+ 0x188bd0000 - 0x18916ffff libswiftCore.dylib arm64e <24ec482a274c32f2aa6da809fabfdc58> /usr/lib/swift/libswiftCore.dylib
+ 0x1891a8000 - 0x189ec6fff Foundation arm64e <6d058a2d05cf3a298bebb1030ae97282> /System/Library/Frameworks/Foundation.framework/Foundation
+ 0x18a575000 - 0x18aabafff CoreFoundation arm64e <4250d45c51a93ac58c3d69de936fad46> /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
+ 0x18ba43000 - 0x18be05fff CFNetwork arm64e <7d799a941e3c3a4fb1a0d9414e8eaca5> /System/Library/Frameworks/CFNetwork.framework/CFNetwork
+ 0x18cd40000 - 0x18ec00fff UIKitCore arm64e <187c890e8e4c32f2a97efa905a55f094> /System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore
+ 0x19226e000 - 0x1922b3fff libdispatch.dylib arm64e <b8c15e69d076317d92961279500738fc> /usr/lib/system/libdispatch.dylib
+ 0x1922b4000 - 0x192333ff3 libsystem_c.dylib arm64e <1ad666a001a831c188b9808915fb97b4> /usr/lib/system/libsystem_c.dylib
+ 0x1afd69000 - 0x1afdec693 dyld arm64e <5db839882ee63756bd07b8d67b1133a5> /usr/lib/dyld
+ 0x1d605d000 - 0x1d6065fff GraphicsServices arm64e <227122eb72fd3ecb9f66986addd8e048> /System/Library/PrivateFrameworks/GraphicsServices.framework/GraphicsServices
+ 0x1da40b000 - 0x1da444ffb libsystem_kernel.dylib arm64e <8d929b3bd4d639bfadbbbe59e928b332> /usr/lib/system/libsystem_kernel.dylib
+ 0x21195f000 - 0x21196bff3 libsystem_pthread.dylib arm64e <fcc77eb0558f370392675419c4d33ae7> /usr/lib/system/libsystem_pthread.dylib
+
+EOF