iOS--執行通用周邊角色任務(Performing Common Peripheral Role Tasks)

weixin_34138377發表於2016-07-10

上一章,你已經學習了怎樣執行大多數的中心這邊的BLE任務,在這一章中,你將學習怎樣使用CoreBluetooth框架執行大多數的周邊這邊的BLE任務。基礎程式碼示例可以幫助你在你的裝置上實現周邊角色,你將學習:

  • 開始一個周邊管理者物件(Start up a peripheral manager object)
  • 在本地裝置上設定服務和特徵(Set up services and characteristics on your local peripheral)
  • 釋出你的服務和特徵到裝置本地資料庫(Publish your services and characteristics to your device’s local database)
  • 廣告你的服務(Advertise your services)
  • 對中心的讀寫應答(Respond to read and write requests from a connected central)
  • 傳送被中心訂閱的更新特質值(Send updated characteristic values to subscribed centrals)

你在本章所見的程式碼示例是簡單且抽象的,你需要作出適當的修改再混合近你的app之中,更多關於實現本地周邊角色的高階技術專題-包括祕訣、技巧和最佳實踐-包含在後面的章節中iOSAPP中CoreBluetooth的後代執行模式 (Core Bluetooth Background Processing for iOS Apps )and實現本地周邊的最佳實踐( Best Practices for Setting Up Your Local Device as a Peripheral.)

開始一個周邊管理者物件(Start up a peripheral manager object)

在本地裝置上實現周邊角色的第一步是分配並初始化一個周邊管理者物件例項餓(用CBPeripheralManager物件表示),可以通過呼叫CBPeripheralManager類中的 initWithDelegate:queue:options:方法開始你的周邊管理者,像這樣:

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

在這個例子中,“self”被設定為委託者用於接收任何周邊角色事件,當你指定分派佇列為“nil”,周邊管理者將在主佇列分派周邊角色事件。
當你建立一個周邊管理者,這個周邊管理者會為它的委託物件呼叫peripheralManagerDidUpdateState:方法。你必須實現這個委託方法來保證BLE在本地裝置上是支援並且可用的。更多關於實現委託方法的內容,請看 CBPeripheralManagerDelegate Protocol Reference.

設定你的服務和特徵(Setting Up Your Services and Characteristics)

一個本地周邊上的服務和特徵的資料庫是被分類成樹狀結構的,在你的本地裝置上你也必須要把他們整理出樹狀結構。你執行這項任務的第一步是理解服務和特徵怎麼被標示的。
服務和特徵通過UUIDs標示
藍芽指定128位的UUIDs標記周邊的服務和特徵,在corebluetooth中用CBUUID物件抽象。雖然並非所有的服務和特徵的UUIDs都被藍芽特別興趣小組 (SIG)預先定義,為了方便,他們定義並公開了一些16位的通用UUIDs。舉個例子,藍芽興趣小組為心率服務預先定義了180D的16位(2byte)UUID。這個UUID是它相等的一個128位UUID的簡稱:0000180D-0000-1000-8000-00805F9B34FB。這個定義在藍芽4.0協議定義的bluetooth基礎uuid的第三卷,F部分3.2.1節中有介紹。
CBUUID類提供工廠函式幫你解決開發時那麼長的UUID的問題。舉例,你可以使用UUIDWithString函式從一個預定義的16位UUID中建立一個CBUUID的物件來代替那個心率服務128位的UUID,像這樣:

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

當你通過16位UUID建立一個CBUUID時,CoreBluetooth還原了128位的那個藍芽基礎UUID。
建立你自己定義的服務和特徵的UUIDS
你可能有預定義藍芽UUIDs未定義的服務和特徵。如果是這樣,你需要為他們自定義128位UUID。
用uuidgen命令列程式可以很輕鬆得到128位的UUIDs。開始做,開啟一個命令列視窗。下一步,分別為你需要的每一個特徵和服務獲取128位值,用ASCII碼的格式化,用連字元分開的字串。像下面這個例子:

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

然後你可以用這個UUID和UUIDWithString函式建立一個CBUUID物件:

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

構建你的服務和特徵樹
在你擁有了特徵和服務的UUIDs之後(用CBUUID物件替代),你可以建立可變的服務和特徵並且按上面描述的樹狀結構規則組織它們。舉例:如果你有特徵的UUID,你可以建立一個可變的特徵,通過呼叫CBMutableCharacteristic類中的initWithType:properties:value:permissions:函式。像這樣:

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

