iOS BLE 開發小記[2] 如何實現一個 Local Central

muhlenXi發表於2019-04-22

歡迎訪問我的部落格 muhlenXi,該文章出自我的部落格,歡迎轉載,轉載請註明來源: muhlenxi.com/2017/04/29/…

導語:

在這一節,你將會學到,如何通過 CoreBluetooth 框架來實現 Local Central 方面的功能和代理方法。

在 BLE 通訊中,實現了 Central 規範的裝置,能夠呼叫許多常用的方法,比如搜尋和連線可用的 Peripheral,然後與 Peripheral 提供的資料進行互動。但是實現了 Peripheral 規範的裝置同樣能夠呼叫許多常見的方法,但是也有一些不同。比如,釋出和廣播 Service,對 Central 的讀寫請求進行響應以及 響應Central 的訂閱請求。

在這個章節中,你將會學習在 Central 端如何使用 Core Bluetooth 框架來執行通用的 BLE 方法。基於程式碼的示例將會引導和協助你在你的本地裝置上實現 Central 的角色。尤其,你將會學到:

  • 如何建立一個 Central Manager 物件
  • 如何搜尋和連線一個正在廣播資訊的 Peripheral
  • 連線成功後如何與 Peripheral 的資料進行互動
  • 如何傳送讀取或寫入請求給 Peripheral Service 的 Characteristic
  • 如何訂閱一個當資料更新時就會發出通知的 Characteristic

在下個章節,你將會學習如何在你的本地裝置上實現 Peripheral 的角色。

你也許會發現本章節中的程式碼有點簡單和抽象,在你的真實 App 中,你需要做些恰當的改變。更高階的開發技能可以參考後續的章節。

Central 實現詳情

在本文中,你的 ViewController 需要遵循 CBCentralManagerDelegateCBPeripheralDelegate 代理協議。

建立 Central Manager

因為在 CoreBluetooth 中是通過物件導向的思想用一個 CBCentralManager(中心管理) 物件來表示一個 Local Central 裝置,所以在呼叫該物件的方法之前需要先 allocate(分配)和 initialize(初始化)一個 Central Manager 例項。可以通過 initWithDelegate:queue:options: 方法來初始化一個 Central Manager 物件。

