iOS淺析藍芽裝置之伺服器(外圍裝置)
藍芽服務端-(外部裝置)
主要內容
1. 建立外部管理器物件
2. 設定本地外設的服務和特徵
3. 新增服務和特徵到到你的設定的資料庫中
4. 向外公佈你的的服務
5. 相應來自連線上的中心裝置的請求
6. 向訂閱了特徵值改變的中心裝置傳送通知
1. 建立外設管理器
首先你需要建立一個CBPeripheralManager
物件,通過CBPeripheralManager
的initWithDelegate:queue:options:
,像這樣:
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
這裡設定self來作為外設的任何事件的接受者.
queue:表示外設管理器分發事件的佇列,當你指定為nil,外設管理器將在主佇列中的分發外設事件.
當你建立CBPeripheralManager物件時,這個管理器物件會呼叫peripheralManagerDidUpdateState:
方法,你必須實現這個來判斷你裝置是否支援並開啟藍芽4.0
2. 設定服務和特徵
你必須按樹形結構進行管理服務
和特徵
,樹結構如圖
1. 服務和特徵通過UUID
來標示的
外設的服務和和特徵通過一個128位的藍芽UUID
來標示的,在在CoreBlueTooth
框架中是通過CBUUID
來表示的,儘管不是所有服務和特質的UUID都需要通過藍芽特定興趣組來(SIG[ Special Interest Group])重定義,但是通過SIG
通用UUID
可以被縮短為16位.例如:通過藍芽SIG
定義表示心率的服務的16為UUID180D
,這個UUID是藍芽4.0,特定的服務UUID0000180D-0000-1000-8000-00805F9B34FB
簡便形式.
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"]; 當你通過16為的簡便形式建立一個UUID,核心藍芽會把填滿為128位的UUID通過基UUID
2. 建立你自己的服務和特徵的UUID
,
1. 你可以通過終端生成一個UUID
$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7
2. 你可以使用這個UUID,通過CBUUID
的UUIDWithString:
方法建立一個CBUUID
物件,像這樣
CBUUID *myCustomServiceUUID =
[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];
3. 構建你的服務和特徵樹
當你有了服務或特徵標示你就可以按照上面的特徵服務樹來組織你的服務和特徵.你可以通過CBMutableCharacteristic
的initWithType:properties:value:permissions:
來建立一個可變特徵,像這樣
CBMutableCharacteristic myCharacteristic =
[[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID
properties:CBCharacteristicPropertyRead
value:myValue permissions:CBAttributePermissionsReadable];
當你建立一個可變的特徵的時候,你就可以指定他的屬性,值和許可.你可以通過這些屬性和許可權來指定這個特徵的值是可讀的
或可寫的
和連線上的中心裝置能否可以訂閱這個值,在這個例子中,中心裝置可以讀取特徵的值,更多資訊參考CBMutableCharacteristic
類
注意
: 當你給某個特徵指定一個值,這個將被快取 並且它的屬性和許可被設定為可讀.因此,如果你需要這個該特徵的值是可寫的
或如果希望這個特徵的值在其所屬服務的生命週期中是可以改變的,你必須指定這個值為nil
.這樣做來確保這個值是動態的和當外設管理器接收來自中心裝置的讀和寫請求是,可以被外設管理器請求.
現在你有一個可變特徵後,你可以建立一個服務,然後關聯上該特徵.你可以通過CBMutableService
的initWithType:primary:
方法,如下:
myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];
在這個例子中,第二個引數設定為YES
,它說明這個服務是一個主要
的服務而不是次要
的,主服務
描述了一個服務的主要功能,他可以包含
其他的服務
,次要服務
必須關聯在他所參照的其他服務的上下文中.比如:心率檢測器的主要服務
是提供心率的資料的,那麼次要服務
可能就是提供心率檢測器的電量資料的.
在你建立服務之後,你可以關聯那個特徵到這個服務上,像這樣:
myService.characteristics = @[myCharacteristic];
3. 添加布服務和特徵到外設資料庫
在你建立了服務和特徵之後,下一步就是新增服務到裝置的服務和特徵資料庫中,在藍芽核心框架中很容易做.你只需要呼叫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]);
}
注意
:當你釋出一個關聯在外設資料庫的服務和特徵時,這個服務將被快取,並且你以後再也不能在修改它了.
4. 公佈你的服務給監聽的中心管理器
當你已經新增服務和特徵到外設的服務特徵資料庫
中後,你已經公佈給服務或特徵給中心管理器
做好準備,你可以通過你的CBPeripheralManager
的startAdvertising:
,傳入一個包含需要公佈資料的字典.
[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
@[myFirstService.UUID, mySecondService.UUID] }];
這個例子中通過CBAdvertisementDataServiceUUIDsKey
這個key
來指定的需要公佈分服務的 UUID
,可能允許使用的key
,在CBCentralManagerDelegate Protocol Reference.
的Advertisement
Data Retrieval Keys
中描述:
1. CBAdvertisementDataLocalNameKey 對應的值是一個字串,描述外設的名稱
2.CBAdvertisementDataManufacturerDataKey 對應的值是一個NSData物件,包含外設的產生的資料
3. CBAdvertisementDataServiceDataKey 包含特定服務的分發資料,該字典的key為代表著該服務的CBUUID物件.值為NSData物件
4. CBAdvertisementDataServiceUUIDsKey 需要公佈的服務的`UUID`陣列
5. CBAdvertisementDataOverflowServiceUUIDsKey 代表著在公佈資料的"overflow"區域能夠被發現的服務的UUID的陣列,因為儲存在這個`UUID`列表是`最大努力的` 並且不總是精確的.如果裝置資源不足這些屬性可能不會被公佈.
6. CBAdvertisementDataTxPowerLevelKey 一個包含外設發射功率NSNumber的數字,如果外設在廣播的資料包中,提供了他的`Tx`功率級別時候,這個屬性是可用的. 使用這個`RSSI` 值和電臺功率,計算出路徑損耗是有可能.
7. CBAdvertisementDataIsConnectable 一個布林值,標示公佈事件型別是否為可連線的,對應這個Key是一個 NSNumber物件,你可用使用這個值來檢查一個外設當前是否為連線狀態
8. CBAdvertisementDataSolicitedServiceUUIDsKey 一個代表著一個或多個服務的`UUID`
但是對於外設管理器只支援兩個key CBAdvertisementDataLocalNameKey
和 CBAdvertisementDataServiceUUIDsKey
.
當你在你的本地外設開始公佈公佈你的外設資料的時候,會呼叫外設管理器代理的peripheralManagerDidStartAdvertising:error:
,你可以通過實現這個方法,來檢視你公佈資料時的錯誤
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error {
if (error) {
NSLog(@"Error advertising: %@", [error localizedDescription]);
}
...
注意:
資料釋出是基於最大努力
的,由於頻寬是有限並且多個app可能同時釋出,更多資訊參考CBPeripheralManager
的startAdvertising:
的註釋. '當你應用後臺也可以進行釋出行為,這個話題參照core bluetooth
文件的Core Bluetooth Background Processing for iOS Apps
一旦你開始公佈資料,遠端的中心裝置就可用發現並連線上你.
5. 響應來自中心裝置
的讀
和寫
請求
當你連線上一個或多箇中心裝置後,你便開始接受來自他們的讀或寫請求,請你確保以合適方式來響應的這些請求,下面的例子來描述如何相應這些請求
當一箇中心管理器對去某個特徵中的值的時候,會呼叫外設管理器的代理方法peripheralManager:didReceiveReadRequest:
,這個方法傳入了一個CBATTRequest
的請求,他提供很多屬來滿足這個請求
例如當你接受到一個單獨讀取某個特徵值的請求,代理方法傳入的CBATTRequest
物件的屬性課用來判斷你裝置資料中的特徵是否與遠端中心裝置請求的特徵是否匹配,你可以向這樣的來實現這個方法
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
...
如果特徵是匹配的,那麼下一步是確保請求讀取資料的索引沒有超出特徵值的範圍.下面的例子向你展示如何使用CBATTRequest
物件的offset
來確保取得的索引沒有超出特徵值的邊界
if (request.offset > myCharacteristic.value.length) {
[myPeripheralManager respondToRequest:request
withResult:CBATTErrorInvalidOffset];
return;
}
假定已經驗證通過,你可以通過本地外設的特徵的值
給請求
設定值(它預設是nil)
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:
方法提供一個失敗的原因.詳情參照CBATTError
列舉
處理來自中央裝置的寫請求是簡單的,當一箇中心裝置傳送物件一個或多個特徵值的寫請求時,外設管理器就會呼叫其代理peripheralManager:didReceiveWriteRequests:
方法,在這個方法中傳入了一個CBATTRequest
物件的陣列.每一個都代表著一個寫請求.在確認請示是可以被滿足的後,你可以寫特徵的值,像這樣:
myCharacteristic.value = request.value;
儘管上面的例子中沒有演示,當寫入你的特徵值時,如何確保寫入的偏移量.但是在你真實的app中需要進行處理.
和是響應讀請求一樣,在代理方法peripheralManager:didReceiveWriteRequests:
明確呼叫一次respondToRequest:withResult:
方法.這也就是說第一個引數是單獨一個請求物件,儘管你可能在peripheralManager:didReceiveWriteRequests:
方法中受到了多個請求物件.你應該傳入陣列中的第一個請求作為引數.像這樣:
[myPeripheralManager respondToRequest:[requests objectAtIndex:0]
withResult:CBATTErrorSuccess];
注意
:對於多個請求物件只有一次請求,只要任意的一個請求物件得不到滿足,你任意一個都不要滿足,而是立即呼叫respondToRequest:withResult:
方法提供一個結果,告訴失敗的原因.
6. 傳送更新特徵的值給已經訂閱的中心裝置
通常情況,連線上的中央裝置將訂閱你的一個或多個特徵值, 當他們這麼做的時候,你有責任當某個訂閱特徵的值發生改變了通知他們.下面的例子描述如何做.
當一個連線上的中心管理器訂閱了你的特徵上的某個值,那麼外設管理器就會呼叫其代理的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,表示所有訂閱的中心裝置都更新資料(連線上沒有訂閱的中心裝置講被忽略).該方法返回一個Bool
,來指示是否傳送成功,如果用於傳入更新值的佇列是滿的,那麼該方法返回NO
,當傳輸佇列可用的時,會呼叫外設管理器
的代理peripheralManagerIsReadyToUpdateSubscribers:
,你可以實現這個方法再次傳送資料
注意:
使用這個方法傳送通知給訂閱的中心裝置管理器一個單獨的資料包,當你更新資料的應該把整個資料作為一個通知傳送.只調用一次updateValue:forCharacteristic:onSubscribedCentrals:
方法,如果你的資料不能通過一次通知傳遞過去,解決方案就是在中心裝置
一邊,通過CBPeripheral
的readValueForCharacteristic:
方法,來獲取資料,這個方法可以取回整個資料.