iOS低功耗藍(lán)牙(Corebluetooth)開發(fā)(swift版)

現(xiàn)在公司手頭的項(xiàng)目基本上是基于藍(lán)牙的開發(fā),所以對(duì)低功耗藍(lán)牙還是有一定的了解,今天特意把我寫的藍(lán)牙模塊兒整理出來(lái),供大家參考。


一 : 初識(shí)低功耗藍(lán)牙

1, 中心(central)和外設(shè)(peripheral)

剛接觸藍(lán)牙開發(fā)的時(shí)候,找了許多專業(yè)的資料,講到藍(lán)牙開發(fā)分為兩種模式,中心模式(central),和外設(shè)模式(peripheral)。云里霧里,以至于搞了兩三天都不知道自己做的東西到底是做為中心還是做為外設(shè)。其實(shí)遠(yuǎn)遠(yuǎn)沒有那么復(fù)雜,一般來(lái)講,我們做的需要在軟件內(nèi)連接硬件,通過連接硬件給硬件發(fā)送指令以完成一些動(dòng)作的藍(lán)牙開發(fā)都是基于中心模式(central)模式的開發(fā),也就是說(shuō)我們開發(fā)的app是中心,我們要連接的硬件是外設(shè)。外設(shè)有個(gè)特點(diǎn)是它會(huì)一直廣播自己,告訴中心我在這里,我在這里,快來(lái)連我---,這個(gè)時(shí)候呢,我們的中心就可以通過掃描發(fā)現(xiàn)并且點(diǎn)擊連接了。

2,服務(wù)(service)和特征(character)

我們通過藍(lán)牙連接了硬件,無(wú)非就是想通過連接硬件的讀取一些信息,例如硬件通過溫度傳感器獲取的環(huán)境溫度,或者濕度傳感器獲取的環(huán)境濕度,又或者是硬件自己的電量等等。或者是給這個(gè)設(shè)備發(fā)送一些指令以達(dá)到控制硬件行為的目的,例如我們的app連接了空調(diào),想發(fā)送指令過去改變空調(diào)的預(yù)設(shè)溫度,或者發(fā)送指令給共享單車,讓單車的鎖解開。我們的app中心與設(shè)備之間的通信需要一個(gè)橋梁,這個(gè)橋梁就是服務(wù)與特征。一個(gè)設(shè)備中可能包含很多服務(wù),而一個(gè)服務(wù)可能又包含幾個(gè)特征。比方說(shuō),標(biāo)準(zhǔn)藍(lán)牙模塊會(huì)有幾個(gè)內(nèi)置的服務(wù)與特征如溫度,心跳,等等。


藍(lán)牙標(biāo)準(zhǔn)的服務(wù).png

這些服務(wù)與特征都是硬件工程師可以直接拿來(lái)用的。如果有其他特殊的需求,硬件工程師也可以再重新自己定義服務(wù)與特征。


外設(shè)服務(wù)與特征的關(guān)系.png
3,讀(read) , 寫(write), 訂閱(notify)

我們的目的是讀取設(shè)備中的數(shù)據(jù)(read) , 或者給設(shè)備寫入一定的數(shù)據(jù)(write)。有時(shí)候我們還想設(shè)備的數(shù)據(jù)變化的時(shí)候不需要我們手動(dòng)去讀取這個(gè)值,需要設(shè)備自動(dòng)通知我們它的值變化了,值是多少。把值告訴app,這個(gè)時(shí)候就需要訂閱這個(gè)特征了(notify)


二 : app做為中心(central)編碼

鑒于大部分需求都是以app做為中心,硬件設(shè)備做為外設(shè),所以這里只針對(duì)中心模式做詳細(xì)介紹。
一般來(lái)講,都要求我們的app時(shí)刻與硬件保持連接,當(dāng)然了,這里的時(shí)刻保持連接指的是物理上的,我們的app能看到的是當(dāng)前設(shè)備處于連接狀態(tài)。低功耗藍(lán)牙內(nèi)部的實(shí)現(xiàn)絕對(duì)不是時(shí)刻保持連接,否則也不能稱之為低功耗藍(lán)牙了。Corebluetooth框架內(nèi)部app做為中心的情況下藍(lán)牙管理有一個(gè)中心類,叫CBCentralManager,我們叫它中心管理類。我們手機(jī)的藍(lán)牙開啟狀態(tài),手機(jī)與藍(lán)牙設(shè)備等連接,斷開連接,連接失敗等等都由這個(gè)中心管理類來(lái)控制。為了這個(gè)中心管理類在app的生命周期都存在。我們一般把它進(jìn)行封裝,做成一個(gè)單例。這樣app整個(gè)生命周期中這個(gè)單例時(shí)刻存在。下面是封裝的藍(lán)牙單例代碼。

import Foundation
import CoreBluetooth

//用于看發(fā)送數(shù)據(jù)是否成功!
class LLBlueTooth:NSObject {
    
    //單例對(duì)象
    internal static let instance = LLBlueTooth()
    
    //中心對(duì)象
    var central : CBCentralManager?
    
    //中心掃描到的設(shè)備都可以保存起來(lái),
    //掃描到新設(shè)備后可以通過通知的方式發(fā)送出去,連接設(shè)備界面可以接收通知,實(shí)時(shí)刷新設(shè)備列表
    var deviceList: NSMutableArray?
    
    // 當(dāng)前連接的設(shè)備
    var peripheral:CBPeripheral!
    
