[iOS] Core Bluetooth 使用筆記

當我回來看之前的筆記時,發現這邊居然沒有寫完,所以現在更新內容。請注意,筆記內容不是針對全體API詳細的使用說明,是屬於比較偏向實作導向,而官方的文件也非常好讀,照著官方的文件也可以順順地寫出可以用的功能。



以下我會以最基本的內容,說明CoreBluetooth的使用概念。
CoreBluetooth的結構其實跟網頁-伺服器的概念一樣,一個是負責發送資料的,一個負責接收並處理資料。在CoreBluetooth裡面分成兩種,Central和Peripheral。

Central

Central的角色就是尋找附近的藍牙設備、連線並且收資料。你要尋找藍牙時,可以限定藍牙提供的服務(Service),比方說你只要搜尋僅提供心率數據服務的設備,那指定好你要找的服務是0x180D,你就可以先進行第一次過濾了。
再來是對於資料的接收,這邊和設備的連接有兩種,一種是設備不定時發送,一種是設備定時發送,兩種各有不同的處理接口。
建立一個接收者時,你需要處理兩個protocol:CBCentralManagerDelegate和CBPeripheralDelegate。

建立Central Manager

初始化時,請呼叫方法
public init(delegate: CBCentralManagerDelegate?, queue: DispatchQueue?, options: [String : Any]? = nil)
實作為
let central = CBCentralManager(delegate: self as CBCentralManagerDelegate, queue: nil, options: nil)
其中queue和options可以給他nil就好。

藍芽狀態偵測

當你初始化CBCentralManager後,他會判斷你目前藍芽的狀態,因此他會呼叫
public func centralManagerDidUpdateState(_ central: CBCentralManager)
告訴你藍牙目前的狀態,是啟動了,還是沒開、沒支援之類的。這是是要透過central.state去判斷藍牙的狀態,並提供提示給使用者。

開始掃描

當你準備就緒後,你就可以開始尋找peripheral設備了,你可以指定你要找的設備會提供哪些功能,比方說你想要找有提供心率數據的設備,依照藍牙官方定義的識別號碼,說你要找的是
centralManager!.scanForPeripherals(withServices: [CBUUID(string: "0x180D")], options: nil)
一但找到後,callback方法centralManager(_:didDiscover:advertisementData:rssi:)就會被呼叫。在這裡面你要做的事情是:
  1. 判斷要不要連線,也就是判斷這是不是你要的device
  2. 如果要連線,你必須有一個ref到該peripheral
  3. 確定連線的話,你可以選擇停止掃描
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
    // You must hold the peripheral if you want to connect to.
    self.refDevice = peripheral
    self.centralManager.connect(self.refDevice, options: nil)
    self.centralManager.stopScan()
}

與裝置連線之後

這邊要先介紹藍牙一下有關藍芽的資料結構。比較簡單的概念是,一個藍牙設備,會提供一個或多個服務(Service),每個服務會提供一個或多個數據(Characteristics)給使用者。
比方說,一個藍牙設備可能會有監控心率的能力,也可以偵測目前GPS位置,那就是有兩個Service,監控心率的服務會提供量測到的心率數據,這就是一個Characteristics。
所以,當你與裝置連線後,你要先檢查該設備有沒有你要的service (因為你在最初掃描時可以不用指定service,所以你連到的設備可能沒有該服務),如果有,再檢查設備有沒有你要的Characteristics。
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
    peripheral.discoverCharacteristics([ViewController.CharUUID], for: (peripheral.services?.first)!)
}
    
// If you find specific service, you can now subscribe the characteristic.
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    peripheral.setNotifyValue(true, for: (service.characteristics?.first)!)
}
我這邊的setNotifyValue,是做訂閱,因為像是心率量測會是不斷的傳送資料,那我就要一直去收,這種時候要用的是訂閱。
當你訂閱後,你的delegate方法會被呼叫
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        
    if let err = error {
        print("\(err.localizedDescription)")
        return
    }
        
    if let data = characteristic.value,
        let info = String(data: data, encoding: String.Encoding.utf8) {
            // do something.....
    }
}
當資料改變時,另一個delegate方法會持續地被呼叫
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
    if let data = characteristic.value,
        let info = String(data: data, encoding: String.Encoding.utf8) {
        // do something.....
    }
}
有關read request,可以參考官方文件
到這邊,Central的介紹就到一個段落了。

Peripheral

Peripheral主要是發送資料,身為Peripheral(這邊會以手機端作為Peripheral的角度來說明),要定義它所提供的service和characteristic,這樣才知道要廣播什麼樣的訊息出去給central去偵測自身的存在。
等到建立連線後,可以對要傳送的資料(characteristic)進行修改,讓central去讀。

建立Peripheral Manager

Peripheral負責發資料,初始化時,請呼叫
public init(delegate: CBPeripheralManagerDelegate?, queue: DispatchQueue?, options: [String : Any]? = nil)
其中queue和options可以給他nil就好。

狀態偵測

當你建立好Manager後,他會判斷你目前藍芽的狀態,因此他會呼叫
public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager)
在這裡面你可以透過peripheral.state去判斷藍牙的狀態,並提供提示給使用者。

建立Service & Characteristic

基本順序是,建立好Service,然後建立Characteristic再給它所屬的service,然後把service給peripheral manager,準備去播放出去。
// Create characteristic
let characteristic = CBMutableCharacteristic(type: ViewController.CharUUID, properties: CBCharacteristicProperties.notify, value: nil, permissions: CBAttributePermissions.readable)

// Create service
let service = CBMutableService(type: ViewController.ServiceUUID, primary: true)

// Assign characteristic to service
service.characteristics = [characteristic!]

// Add service to manager
peripheralManager!.add(service)
當你add時,就會呼叫delegate方法:
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?)
可以從error看有沒有成功。

Advertising

一切準備就緒後,就可以廣播了:
let advertising = [CBAdvertisementDataServiceUUIDsKey : [ViewController.ServiceUUID]
peripheralManager!.startAdvertising(advertising)
這邊是說你要決定廣播出去的服務是什麼,告訴central說你這邊有提供什麼樣的服務,就好像在路邊攤在喊一樣。

被訂閱

如同前面Central介紹時,Central選擇要訂閱,所以這邊Peripheral的delegate方法會被呼叫:
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic)
這就表示你被訂閱了。

更新傳送出去的資料

被訂閱之後,比方你要告訴central說你量測到的數值是什麼:
let data = measureValue.data(using: String.Encoding.utf8)
let didSendValue = self.peripheralManager!.updateValue(data!, for: self.characteristic!, onSubscribedCentrals: nil)
                
if didSendValue == false {
    print("fail to send value")
}
這邊是把你量到的數值轉成了String,再封成Data,然後告訴peripheral manager去更新特定characteristic的數值。在Central的delegate方法peripheral(didUpdateValueFor characteristic:error:)會被呼叫。
這邊可以透過didSendValue確認有沒有送成功。

留言