CoreBluetooth第二節:Performing Common Peripheral Role Task(執行常見的外設端任務)

本文官方文檔鏈接


在前一章節中你掌握了執行常見的藍牙低功耗核心端的任務.在本章中,你要學習執行常見的藍牙低功耗外設端的任務.基於代碼的例子會幫助你在本地設備中扮演外設端.特別的,你將掌握如何:

  • 啓動一個peripheral manager對象
    • 在本地外設中建立services 和characteristics
    • 將services和characteristics發送到設備的本地數據庫
    • 廣播services
    • 響應與自身建立連接的central(核心端)的讀寫請求
    • 向訂閱自己的central(核心端)更新特徵值

本章中的樣例簡單並抽象,你可能需要在實際開發中做出相應更改.更多外設端的進階主題-包括小貼士/技巧/最佳實踐在隨後的章節:Core Bluetooth Background Processing for iOS AppsBest Practices for Setting Up Your Local Device as a Peripheral.


啓動一個Peripheral Manager

第一步就是分配內存並初始化一個peripheral manager實例(CBPeripheralManager對象)調用CBPeripheralManager類的 initWithDelegate:queue:options 方法:

 myPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];

在本例中,self設置成代理對象來接收外設端的事件.當你指定隊列爲nil時,peripheral manager使用主隊列來分發事件.
當創建一個peripheral manager, peripheral 調用peripheralManagerDidUpdateState代理方法.爲了確保本地外設支持藍牙低功耗並可用,你必須實現該代理.關於實現該代理方法的更多信息,請點擊CBPeripheralManagerDelegate Protocol Reference


建立Services和Characteristics

1-7
如上圖所示,一個本地外設的數據庫(存儲services和characteristics)是一個樹狀結構.在本地的外設上建立services和characteristics時必須將他們構造成這個樹狀結構.第一步就是理解services和characteristics是如何標識的.

Services和Characteristics通過UUIDs區分

外設的services和characteristics通過128位特定的藍牙UUID區分,在Core BlueTooth框架中用作CBUUID對象.儘管SIG並未預定義用來區分service和characteristic的UUID,SIG定義併發布了一些常見的便於使用的16位的UUID.舉個栗子,SIG將心率設備的標識符定義爲16位的180D.這個UUID是由和它等價的128位UUID縮短而成,0000180D-0000-1000-8000-00805F9B34FB,基於基本的UUID定義爲藍牙4.0規範,頻道3,part F, Section3.2.1
CBUUID類提供了可讓你在開發中快速處理長UUID的工廠方法.例如,你可以簡單的使用UUIDWithString來從系統預定義的16位UUID創建CBUUID對象,從而取代128位心率service的UUID:

CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];

爲自定義的Services和Characteristics創建自己的UUIDs

你可能會有一些沒有預定義UUID的services和characteristic.如果這麼做,你需要爲它們生成自己的128位UUID.
使用命令行工具uuidgen可以快速的生成128位UUID.首先,開啓一個終端窗口,下一步爲每一個需要標識的service和characteristic生成爲一個128位使用-連接的ASCII值:

$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7

然後你可以使用這個UUID通過UUIDWithString創建一個CBUUID對象:

CBUUID *myCustomServiceUUID = [CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];

創建Services和characteristics的樹形結構

在你擁有了services和characteristic的UUIDs之後,你可以創建可變的service和characteristics並且將它們構成樹形.舉例,如果你有一個characteristic的UUID,你可以通過initWithType:properties:value:permissions創建可變的characteristic:

 myCharacteristic =
        [[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID
         properties:CBCharacteristicPropertyRead
         value:myValue permissions:CBAttributePermissionsReadable];

當創建了一個可變的characteristic後設置他的屬性,授權.除了其他事項外,屬性授權決定了characteristic value(特徵值)是可讀的還是可寫的,以及與本設備建立連接的central(核心)設備是否可以訂閱該characteristic.在本例中,特徵值被設置成可以被連接的核心設備讀取.更多關於可變characteristic相關的可用屬性和授權請戳CNMutableCharacteristic Class Reference
Note:如果你爲characteristic指定了一個值,這個值被緩存下來並且它的屬性和授權被設置爲了可讀.如果你需要將characteristic設置爲可寫的,或者在發佈service的生命週期中改變characteristic的值,你必須指定值爲nil.如此確保在外設收到核心設備的讀寫請求時,值是動態變化的
現在你創建了一個可變的characteristic,你可以創建可變的service來聯結characteristic,調用CBMutableService 類的 initWithType:primary:

myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];