當你建立一個個可變的特徵,你可以設定它的屬性,值,還有許可權。你設定的屬性和許可權決定了這個特徵的值是否可讀的和是否可寫的,並且決定了特徵的值是不是可以被連線的中心訂閱。在這個例子中,這隻特徵的值被設定為對連線中心可讀的
。更多關於可變特徵的屬性和全市支援範圍,看 CBMutableCharacteristic Class Reference.

注意:當你為特徵指定了一個值,那麼這個值將被貯藏起來,並且特徵的屬性和許可權會被設為可讀的。所以,當你需要特徵的值變成可寫的,或者你希望這個值在公共服務的特徵的生命週期裡是可變的,你必須要指定值為nil。通過下面步驟確保這個值在周邊管理者收到來自連線的中心的請求的時候可以動態被周邊管理者請求。

現在,你可以建立一個可變的特徵,還可以建立一個特徵關聯的可變的服務。假如要這麼做,你可以呼叫 CBMutableService 類的initWithType:primary:方法,像這裡展示的這樣:

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

在這個例子中,第二個引數被設定為YES,表面這個服務是主要的不是次要的。一個主要的服務描述裝置主要的功能並且可以包括(關聯)其它的服務。一個次要的服務只能在關聯它的服務的上下文裡發揮作用。舉例:心率計的主要服務顯示獲取心率感測器的資料,然而一個次要的服務可能顯示感測器的電量資料。
在你建立了服務之後,你可以通過設定characteristics這個服務的陣列把它和特徵值關聯。像這樣:

myService.characteristics = @[myCharacteristic];

釋出你的服務和特徵

您已經構建了服務和特性的樹後,執行你的本地裝置上外圍裝置角色的下一步是公佈它們到裝置的服務和特性的資料庫。這個任務是很容易使用藍芽CoreBluetooth框架來執行。呼叫在CBPeripheralManager類中的addService:的方法,如下:

[myPeripheralManager addService:myService];

當呼叫此方法來發布服務,周邊管理器為它的委託物件呼叫peripheralManager:didAddService:error:方法。如果出現錯誤,你的服務則不能公佈,實現這個委託方法來訪問錯誤的原因,如下例所示:

-(void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service
error:(NSError *)error{
if (error) {
NSLog(@"Error publishing service: %@", [error localizedDescription]);
}
...

注意:您釋出服務及任何與其關聯的特點到周邊的資料庫之後,這個服務將被快取,你不能再進行更改。

廣告你的服務

當你將你的服務和特徵釋出到裝置的服務特徵資料庫,你得準備好你開始廣告一些服務和特徵給其它可以收聽到的任何中心。下面的例子展示了,你可以通過呼叫CBPeripheralManager類中的 startAdvertising: 方法廣告你的一些服務,通過一個廣告資料的字典(NSDictionary的例項):

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

在這個例子中,字典裡唯一的一個鍵是CBAdvertisementDataServiceUUIDsKey,與之對應的值是一個象徵你想要廣告的UUIDs的CBUUID物件陣列(NSArray例項)。廣告資料內容中更多詳細的key的介紹在CBCentralManagerDelegate Protocol Reference.中Advertisement Data Retrieval Keys那一節。也就是說,只有兩個鍵是用於周邊管理物件的支援:CBAdvertisementDataLocalNameKey 和 CBAdvertisementDataServiceUUIDsKey.
當你開始為本地周邊廣告一些資料時,周邊管理者為他的委託物件呼叫peripheralManagerDidStartAdvertising:error: 方法,如果發生了錯誤導致服務不能被廣告,實現這個委託方法查詢錯誤的原因,像這樣:

-(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error {
if (error) {
NSLog(@"Error advertising: %@", [error localizedDescription]);
}
...
注意:資料的廣告是“盡力而為”的基礎上進行,因為空間是有限的,有可能同時有多個應用在投放廣告。欲瞭解更多資訊,請參閱在CBPeripheralManager Class Reference中startAdvertising方法的討論。

當你的應用程式是在後臺廣告的行為也受到了影響。這個話題在下一章中,核心的藍芽後臺處理的iOS應用進行討論( Core Bluetooth Background Processing for iOS Apps.)。
一旦你開始廣告資料,遠端中心就可以發現你並和你建立連線。

響應來自中心的讀寫請求

在你連線了一個或多個遠端中心之後,你可以開始接受來自遠端中心的讀或寫請求。當你這樣做時,一定要以適當的方式響應這些請求。下面的例子說明如何處理這樣的請求。
當連線的中心需要請求讀你的特徵值時,中心管理者會為他的委託物件呼叫peripheralManager:didReceiveReadRequest: 方法。這個委託方法封裝了一個 CBATTRequest 物件,包含關於請求的屬性的所有值。
例如,當您收到一個簡單讀取特性的值的請求,你從委託方法中收到的CBATTRequest物件的屬性可以被用來確保在裝置的資料庫中的特徵與遠端中央指定的在原始讀請求是否相匹配。你可以開始實現這個委託方法,如下所示:

-(void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
...

如果該特徵'的UUID匹配,則下一個步驟是確保該讀請求是不是要求從你的特徵的值的範圍外的索引位置讀取。如下面的例子顯示,你可以使用一個CBATTRequest物件的偏移屬性,以確保讀請求不試圖在適當的範圍之外閱讀:

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

假設請求的偏移得到驗證,現在設定請求的特徵屬性的值(預設其值為零)為您在本地外圍建立的特性的值;考慮到偏移的讀出的請求:

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

設定值後,回覆遠端中心,表面請求十分成功。呼叫 CBPeripheralManager 類中的respondToRequest:withResult:方法回覆那個請求(指定值的那個)回覆請求結果。像這樣:

[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];

在委託物件每一個peripheralManager:didReceiveReadRequest: 回撥方法裡呼叫respondToRequest:withResult。

注意:如果特徵的UUID不匹配,或者因為任何其他原因讀不能完成,你不會試圖完成請求。替代做法是,你講立即呼叫respondToRequest:withResult:方法。並且提供結果描述失敗。對於你可以指定可能的結果列表,請參閱Core Bluetooth Constants Reference的CBATTError常量列舉。

處理來自連線的中心的寫請求的方式是直截了當的。當一箇中心試圖通過傳送寫請求來改變你的一個多個特徵的值,周邊管理者會通過呼叫委託物件的 peripheralManager:didReceiveWriteRequests:方法來通知。委託物件交付一個包含一到多個CBATTRequest物件的規則的陣列,其中每個物件代表一個寫請求。在你確定這個寫請求是合格的,你就可以把它寫入特徵值當中,像這樣:

myCharacteristic.value = request.value;

雖然上面的例子並不能說明這個問題,但請你寫特性值時,要考慮到請求的偏移特性。
就像回覆讀請求一樣,呼叫 respondToRequest:withResult:方法在peripheralManager:didReceiveWriteRequests: 方法被呼叫之後。這就是說,respondToRequest:withResult:方法需要的第一個引數是一個CBATTRequest物件,即使你從 peripheralManager:didReceiveWriteRequests:方法中得到了一個陣列的多個物件,你可以通過陣列的第一個請求,像這樣:

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

注意:對待多個請求當作你一個請求,如果有個別要求不能得到滿足,你不應該滿足其中任何一個請求。相反,立即呼叫respondToRequest : withResult 方法,並提供一個結果,指出錯誤的原因:。

上報更新的值給訂閱特徵值的中心(Sending Updated Characteristic Values to Subscribed Centrals)

通常,連線的中心會訂閱一到多個特徵的值,像前面章節說的,當他們這樣做,你有責任向他們傳送通知當他們訂閱的特性的值變化時。下面的例子說明怎麼做。
當連線的中心訂閱了你的特徵值時,周邊管理者會為委託物件呼叫peripheralManager:central:didSubscribeToCharacteristic: 方法。

-(void)peripheralManager:(CBPeripheralManager *)peripheral
central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"Central subscribed to characteristic %@", characteristic);
...

使用上述委託方法作為一個線索開始向中心傳送更新的值。下一步,通過呼叫 CBPeripheralManager類的updateValue:forCharacteristic:onSubscribedCentrals:方法得到更新的特徵值併傳送到中心

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

通過呼叫這個方法向訂閱中心傳送特徵值時,你可以在最後一個引數指定特定的中心。像上面的例子,如果引數傳入nil,所有訂閱並連線的中心都會得到更新(連線但是沒有訂閱的被忽略)。
updateValue:forCharacteristic:onSubscribedCentrals: 方法返回一個bool值表示傳送到中心的訂閱的結果。如果底層佇列需要傳送的值滿了,方法會返回no,當傳輸空間變得有用時中心管理者為委託物件呼叫peripheralManagerIsReadyToUpdateSubscribers:。你可以通過實現這個委託方法再次傳送想要的值,如果這樣做的話,你可以再次使用之前熟悉的updateValue:forCharacteristic:onSubscribedCentrals: 方法。

注意:使用通知傳送單一值的資料包到訂閱的中心。也就是說,當你更新了訂閱中心,你應該傳送全部的更新值在一個通知裡,通過呼叫一次
updateValue:forCharacteristic:onSubscribedCentrals: 方法。依賴於特徵值的規模,並不是所有資料都被通知傳送。如果發生了,這種情形應該被中心端通過呼叫CBPeripheral類的 readValueForCharacteristic:方法處理,這樣可以獲取全部的值。

相關文章