iOS App 通過CoreBluetooth(Swift 藍牙)和Android(低功耗藍牙BLE)交互。

> 概念

如果你是進來找代碼的,那么直接拉到最后!!!
本文概念參考的了Pein_Ju的文章BLE藍牙開發(fā)—Swift版 本文更像是是偏向于在工作中記錄和實踐性,大佬請隨意鄙視??。我的代碼連接放在最后。

  1. 現(xiàn)在iOS BLE開發(fā)一般調用的是CoreBluetooth系統(tǒng)原生庫開發(fā)的藍牙4.0以上的低功耗版本,其他連接方式和版本的暫不討論。

  2. 藍牙術語:

    * CBCentralManager //系統(tǒng)藍牙設備管理對象
    * CBPeripheral //外圍設備
    * CBService //外圍設備的服務或者服務中包含的服務
    * CBCharacteristic //服務的特性
    * CBDescriptor //特性的描述符
    

    關系圖如下:


    關系圖
    1. 模式 & 步驟
      • 中心模式 Client

        1. 建立中心角色 CBCentralManager
        2. 掃描外設 cancelPeripheralConnection
        3. 發(fā)現(xiàn)外設 didDiscoverPeripheral
        4. 連接外設 connectPeripheral
        5. 掃描外設中的服務 discoverServices
        6. 發(fā)現(xiàn)并獲取外設中的服務 didDiscoverServices
        7. 掃描外設對應服務的特征 discoverCharacteristics
        8. 發(fā)現(xiàn)并獲取外設對應服務的特征 didDiscoverCharacteristicsForService
        9. 給對應特征寫數(shù)據(jù) writeValue:forCharacteristic:type:
        10. 訂閱特征的通知 setNotifyValue:forCharacteristic:
        11. 根據(jù)特征讀取數(shù)據(jù) didUpdateValueForCharacteristic
      • 外設模式 Server --->

        1. 建立外設角色
        2. 設置本地外設的服務和特征
        3. 發(fā)布外設和特征
        4. 廣播服務
        5. 響應中心的讀寫請求
        6. 發(fā)送更新的特征值,訂閱中心
        • Android提供服務參考這里
        • iOS也可以作為外設(Server)參考這里

> Tips

  • 用上面的方式進行掃描后能獲得的設備是正在廣播的設備。這就可能和系統(tǒng)的列表不一樣,連接的時候需要Android作為Server。
  • 需要注意外設,服務,特征之間的uuid,斷線重連是用的Peripherals的uuid不要弄混了??

> 具體連接步驟

方式1 原生連接
1.實現(xiàn)代理及代理方法 CBCentralManagerDelegate,CBPeripheralDelegate
2.在代理方法 centralManagerDidUpdateState 中檢測到藍牙設備的狀態(tài)是poweredOn 才能開始掃描設備,要不然找不到~

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        
        tempInputView.text = "初始化對象后,來到centralManagerDidUpdateState"
        
        switch central.state {
        case .unknown:
            print("CBCentralManager state:", "unknown")
            break
        case .resetting:
            print("CBCentralManager state:", "resetting")
            break
        case .unsupported:
            print("CBCentralManager state:", "unsupported")
            break
        case .unauthorized:
            print("CBCentralManager state:", "unauthorized")
            break
        case .poweredOff:
            print("CBCentralManager state:", "poweredOff")
            break
        case .poweredOn:
            print("CBCentralManager state:", "poweredOn")
            //MARK: -3.掃描周圍外設(支持藍牙)
            // 第一個參數(shù),傳外設uuid,傳nil,代表掃描所有外設
            self.addInputString(str: "開始掃描設備")
            central.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber.init(value: false)])
        }
    }

3.發(fā)現(xiàn)設備回調的方法是:

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        
        print("=============================start")
        
        if (peripheral.name != nil && peripheral.name! != "xxb") { //排除 xxb
            
            print("peripheral.name = \(peripheral.name!)")
            print("central = \(central)")
            print("peripheral = \(peripheral)")
            print("RSSI = \(RSSI)")
            print("advertisementData = \(advertisementData)")
            
            deviceList.append(peripheral)
            
            tableView.reloadData()
        }
        print("=============================end")

    }