在本例中,第二個參數設置成了Yes,意味着service是主要的.一個主service描述一個設備的主要功能並可以被其他service引用.次service描述了一個只在引用它的其他service的上下文中相關(翻譯的好像不太對.A secondary service describes a service that is relevant only in the context of another service that has referenced it).舉個栗子,心率檢測器的主service可能從心率傳感器的傳輸心率數據,而次service可能傳輸傳感器的電量數據.
在創建了一個service後,你可以設置service的characteristic數組來聯結它們:

myService.characteristics = @[myCharacteristic];

發佈你的Service和Characteristic

在你創建好樹形的services和characteristics後,下一步是將他們發佈到本地設備的services和characteristics數據庫中.使用CB可以簡單地實現,你可以調用CBPeripheralManager 類的addService:

[myPeripheralManager addService:myService];

當你調用這個方法發佈services時,peripheral manager調用peripheralManager:didAddService:error: 代理函數.如果錯誤發生了並且services不能被髮出,實現該代理來捕獲錯誤信息:

- (void)peripheralManager:(CBPeripheralManager *)peripheral
            didAddService:(CBService *)service
                    error:(NSError *)error {

    if (error) {
        NSLog(@"Error publishing service: %@", [error localizedDescription]);
    }
    ...

Note:在你向數據庫發佈一個service以及相關的characteristic後,這個service被緩存下來並且你不能再改變它


廣播Service

當你將services和characteristics發佈到設備的service和characteristic數據庫中後,你已經準備將他們廣播給一些正在監聽的central設備.如下例所示,你可以調用CBPeripheralManager 類中的 startAdvertising:,傳入一個NSDictionary類型的廣播數據.

[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : @[myFirstService.UUID, mySecondService.UUID] }];