myCentralManager =
        [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
複製程式碼

在示例程式碼中,設定 Central Manager 的 Delegate 為 self,是為了接收 Central 的事件響應。 引數 dispatch queue(排程佇列)設定為 nil,表示 Central Manager 是在 main queue(主佇列)中分發響應事件。

當你建立一個 Central Manager 物件時,Central Manager 會呼叫 centralManagerDidUpdateState: 方法來代理回撥。因此你必須實現這個代理方法來確保 Central 裝置能夠使用 BLE 技術,代理方法的詳情見 CBCentralManagerDelegate Protocol Reference

搜尋正在廣播資料的 Peripheral 裝置

初始化 Central Manger 物件後,第一個任務就是搜尋周圍的 Peripheral,上一篇曾提到過, Peripheral 通過廣播資料的方式來顯示它們的存在,你可以通過 scanForPeripheralsWithServices:options: 方法來搜尋周圍正在廣播資料的 Peripheral 裝置。

[myCentralManager scanForPeripheralsWithServices:nil options:nil];
複製程式碼

關於引數說明:如果第一個引數置為 nil 時,Central Manager 會返回所有正在廣播資料的 Peripheral 裝置。在實際 APP 開發中,通過指定一個包含 CBUUID 物件的陣列來獲取指定的 Peripheral,其中用一個 UUID(通用唯一識別碼)來表示 Peripheral 正在廣播的一個服務。關於 CBUUID 物件的詳情見 Services and Characteristics Are Identified by UUIDs.

每當 Central Manager 搜尋到一個 Peripheral 裝置時,就會通過 代理方法 centralManager:didDiscoverPeripheral:advertisementData:RSSI: 進行回撥,新發現的 Peripheral 會以 CBPeripheral 物件的方式返回。如果你後面需要連線這個 Peripheral,需要用一個 CBPeripheral 型別的 Strong Reference(強引用)來指向這個物件,這樣系統暫時就不會釋放這個物件了。

- (void)centralManager:(CBCentralManager *)central
 didDiscoverPeripheral:(CBPeripheral *)peripheral
     advertisementData:(NSDictionary *)advertisementData
                  RSSI:(NSNumber *)RSSI {
 
    NSLog(@"Discovered %@", peripheral.name);
    self.discoveredPeripheral = peripheral;
    // ...
}
複製程式碼

當你需要連線多個 Peripheral 裝置時,需要使用一個 NSArray 來儲存這些搜尋到的 Peripheral,不管怎樣,為了減少電量損耗,增加續航時間,只要搜尋到你需要連線的 Peripheral 時,就可以停止搜尋了。通過下面的方法可以停止搜尋了。

[myCentralManager stopScan];
複製程式碼

連線剛發現的 Peripheral 裝置

可以呼叫 connectPeripheral:options: 方法來連線你想要連線的 Peripheral 裝置。

[myCentralManager connectPeripheral:peripheral options:nil];
複製程式碼

如果連線成功,Central 會通過 centralManager:didConnectPeripheral: 方法進行代理回撥。在你與 Peripheral 進行互動時,你需要設定 Peripheral 的 Delegate 為 self 來確保能收到 Peripheral 的代理回撥。

- (void)centralManager:(CBCentralManager *)central
  didConnectPeripheral:(CBPeripheral *)peripheral {
 
    NSLog(@"Peripheral connected");
    peripheral.delegate = self;
    // ...
}
複製程式碼

搜尋剛連線 Peripheral 的 Service

當與 Peripheral 成功建立連線後,你就可以獲取 Peripheral 的 Service 資料了,第一步就是搜尋 Peripheral 提供的可用 Service。因為 Peripheral 對廣播的資料包大小有限制,所以你可能會搜尋到除了廣播的 Service 之外的 其他 Service,你可以通過 discoverServices: 方法搜尋 Peripheral 提供的所有的 Service。

[peripheral discoverServices:nil];
複製程式碼

提示:在實際開發中,傳入的引數一般不為 nil,傳入 nil 會返回全部的可用 Service,為了節省電量以及一些不必要的時間浪費,通過指定一個 Service Array(包含 UUID 物件)為引數,來獲取你想要了解的 Service 的資訊,詳情見 Explore a Peripheral’s Data Wisely.

當搜尋到指定的 Service 時,Peripheral 物件會通過 peripheral:didDiscoverServices: 方法進行代理回撥。CoreBluetooth 會生成一個陣列用來儲存 CBService 物件,你指定的 Service 就被包含在這個陣列中。你可以通過實現下面這個代理方法來獲取 Service 陣列。

- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error {
 
    for (CBService *service in peripheral.services) {
        NSLog(@"Discovered service %@", service);
        // ...
    }
    // ...
}
複製程式碼

搜尋 Service 的 Characteristic

當你找到指定的 Service 之後,你下一步要做的就是搜尋這個 Service 中提供的所有的 Characteristic,通過呼叫 discoverCharacteristics:forService: 方法來搜尋指定的 Service 的 Characteristic。

[peripheral discoverCharacteristics:nil forService:interestingService];
複製程式碼

提示:在實際開發中,第一個引數一般不傳入 nil,因為你需要的 Characteristic 也許是所有 Characteristic 的一部分,為了節省電量以及一些不必要的時間浪費,通常指定一個 Characteristic Array(包含 UUID 物件)為引數,來獲取你想要了解的 Characteristic 的資訊。

當搜尋到指定的 Characteristic 後, Peripheral 會通過 peripheral:didDiscoverCharacteristicsForService:error: 方法進行代理回撥,CoreBluetooth 會建立一個包含 CBCharacteristic 物件的陣列用來儲存指定的 Characteristic,如下是遍歷陣列中的 Characteristic。

- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
             error:(NSError *)error {
 
    for (CBCharacteristic *characteristic in service.characteristics) {
        NSLog(@"Discovered characteristic %@", characteristic);
        // ...
    }
    // ...
}
複製程式碼

檢索 Characteristic 的值

讀取某個 Characteristic 的值

當你找到 Service 指定的 Characteristic,可以通過 readValueForCharacteristic: 方法來讀取這個 Characteristic 的值。

NSLog(@"Reading value for characteristic %@", interestingCharacteristic);
[peripheral readValueForCharacteristic:interestingCharacteristic];
複製程式碼

當你嘗試去讀取一個 Characteristic 的值時, Peripheral 會通過 peripheral:didUpdateValueForCharacteristic:error: 代理回撥來返回結果,你可以通過 Characteristic 的 value 屬性來得到這個值。

- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error {
 
    NSData *data = characteristic.value;
    // parse the data as needed
    // ...
}
複製程式碼

提示:並不是所有的 Characteristic 的值都是可讀的,決定一個 Characteristic 的值是否可讀是通過檢查 Characteristic 的 Properties 屬性是否包含 CBCharacteristicPropertyRead 常量來判斷的。當你嘗試去讀取一個值不可讀的 Characteristic 時,Peripheral 會通過 peripheral:didUpdateValueForCharacteristic:error: 給你返回一個合適的錯誤。

訂閱一個 Characteristic 的值

通過 readValueForCharacteristic: 方法讀取一個 Characteristic 的靜態值是有效的,但是,對於動態的值,就不是一個有效的方法,因為 Characteristic 的值在實時改變,比如你的心率資料。只有通過訂閱才能獲取實時的改變值,當你訂閱一個 Characteristic 的值時,每當這個值發生改變時,你就會收到一個通知。

通過 setNotifyValue:forCharacteristic: 方法可以訂閱指定 Characteristic 的值,該方法的第一個引數需要指定為 YES

[peripheral setNotifyValue:YES forCharacteristic:interestingCharacteristic];
複製程式碼

當你訂閱或取消訂閱一個 Characteristic 的值時,Peripheral 會通過呼叫 peripheral:didUpdateNotificationStateForCharacteristic:error: 方法進行代理回撥,如果訂閱失敗了,你可以通過這個方法獲取到發生錯誤的原因。

- (void)peripheral:(CBPeripheral *)peripheral
didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error {
 
    if (error) {
        NSLog(@"Error changing notification state: %@",
           [error localizedDescription]);
    }
    // ...
}
複製程式碼

提示:並不是所有的 Characteristic 都提供訂閱功能,決定一個 Characteristic 是否能訂閱是通過檢查 Characteristic 的 properties 屬性是否包含 CBCharacteristicPropertyNotify 或者 CBCharacteristicPropertyIndicate 常量來判斷的。

寫資料到 Characteristic 中

有時需要寫入一個資料到 Characteristic 中,比如,如果你的 APP 與數字恆溫器進行互動。你或許想要給數字恆溫器提供一個值,使得房間的室溫能保持在這個溫度左右。如果一個 Characteristic 的值是可寫的,你可以通過呼叫 writeValue:forCharacteristic:type: 方法將一個 data 型別(NSData 物件)的值寫入到 Characteristic 中。

NSLog(@"Writing value for characteristic %@", interestingCharacteristic);
[peripheral writeValue:dataToWrite forCharacteristic:interestingCharacteristic
        type:CBCharacteristicWriteWithResponse];
複製程式碼

當你寫一個資料到 Characteristic 中時,你可以指定寫入型別,上面程式碼中的寫入型別是 CBCharacteristicWriteWithResponse ,此型別時,Peripheral 會通過 peripheral:didWriteValueForCharacteristic:error: 方法來代理回撥告知你是否寫入資料成功,可以實現下面這個代理方法進行錯誤處理。

- (void)peripheral:(CBPeripheral *)peripheral
didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error {
 
    if (error) {
        NSLog(@"Error writing characteristic value: %@",
            [error localizedDescription]);
    }
    // ...
}
複製程式碼

如果你指定寫入型別為 CBCharacteristicWriteWithoutResponse 時,不能保證寫入操作是否有效的執行了。這時 Peripheral 不會呼叫任何代理方法,如果想了解 CoreBluetooth 提供的寫入型別詳情,可以查閱 CBCharacteristicWriteType.

提示:有的 Characteristic 的值可能僅僅是可寫的,或者不是可寫的。決定 Characteristic 的值是否可寫,需要通過檢視 Characteristic 的 properties 屬性是否包含 CBCharacteristicPropertyWriteWithoutResponse 或者 CBCharacteristicPropertyWrite 常量來判斷的。

參考文獻

1、Performing Common Central Role Tasks

結束語

歡迎在本文下面留言一起交流心得...

相關文章