    //發(fā)送數(shù)據(jù)特征(連接到設(shè)備之后可以把需要用到的特征保存起來(lái),方便使用)
    var sendCharacteristic:CBCharacteristic?
    
    
    override init() {
        
        super.init()
        
        self.central = CBCentralManager.init(delegate:self, queue:nil, options:[CBCentralManagerOptionShowPowerAlertKey:false])
        
        self.deviceList = NSMutableArray()
        
    }

    
    // MARK: 掃描設(shè)備的方法
    func scanForPeripheralsWithServices(_ serviceUUIDS:[CBUUID]?, options:[String: AnyObject]?){
        
        self.central?.scanForPeripherals(withServices: serviceUUIDS, options: options)
        
    }
    
    
    // MARK: 停止掃描
    func stopScan() {
        
        self.central?.stopScan()
        
    }
    
    // MARK: 寫數(shù)據(jù)
    func writeToPeripheral(_ data: Data) {
        peripheral.writeValue(data , for: sendCharacteristic!, type: CBCharacteristicWriteType.withResponse)
    }
    
    
    // MARK: 連接某個(gè)設(shè)備的方法
    /*
     *  設(shè)備有幾個(gè)狀態(tài)
     @available(iOS 7.0, *)
     public enum CBPeripheralState : Int {
         case disconnected
         
         case connecting
         
         case connected
         
         @available(iOS 9.0, *)
         case disconnecting
     }
     */
    func requestConnectPeripheral(_ model:CBPeripheral) {
        
        if (model.state != CBPeripheralState.connected) {
            
            central?.connect(model , options: nil)
            
        }
        
    }
    
}


//MARK: -- 中心管理器的代理
extension LLBlueTooth : CBCentralManagerDelegate{
    
    // MARK: 檢查運(yùn)行這個(gè)App的設(shè)備是不是支持BLE。
    func centralManagerDidUpdateState(_ central: CBCentralManager){
        
        if #available(iOS 10.0, *) {
            switch central.state {
                
            case CBManagerState.poweredOn:
                print("藍(lán)牙打開")
                
            case CBManagerState.unauthorized:
                print("沒有藍(lán)牙功能")
                
            case CBManagerState.poweredOff:
                print("藍(lán)牙關(guān)閉")
                
            default:
                print("未知狀態(tài)")
            }
        }
        // 手機(jī)藍(lán)牙狀態(tài)發(fā)生變化,可以發(fā)送通知出去。提示用戶
        
    }
    
    
    // 開始掃描之后會(huì)掃描到藍(lán)牙設(shè)備,掃描到之后走到這個(gè)代理方法
    // MARK: 中心管理器掃描到了設(shè)備
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        
        //  在這個(gè)地方可以判讀是不是自己本公司的設(shè)備,這個(gè)是根據(jù)設(shè)備的名稱過濾的
        guard peripheral.name != nil , peripheral.name!.contains("*****") else {
            return
        }
        
        //  這里判斷重復(fù),加到devielist中。發(fā)出通知。
        
    }
    
    
    // MARK: 連接外設(shè)成功,開始發(fā)現(xiàn)服務(wù)
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral){
        
        // 設(shè)置代理
        peripheral.delegate = self
        
        // 開始發(fā)現(xiàn)服務(wù)
        peripheral.discoverServices(nil)
        
        // 保存當(dāng)前連接設(shè)備
        self.peripheral = peripheral
        
        // 這里可以發(fā)通知出去告訴設(shè)備連接界面連接成功
        
    }
    
    // MARK: 連接外設(shè)失敗
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        
        // 這里可以發(fā)通知出去告訴設(shè)備連接界面連接失敗
        
    }
    
    // MARK: 連接丟失
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        NotificationCenter.default.post(name: Notification.Name(rawValue: "DidDisConnectPeriphernalNotification"), object: nil, userInfo: ["deviceList": self.deviceList as AnyObject])

        // 這里可以發(fā)通知出去告訴設(shè)備連接界面連接丟失
        
    }
    
}


// 外設(shè)的代理
extension LLBlueTooth : CBPeripheralDelegate {
    
    //MARK: - 匹配對(duì)應(yīng)服務(wù)UUID
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?){
        
        if error != nil {
            return
        }
        
        for service in peripheral.services! {
            peripheral.discoverCharacteristics(nil, for: service )
        }
        
    }
    
    //MARK: - 服務(wù)下的特征
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?){
        
        if (error != nil){
            return
        }
        
        for  characteristic in service.characteristics! {
            
            switch characteristic.uuid.description {
                
            case "A28DA977":
                // 訂閱特征值,訂閱成功后后續(xù)所有的值變化都會(huì)自動(dòng)通知
                peripheral.setNotifyValue(true, for: characteristic)
            case "******":
                // 讀區(qū)特征值,只能讀到一次
                peripheral.readValue(for:characteristic)
            default:
                print("掃描到其他特征")
            }
            
        }
        
    }
    
    //MARK: - 特征的訂閱狀體發(fā)生變化
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?){
        
        guard error == nil  else {
            return
        }
        
    }
    
    // MARK: - 獲取外設(shè)發(fā)來(lái)的數(shù)據(jù)
    // 注意,所有的,不管是 read , notify 的特征的值都是在這里讀取
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)-> (){
        
        if(error != nil){
            return
        }
        
        switch characteristic.uuid.uuidString {
            
        case "***************":
            print("接收到了設(shè)備的溫度特征的值的變化")
        default:
            print("收到了其他數(shù)據(jù)特征數(shù)據(jù): \(characteristic.uuid.uuidString)")
        }
        
    }
    
    
    
    //MARK: - 檢測(cè)中心向外設(shè)寫數(shù)據(jù)是否成功
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        
        if(error != nil){
            print("發(fā)送數(shù)據(jù)失敗!error信息:\(String(describing: error))")
        }
        
    }
    
}

如果有沒有寫清楚的地方,歡迎大家提問和補(bǔ)充。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容