歡迎訪問我的部落格 muhlenXi,該文章出自我的部落格, 歡迎轉載,轉載請註明來源: muhlenxi.com/2017/05/01/…。
導語:
在這一節,你將會學到,如何通過 CoreBluetooth 框架來實現 Local Peripheral 方面的功能和代理方法。
在 iOS BLE 開發小記[2]中,你已經學到了如何在 Central 方面去呼叫 BLE 的常用方法。在這一節中,你將學習用 CoreBluetooth 框架來呼叫 Peripheral 方面 BLE 的常用方法。通過本文的示例程式碼,將會引導你開發一個將你的 Local 裝置實現為 Local Peripheral。你將會從中學到:
- 如何建立一個 Peripheral Manager 物件
- 如何為你的 Local Peripheral 設定 Services 和 Characteristics
- 如何釋出你的 Services 和 Characteristics 資料
- 如何廣播你的裝置
- 如何對連線的 Central 做讀寫請求響應
- 如何傳送更新後的值給訂閱的 Central
或許你發現示例程式碼過於簡單和抽象,你需要在你的 App 中做些恰當的練習來掌握這些內容。更高階的技巧和最佳實踐在後續的文章中將會講解。
Peripheral 實現詳情
建立一個 Peripheral Manager 物件
在 Local Device(當前裝置)實現 Peripheral 規範的第一步是分配(allocate)和初始化(initialize)一個周邊管理(Peripheral Manager),(用 CBPeripheralManager 物件表示),通過呼叫 CBPeripheralManager 的 initWithDelegate:queue:options:
方法來建立管理物件,如下所示
myPeripheralManager =
[[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];
複製程式碼
在示例程式碼中,設定 Delegate 為 self 是為了接收 Peripheral 的事件響應,將引數 dispatch queue 置為 nil。意味著 Peripheral Manager 將會在主佇列中分發事件。
當你建立一個 Peripheral Manger 物件時,Peripheral Manager 會通過 peripheralManagerDidUpdateState:
方法來代理回撥,你必須實現這個代理方法來確保當前裝置是否支援 BLE 技術,關於代理方法的詳情可以查閱 CBPeripheralManagerDelegate Protocol Reference.
設定你的 Services 和 Characteristics
在第一節中,我們瞭解到,一個 Local Peripheral 採用樹形結構來組織 Services 和 Characteristics 的資料。因此必須採用樹形結構方式來設定 Local Peripheral 的 Services 和 Characteristics。你第一步要做的是搞清和理解 Service 和 Characteristic 是如何標識的。
通過 UUID 標識 Services 和 Characteristics
Peripheral 的 Service 和 Characteristic 是通過 128 位的特定藍芽 UUID(通用唯一識別碼)來標識的,在 CoreBluetooth 中是用 CBUUID 物件來表示的。並不是所有的 UUID 都是通過 Bluetooth Special Interest Group (藍芽特別興趣小組)預定義的。為了方便起見,Bluetooth SIG 定義和釋出了許多通用的 16位 UUID。舉個例子,Bluetooth SIG 事先定義了一個16位的 UUID 用來標識一個心率 Service,該 UUID 是 128位 UUID 0000180D-0000-1000-8000-00805F9B34FB 進行縮減而來的,這是基於藍芽 4.0 規範中,第 3 卷 F 部分第 3.2.1 節定義的藍芽基礎 UUID。
CBUUID 提供了一個處理比較長的 UUID 的工廠方法,舉個例子,生成一個表示心率 Service 的 UUID,可以呼叫 UUIDWithString
方法來通過預定義的 16位 UUID來建立 CBUUID 物件。
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
複製程式碼
當你通過預定義的 16位 UUID 來建立 CBUUID 物件時,CoreBluetooth 會基於128位Bluetooth Base UUID 填充剩下的的 UUID 位。
為你定製的 Services 和 Characteristics 生成 UUID
你的 Service 和 Characteristic 的UUID也許可能沒有被 Bluetooth UUIDs 預定義,如果沒有被預定義,你需要手動生成你自己的 128位 UUID 來表示 Service 和 Characteristic。
通過命令列命令 uuidgen
可以生成 128位的 UUID,開啟你的 Terminal(終端),通過這種方式依次為你的 Services 和 Characteristics 生成一個 UUID (用連字元連線起來的字串)來標識。舉例如下:
$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7
複製程式碼
你可以用上面方法生成的 UUID 呼叫 UUIDWithString
方法來建立一個 CBUUID 物件。
CBUUID *myCustomServiceUUID =
[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];
複製程式碼
構建你的 服務特徵樹
當你為每個 Service 和 Characteristic 建立 CBUUID 物件後,你可以建立 mutable Service(可變服務) 和 mutable Characteristic(可變特徵),然後以樹形的方式組織它們。舉個例子,如果你現在有一個 Characteristic 的 UUID,你可以通過呼叫 CBMutableCharacteristic 類的 initWithType:properties:value:permissions:
方法生成一個 mutable Characteristic。
myCharacteristic =
[[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID
properties:CBCharacteristicPropertyRead
value:myValue permissions:CBAttributePermissionsReadable];
複製程式碼
當你建立 mutable Characteristic 的時候,你可以指定它的 properties(屬性)、value(值)和 permissions(許可權許可),你指定的 properties 和 permissions 決定這個 Characteristic 的值是否可以讀或者寫,或者連線的 Central 能否訂閱該 Characteristic 的值。下面的示例中,Characteristic 的值是被指定為可讀的。關於 mutable Characteristic 的 properties 和 permissions 詳情可以查閱 CBMutableCharacteristic Class Reference.
提示:如果你指定了 Characteristic 的值,那麼該值將被快取並且該 Characteristic 的 properties 和 permissions 將被設定為可讀的。因此,如果你需要 Characteristic 的值是可寫的,或者你希望在 Service 釋出後,Characteristic 的值在 lifetime(生命週期)中依然可以更改,你必須將該 Characteristic 的值指定為 nil。通過這種方式可以確保 Characteristic 的值,在 Peripheral Manager 收到來自連線的 Central 的讀或者寫請求的時候,能夠被動態處理。
既然你建立了一個 mutable Characteristic,你也能通過呼叫 CBMutableService
類的 initWithType:primary:
方法建立一個 mutable Service。如下所示:
myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];
複製程式碼
在示例程式碼中,第二個引數被指定為 YES,用來表明該 Service 是 Primary(主要的),而不是 secondary(次要的)。一個 Primary Service 用來描述這個裝置的主要功能,還可以用來引用其他的 Service。一個 Secondary Service 用來描述的是上下文中相關的或者被引用的 Service。舉個例子,從心率感測器中獲取心率的服務是 primary Service,而獲取感測器電量的服務就可以被視為 secondary Service 。
當你建立完 Service 後。你需要設定 Service 的 Characteristic 陣列屬性,如下:
myService.characteristics = @[myCharacteristic];
複製程式碼
傳送你的 Services 和 Characteristics
當你構建好服務特徵樹後,下一步就是按照 BLE 的規範釋出到裝置的服務特徵庫中,用 CoreBluetooth 可以很輕鬆的完成這一步,只需要呼叫 CBPeripheralManager
類 的 addService:
方法就可以了。 示例程式碼如下:
[myPeripheralManager addService:myService];
複製程式碼
當你呼叫該方法釋出服務時,Peripheral Manager 會呼叫 peripheralManager:didAddService:error:
方法進行代理回撥,實現這個代理方法可以獲取到產生的錯誤,示例程式碼如下:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service
error:(NSError *)error {
if (error) {
NSLog(@"Error publishing service: %@", [error localizedDescription]);
}
// ...
}
複製程式碼
提示:當你釋出 Service 和相關的 Characteristic 到 Peripheral 的資料庫中後,裝置已經將資料快取,你不能再改變它了。
廣播你的 Service
當你釋出你的 Service 和 Characteristic 到裝置的服務特徵庫時,你可以廣播一些服務給正在監聽的 Central,你可以通過呼叫 CBPeripheralManager
類的 startAdvertising:
方法來開始廣播,傳入的字典是要廣播的資料。
[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
@[myFirstService.UUID, mySecondService.UUID] }];
複製程式碼
在示例程式碼中,傳入的字典中唯一的 key 是 CBAdvertisementDataServiceUUIDsKey
,用一個包含 CBUUID 物件的陣列來表示你想要廣播的服務的 UUID。你在字典中可以指定的其他 key 在 Advertisement Data Retrieval Keys 中有詳細說明。也就是說,僅有 CBAdvertisementDataLocalNameKey
和 CBAdvertisementDataServiceUUIDsKey
這兩個 key 支援 Peripheral Manager 物件。
當你在本地裝置中廣播一些資料時,Peripheral Manager 會通過 peripheralManagerDidStartAdvertising:error:
方法來代理回撥。如果你的裝置不能廣播而發生錯誤時,實現這個代理方法可以獲取產生的錯誤:
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error {
if (error) {
NSLog(@"Error advertising: %@", [error localizedDescription]);
}
// ...
}
複製程式碼
提示:廣播資料方法會被盡力執行,因為空間是有限的和多個 APP 可能同時需要廣播資料,更多詳情可以查閱關於 startAdvertising: 方法的討論。
當你的 APP 在後臺執行時也會影響廣播的行為,這一內容將會在下一篇中進行討論。
響應 Central 的讀寫請求
當你連線一個或多個 Central 後,你可能會收到讀或者寫的請求,對這些請求作出響應需要採取恰當的方式,下面的示例程式碼將會描述如何處理這些請求。
當一個連線的 Central 傳送讀取某個 Characteristic 資料的請求時,Peripheral Manager 會呼叫 peripheralManager:didReceiveReadRequest:
方法進行代理回撥。代理方法以 CBATTRequest
物件的方式來傳遞請求,它包含一些請求的屬性。
比如,當你收到一個讀取 Characteristic 值的簡單請求時,可以通過代理方法回撥的 CBATTRequest
對像來判斷 Central 指定要讀取的 Characteristic 是否和裝置服務庫中的 Characteristic 是否相匹配。你可以開始實現這個代理方法,示例程式碼如下:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
// ...
}
}
複製程式碼
如果 Characteristic 的 UUID 能夠匹配,下一步就是確保讀取請求的位置沒有超出 Characteristic 的值的邊界。如下面程式碼所示,你可以通過使用 CBATTRequest
物件的 offset
屬性來確保讀取請求沒有嘗試讀取範圍之外的資料。
if (request.offset > myCharacteristic.value.length) {
[myPeripheralManager respondToRequest:request
withResult:CBATTErrorInvalidOffset];
return;
}
複製程式碼
假如讀取請求的 offset(偏移)已經確認,現在就可以設定請求的 Characteristic 的屬性(預設值為 nil)為你裝置中的 Characteristic 的值了,你應該重視讀取請求的偏移:
request.value = [myCharacteristic.value
subdataWithRange:NSMakeRange(request.offset,
myCharacteristic.value.length - request.offset)];
複製程式碼
設定完值後,通過呼叫 respondToRequest:withResult:
方法並傳入 request(更新值後的)和 請求的結果引數來對 Remote Central 的請求作出響應表示請求已經被成功處理。示例程式碼如下:
[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
// ...
複製程式碼
只要代理方法 peripheralManager:didReceiveReadRequest:
方法被回撥,就需要準確的呼叫 respondToRequest:withResult:
方法。
提示:如果 Characteristic 的 UUID 不匹配,或者因為某種原因不能完全讀取,不必去填充請求,直接呼叫 respondToRequest:withResult:
方法並提供一個表示失敗的結果即可。你可能指定的結果列表見 CBATTError Constants 常量列舉。
處理連線的 Central 寫入請求也比較易懂。當 Central 傳送一個寫入請求給一個或多個你的 Characteristic 時,Peripheral Manager 會通過 peripheralManager:didReceiveWriteRequests:
方法來代理回撥。這是,代理方法會傳遞一個包含一個或多個 CBATTRequest
物件的陣列給你,陣列中的每個物件都代表一個寫入請求。當你確定寫入請求能夠處理時,你可以設定 Characteristic 的值,示例程式碼如下:
myCharacteristic.value = request.value;
複製程式碼
雖然上述例子沒有證明這一點,但當你給 Characteristic 寫資料的時候,你應該確保請求的 offset 屬性的範圍有效。
就像你響應讀取請求一樣,只要代理方法 peripheralManager:didReceiveWriteRequest:
方法被回撥,就需要準確無誤的呼叫 respondToRequest:withResult:
方法。也就是說,respondToRequest:withResult:
方法期望有一個 CBATTRequest
物件,即使你可能通過 peripheralManager:didReceiveWriteRequests:
代理方法接收到一個包含 CBATTRequest
物件的陣列,你也應該傳入陣列中的第一個物件,示例程式碼如下:
[myPeripheralManager respondToRequest:[requests objectAtIndex:0]
withResult:CBATTErrorSuccess];
複製程式碼
提示:將多請求視為單一請求來對待,如果個別的請求不能被填充,你就不必填充其餘的請求了,直接呼叫 respondToRequest:withResult:
方法並提供一個表示失敗的結果即可。
傳送更新 Characteristic 的通知給訂閱的 Central
連線的 Central 經常會訂閱一個或多個 Characteristic 的值,當這些值發生變化時,你應該傳送通知給訂閱的 Central 。
當一個連線的 Central 訂閱一個或多個你的 Characteristic 值時,Peripheral Manager 會通過 peripheralManager:central:didSubscribeToCharacteristic:
方法來代理回撥。示例程式碼如下:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"Central subscribed to characteristic %@", characteristic);
// ...
}
複製程式碼
將上述的代理方法作為一個線索來開始給 Central 傳送更新後的值。
接著,獲取更新後的 Characteristic 的值,通過呼叫 CBPeripheralManager
類的 updateValue:forCharacteristic:onSubscribedCentrals:
方法來給 Central 傳送通知。示例程式碼如下:
NSData *updatedValue = // fetch the characteristic's new value
BOOL didSendValue = [myPeripheralManager updateValue:updatedValue
forCharacteristic:characteristic onSubscribedCentrals:nil];
複製程式碼
當你呼叫這個方法給訂閱的 Central 傳送通知時,你可以通過最後的那個引數來指定要傳送的 Central,示例程式碼中的引數為 nil,表明將會傳送通知給所有連線且訂閱的 Central,沒有訂閱的 Central 則會被忽略。
updateValue:forCharacteristic:onSubscribedCentrals:
方法會返回一個 Boolean 型別的值來表示通知是否成功的傳送給訂閱的 Central 了,如果 base queue (基礎佇列)滿載,該方法會返回 NO,當傳輸佇列存在更多空間時,Peripheral Manager 則會呼叫 peripheralManagerIsReadyToUpdateSubscribers:
代理方法進行回撥。你可以實現這個代理方法,在方法中再次呼叫 updateValue:forCharacteristic:onSubscribedCentrals:
方法傳送通知給訂閱的 Central。
提示:用通知傳送單個資料包給訂閱的 Central,就是說,一旦訂閱的 Central 發行更新時,你就應該呼叫 updateValue:forCharacteristic:onSubscribedCentrals:
方法用單一通知傳送全部的更新值。
並不是所有的資料都是通過通知來傳輸的,這主要取決於你的 Characteristic 的值的大小,只有當 Central 呼叫
CBPeripheral
類的 readValueForCharacteristic:
方法時,你可以檢索全部的值。
參考文獻
1、Performing Common Peripheral Role Tasks
結束語
歡迎在本文下面留言一起交流心得...