注意:這里面是每尋找到一個設備就會回調一次這個方法。

4.選中列表中其中一個點擊進行連接:

self.addInputString(str: "鏈接設備")
            central.stopScan()
            central.cancelPeripheralConnection(p)
            central.connect(p, options: nil)

5.連接成功,連接失敗的回調,其中連接成功了會記錄對應的外設并且開始尋找服務

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        //設備鏈接成功
        self.addInputString(str: "鏈接成功=====>\(peripheral.name ?? "~~")")
        peripheralSelected = peripheral
        peripheralSelected!.delegate = self
        peripheralSelected!.discoverServices(nil) // 開始尋找Services。傳入nil是尋找所有Services
    }
    
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        //設備鏈接失敗
        self.addInputString(str: "鏈接失敗=====>\(peripheral.name ?? "~~")")
        
    }

6.尋找到對應的服務特征會回調到peripheral didDiscoverServices 如果這里有和后臺商量好的對一個的service可以做判斷,當前demo是傳入了nil,發(fā)現(xiàn)所有service,并且利用前面保存好的外設去調用發(fā)現(xiàn)特征,這里傳入nil,和前文的意思相同。

    //請求周邊去尋找他的服務特征
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        
        if error != nil {
            self.addInputString(str: "didDiscoverServices error ====> \(error.debugDescription) ")
            return
        }
    
        guard let serArr = peripheral.services else {
            self.addInputString(str: "Peripheral services is nil ")
            return
        }
        
      
        for ser in serArr {
            
            self.addInputString(str: "服務的UUID \(ser.uuid)")
            self.peripheralSelected!.discoverCharacteristics(nil, for: ser)
        }

        self.addInputString(str: "Peripheral 開始尋找特征 ")
        
    }

7.peripheral外設搜索服務后對應的特征回調信息方法是:peripheral didDiscoverCharacteristicsFor

 //找特征的回調
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        
        if error != nil { self.addInputString(str: "服務的回調error \(error.debugDescription)");return}
        
        guard let serviceCharacters = service.characteristics else {
            self.addInputString(str: "service.characteristics 為空")
            return
        }
        
        for characteristic in serviceCharacters {
            self.addInputString(str: "--------------------------characteristic")
            self.addInputString(str: "特征UUID \(characteristic.uuid)")
            self.addInputString(str: "uuidString \(characteristic.uuid.uuidString)")
            peripheralSelected!.setNotifyValue(true, for: characteristic) //接受通知
            //判斷類型 <=========> 有問題的。
            /*
             CBCharacteristicPropertyBroadcast                                                = 0x01,
             CBCharacteristicPropertyRead                                                    = 0x02,
             CBCharacteristicPropertyWriteWithoutResponse                                    = 0x04,
             CBCharacteristicPropertyWrite                                                    = 0x08,
             CBCharacteristicPropertyNotify                                                    = 0x10,
             CBCharacteristicPropertyIndicate                                                = 0x20,
             CBCharacteristicPropertyAuthenticatedSignedWrites                                = 0x40,
             CBCharacteristicPropertyExtendedProperties                                        = 0x80,
             CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0)    = 0x100,
             CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0)    = 0x200
            */
            self.addInputString(str: "characteristic.properties --> \(characteristic.properties)")
            
            switch characteristic.properties {

            case CBCharacteristicProperties.write:
                self.addInputString(str: "characteristic ===> write")
                writeValue(characteristic) //寫入數(shù)據(jù)
                tempCBCharacteristic = characteristic //給個全局的點,
                continue
            case CBCharacteristicProperties.writeWithoutResponse:
                self.addInputString(str: "characteristic ===> writeWithoutResponse")
                continue
            case CBCharacteristicProperties.read:
                self.addInputString(str: "characteristic ===> read")
                continue
            case CBCharacteristicProperties.notify:
                self.addInputString(str: "characteristic ===> notify")
                continue
            case CBCharacteristicProperties.indicate:
                self.addInputString(str: "characteristic ===> indicate") //獲取本身的權限
                /*
                let f = UInt8(characteristic.properties.rawValue) & UInt8(CBCharacteristicProperties.write.rawValue)
                if f == CBCharacteristicProperties.write.rawValue { //判斷本身有沒有寫的權限
                    self.addInputString(str: "characteristic ===> in indicate test write")
                    writeValue(characteristic) //寫入數(shù)據(jù)
                    tempCBCharacteristic = characteristic //給個全局的點,
                }
                */
                continue
            case CBCharacteristicProperties.authenticatedSignedWrites:
                self.addInputString(str: "characteristic ===> authenticatedSignedWrites")
                continue
            case CBCharacteristicProperties.extendedProperties:
                self.addInputString(str: "characteristic ===> extendedProperties")
                continue
            case CBCharacteristicProperties.notifyEncryptionRequired:
                self.addInputString(str: "characteristic ===> notifyEncryptionRequired")
                continue
            case CBCharacteristicProperties.indicateEncryptionRequired:
                self.addInputString(str: "characteristic ===> indicateEncryptionRequired")
                
            default:
                self.addInputString(str: "characteristic ===> default")
                let f = UInt8(characteristic.properties.rawValue) & UInt8(CBCharacteristicProperties.write.rawValue)
            
                if f == CBCharacteristicProperties.write.rawValue { //判斷本身有沒有寫的權限 這個可能是綜合的 ---> 注意 16進制的轉換問題~
                    self.addInputString(str: "characteristic ===> default --test-- write")
                    
                    tempCBCharacteristic = characteristic //給個全局的點,
                    self.addInputString(str: "連接成功,設置全局characteristic設置成功,可以發(fā)送數(shù)據(jù)")
                }
            }
        }
    }
    