在本例中,NSDictionary的唯一鍵CBAdvertisementDataServiceUUIDsKey,對應需要一個存放CBUUID對象的數組(代表着你想廣播的service的UUIDs).其他你可能用到的NSDictionary中的鍵在這裏.也就是說,只有兩個鍵是支持peripheral manager對象的:CBAdvertisementDataLocalNameKeyCBAdvertisementDataServiceUUIDsKey.
當你開始在本地外設中廣播數據時,peripheral manager調用peripheralManagerDidStartAdvertising:error:代理方法.如果發生錯誤可以在代理中捕獲錯誤信息:

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error {

    if (error) {
        NSLog(@"Error advertising: %@", [error localizedDescription]);
    }
    ...

Note:數據廣播是”努力”的,因爲容量有限並且同時會有多個app廣播,更多相關信息請查看startAdvertiseing:,廣播的行爲也會因程序在後臺受到影響,在下一章中將討論該問題,Core Bluetooth Background Processing for iOS Apps
一旦你開始廣播數據,遠程的核心設備可以發現並且與你建立連接.


響應核心設備發來的讀寫請求

在你連接了一個或多個遠程核心設備後,你可能開始接收他們的讀寫請求.當你這麼做時,確保用適當的方式來響應這些請求.下例描述瞭如何處理這些請求.
當一個連接的核心設備請求讀取一個你的characteristic, Peripheral manager調用peripheralManager:didReceiveReadRequest:代理方法.這個代理方法會以CBATTRequest對象向你傳遞核心端傳來的請求(擁有許多你可以用來實現請求的屬性)
舉例,當你接收到一個簡單的要讀取characteristic的請求,從代理中獲取的CBATTRequest 對象可以用來確定核心端指定要讀的characteristic和你本地設備數據庫中的characteristic相匹配.你可以實現這個代理:

- (void)peripheralManager:(CBPeripheralManager *)peripheral
    didReceiveReadRequest:(CBATTRequest *)request {

    if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
        ...

如果characteristic的UUID匹配成功,下一步要確保這個讀取請求要讀取的index位置不在你的characteristic值的外界.如下例所示,你可一個使用CBATTRequestoffset屬性來保證讀取請求不是在讀取屬性外界的數據.

if (request.offset > myCharacteristic.value.length) {
        [myPeripheralManager respondToRequest:request
            withResult:CBATTErrorInvalidOffset];
        return;
    }

假如請求的偏移量成功驗證,用本地外設端創建的characteristic值覆蓋請求中的characteristic屬性值,並且要顧及到讀取請求的偏移量:

request.value = [myCharacteristic.value
        subdataWithRange:NSMakeRange(request.offset,
        myCharacteristic.value.length - request.offset)];

在設置完值之後,需要向遠程核心設備聲明請求已成功處理.需調用CBPeripheralManagerrespondToRequest:withResult:方法,將你更新的request和請求處理的結果一併返回:

[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];

在每一次peripheralManager:didReceiveReadRequest:被調用後你都需要調用一次respondToRequest:withResult:

Note:如果characteristic的UUID不匹配,或因其他原因不能完成讀取請求,你無須嘗試完成請求.相反的你應該立刻調用respondToRequest:withResult:並且提供一個表明失敗原因的result.對於你可能需要指定的結果,查看CBATTError Constants
處理核心設備發送的寫入請求也很直接.當連接的核心設備向你發送一個或多個characteristic讀寫請求時,peripheral manager調用peripheralManager:didReceiveWriteRequests:代理.此時代理方法會以一個包含多個CBRequest 對象的數組向你傳輸請求,其中每一個代表一個寫入請求.在你確認完成了一個寫入請求後,你可以寫characteristic的值:

myCharacteristic.value = request.value;

儘管上述例子沒有演示,你還要確保在寫入characteristic時考慮請求的偏移量屬性.
正如迴應讀取請求一樣,在每一次peripheralManager:didReceiveWriteRequest:被調用後你都需要調用一次respondToRequest:withResult:,也就是說第一個參數需要一個CBATTRequest對象,即使你從peripheralManager:didReceiveWriteRequests:收到了多於一個Request的數組.你應該傳入數組的第一個請求:

 [myPeripheralManager respondToRequest:[requests objectAtIndex:0]
        withResult:CBATTErrorSuccess];

將多個請求作爲單個請求來處理,如果任一請求不能完成,你不應該完成其中的任何一個.相反,立刻調用respondToRequest:withResult:報錯


向訂閱的核心設備發送更新的characteristic

通常情況下,連接的核心設備會訂閱你的一個或多個characteristic值,如上一節的訂閱characteristic值.當訂閱characteristic值發生改變,你有責任發出通知.下例將指導你如何去做.
當一個連接的核心設備訂閱了你的若干characteristic.peripheral manager 調用peripheralManager:central:didSubscribeToCharacteristic:

- (void)peripheralManager:(CBPeripheralManager *)peripheral
                  central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {

    NSLog(@"Central subscribed to characteristic %@", characteristic);
    ...

使用上面的代理方法來向核心設備發送更新值.
下一步,獲得更新的characteristic值並且通過CBPeripheralManagerupdateValue:forCharacteristic:onSubscribedCentrals: 方法發送.

 NSData *updatedValue = // fetch the characteristic's new value
    BOOL didSendValue = [myPeripheralManager updateValue:updatedValue
        forCharacteristic:characteristic onSubscribedCentrals:nil];

當你調用該方法時,可以通過最後一個參數來指定你想要更新的訂閱characteristic值核心設備.上例中,如果你指定爲nil,所有訂閱的連接核心設備都將收到更新.
updateValue:forCharacteristic:onSubscribedCentrals:返回一個布爾值(意味向訂閱的核心設備發送更新成功).如果用來傳輸更新數據的底層隊列滿了,返回NO.Perpheral manager 會在傳輸隊列有空間時調用peripheralManagerIsReadyToUpdateSubscribers:,可以聲明本方法來重發值,依舊使用updateValue:forCharacteristic:onSubscribedCentrals:

Note:使用通知來向訂閱的核心設備發送的一個數據包,也就是說,當你更新一個訂閱的核心設備,你應該在一個通知中發送全部更新數據,通過調用一次updateValue:forCharacteristic:onSubscribedCentrals:.
不是所有的數據都可以通過通知(notification)傳輸,這取決於characteristic值的大小.如果數據較大,應該在覈心端調用CBPeripheralreadValueForCharacteristic:方法接收全部數據

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章