ps: 這個里面的代碼主要是做了一個判斷,因為是demo,我全部都寫上了,可以根據(jù)實際情況進行篩選,比方說只需要可以讀的就顯示一個.read就可以了~

注意:這里面的default操作,回調的characteristic.properties可能是一個復合信息,以位運算的形式返回,這里面使用了位操作判斷是否支持讀寫。找到后保存了一個全局的characteristic。 最好將characteristic設置成接收notify,這樣后面能接收到發(fā)送數(shù)據(jù)的回調信息。代碼:peripheralSelected!.setNotifyValue(true, for: characteristic)

8.有了全局的characteristic 就可以發(fā)送信息了。

  func writeValue(_ Characteristic: CBCharacteristic) {
        
        let string = inputTextField.text ?? "~測試數(shù)據(jù)"
        let data = string.data(using: .utf8)
        self.addInputString(str: "寫入測試數(shù)據(jù) ==> ")
        peripheralSelected!.writeValue(data!, for: Characteristic, type: CBCharacteristicWriteType.withResponse)
    }

9.接收Notification 和 服務器回傳的數(shù)據(jù) :

// 獲取外設發(fā)來的數(shù)據(jù)
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        print("接到服務端發(fā)送的數(shù)據(jù)")

        if (characteristic.value != nil) {
            print("開始解析數(shù)據(jù)")
            let str = String.init(data: characteristic.value!, encoding: .utf8)
            print(str)
            receiveMessage.text = receiveMessage.text + "\n" + (str ?? "~")
        }
    }
    //接收characteristic信息
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        print("接收characteristic信息")
    }

> 另一種連接方式:第三方庫,這個庫是OC寫的。利用點語法,挺方便。

BabyBluetooth
SimpleCoreBluetooth (自己用swift封裝的。)

如果作為服務端Server請參考
Android
iOS

本文代碼

參考資料:
Swift語言iOS8的藍牙Bluetooth解析
Pein_Ju的文章BLE藍牙開發(fā)—Swift版

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容