藍(lán)牙(CoreBluetooth)-外部設(shè)備(服務(wù)端)

主要內(nèi)容

1. 創(chuàng)建外部管理器對(duì)象
2. 設(shè)置本地外設(shè)的服務(wù)和特征
3. 添加服務(wù)和特征到到你的設(shè)置的數(shù)據(jù)庫(kù)中
4. 向外公布你的的服務(wù)
5. 相應(yīng)來(lái)自連接上的中心設(shè)備的請(qǐng)求
6. 向訂閱了特征值改變的中心設(shè)備發(fā)送通知

1. 創(chuàng)建外設(shè)管理器

首先你需要?jiǎng)?chuàng)建一個(gè)CBPeripheralManager 對(duì)象,通過(guò)CBPeripheralManagerinitWithDelegate:queue:options:,像這樣:

  self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];

這里設(shè)置self來(lái)作為外設(shè)的任何事件的接受者.

queue:表示外設(shè)管理器分發(fā)事件的隊(duì)列,當(dāng)你指定為nil,外設(shè)管理器將在主隊(duì)列中的分發(fā)外設(shè)事件.

當(dāng)你創(chuàng)建CBPeripheralManager對(duì)象時(shí),這個(gè)管理器對(duì)象會(huì)調(diào)用peripheralManagerDidUpdateState:方法,你必須實(shí)現(xiàn)這個(gè)來(lái)判斷你設(shè)備是否支持并開(kāi)啟藍(lán)牙4.0

2. 設(shè)置服務(wù)和特征

你必須按樹(shù)形結(jié)構(gòu)進(jìn)行管理服務(wù)特征,樹(shù)結(jié)構(gòu)如圖

外設(shè),服務(wù),特征結(jié)構(gòu)圖

1. 服務(wù)和特征通過(guò)UUID來(lái)標(biāo)示的

外設(shè)的服務(wù)和和特征通過(guò)一個(gè)128位的藍(lán)牙UUID來(lái)標(biāo)示的,在在CoreBlueTooth框架中是通過(guò)CBUUID來(lái)表示的,盡管不是所有服務(wù)和特質(zhì)的UUID都需要通過(guò)藍(lán)牙特定興趣組來(lái)(SIG[ Special Interest Group])重定義,但是通過(guò)SIG定義或分發(fā)的通用UUID可以被縮短為16位.例如:通過(guò)藍(lán)牙SIG定義表示心率的服務(wù)的16為UUID180D,這個(gè)UUID是藍(lán)牙4.0,特定的服務(wù)UUID0000180D-0000-1000-8000-00805F9B34FB簡(jiǎn)便形式.
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
當(dāng)你通過(guò)16為的簡(jiǎn)便形式創(chuàng)建一個(gè)UUID,核心藍(lán)牙會(huì)把填滿(mǎn)為128位的UUID通過(guò)基UUID

2. 創(chuàng)建你自己的服務(wù)和特征的UUID,

1. 你可以通過(guò)終端生成一個(gè)UUID
$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7```

#####2. 你可以使用這個(gè)UUID,通過(guò)`CBUUID`的` UUIDWithString:`方法創(chuàng)建一個(gè)`CBUUID`對(duì)象,像這樣

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

3. 構(gòu)建你的服務(wù)和特征樹(shù)

當(dāng)你有了服務(wù)或特征標(biāo)示你就可以按照上面的特征服務(wù)樹(shù)來(lái)組織你的服務(wù)和特征.你可以通過(guò)CBMutableCharacteristicinitWithType:properties:value:permissions:來(lái)創(chuàng)建一個(gè)可變特征,像這樣
myCharacteristic = [[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID properties:CBCharacteristicPropertyRead value:myValue permissions:CBAttributePermissionsReadable];

當(dāng)你創(chuàng)建一個(gè)可變的特征的時(shí)候,你就可以指定他的屬性,值和許可.你可以通過(guò)這些屬性和權(quán)限來(lái)指定這個(gè)特征的值是可讀的可寫(xiě)的和連接上的中心設(shè)備能否可以訂閱這個(gè)值,在這個(gè)例子中,中心設(shè)備可以讀取特征的值,更多信息參考CBMutableCharacteristic 類(lèi)

注意: 當(dāng)你給某個(gè)特征指定一個(gè)值,這個(gè)將被緩存 并且它的屬性和許可被設(shè)置為可讀.因此,如果你需要這個(gè)該特征的值是可寫(xiě)的或如果希望這個(gè)特征的值在其所屬服務(wù)的生命周期中是可以改變的,你必須指定這個(gè)值為nil.這樣做來(lái)確保這個(gè)值是動(dòng)態(tài)的和當(dāng)外設(shè)管理器接收來(lái)自中心設(shè)備的讀和寫(xiě)請(qǐng)求是,可以被外設(shè)管理器請(qǐng)求.

現(xiàn)在你有一個(gè)可變特征后,你可以創(chuàng)建一個(gè)服務(wù),然后關(guān)聯(lián)上該特征.你可以通過(guò)CBMutableServiceinitWithType:primary:方法,如下:

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

在這個(gè)例子中,第二個(gè)參數(shù)設(shè)置為`YES` ,它說(shuō)明這個(gè)服務(wù)是一個(gè)`主要`的服務(wù)而不是`次要`的,`主服務(wù)`描述了一個(gè)服務(wù)的主要功能,他可以`包含`其他的`服務(wù)`,`次要服務(wù)`必須關(guān)聯(lián)在他所參照的其他服務(wù)的上下文中.比如:心率檢測(cè)器的`主要服務(wù)`是提供心率的數(shù)據(jù)的,那么`次要服務(wù)`可能就是提供心率檢測(cè)器的電量數(shù)據(jù)的.
    
在你創(chuàng)建服務(wù)之后,你可以關(guān)聯(lián)那個(gè)特征到這個(gè)服務(wù)上,像這樣:

myService.characteristics = @[myCharacteristic];```

3. 添加布服務(wù)和特征到外設(shè)數(shù)據(jù)庫(kù)

在你創(chuàng)建了服務(wù)和特征之后,下一步就是添加服務(wù)到設(shè)備的服務(wù)和特征數(shù)據(jù)庫(kù)中,在藍(lán)牙核心框架中很容易做.你只需要調(diào)用CBPeripheralManageraddService: 方法即可,像這樣:

 [myPeripheralManager addService:myService];```
 
當(dāng)你通過(guò)這個(gè)方法添加服務(wù)到服務(wù)特征數(shù)據(jù)庫(kù)中時(shí),外設(shè)管理器會(huì)調(diào)用代理的` peripheralManager:didAddService:error: `方法,當(dāng)分發(fā)失敗了會(huì)調(diào)用這個(gè)方法,實(shí)現(xiàn)這個(gè)方法可以查看錯(cuò)誤原因
  • (void)peripheralManager:(CBPeripheralManager *)peripheral
    didAddService:(CBService *)service
    error:(NSError *)error {

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

注意:當(dāng)你發(fā)布一個(gè)關(guān)聯(lián)在外設(shè)數(shù)據(jù)庫(kù)的服務(wù)和特征時(shí),這個(gè)服務(wù)將被緩存,并且你以后再也不能在修改它了.

4. 公布你的服務(wù)給監(jiān)聽(tīng)的中心管理器

當(dāng)你已經(jīng)添加服務(wù)和特征到外設(shè)的服務(wù)特征數(shù)據(jù)庫(kù)中后,你已經(jīng)公布給服務(wù)或特征給中心管理器做好準(zhǔn)備,你可以通過(guò)你的CBPeripheralManagerstartAdvertising:,傳入一個(gè)包含需要公布數(shù)據(jù)的字典.

 [myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
    @[myFirstService.UUID, mySecondService.UUID] }];```
    
這個(gè)例子中通過(guò)`CBAdvertisementDataServiceUUIDsKey` 這個(gè)`key`來(lái)指定的需要公布分服務(wù)的 `UUID`,可能允許使用的`key`,在`CBCentralManagerDelegate Protocol Reference. `的`Advertisement Data Retrieval Keys` 中描述:
    1. CBAdvertisementDataLocalNameKey 對(duì)應(yīng)的值是一個(gè)字符串,描述外設(shè)的名稱(chēng)
    2.CBAdvertisementDataManufacturerDataKey 對(duì)應(yīng)的值是一個(gè)NSData對(duì)象,包含外設(shè)的產(chǎn)生的數(shù)據(jù)
    3. CBAdvertisementDataServiceDataKey 包含特定服務(wù)的分發(fā)數(shù)據(jù),該字典的key為代表著該服務(wù)的CBUUID對(duì)象.值為NSData對(duì)象
    4. CBAdvertisementDataServiceUUIDsKey 需要公布的服務(wù)的`UUID`數(shù)組
    5. CBAdvertisementDataOverflowServiceUUIDsKey 代表著在公布數(shù)據(jù)的"overflow"區(qū)域能夠被發(fā)現(xiàn)的服務(wù)的UUID的數(shù)組,因?yàn)榇鎯?chǔ)在這個(gè)`UUID`列表是`最大努力的` 并且不總是精確的.如果設(shè)備資源不足這些屬性可能不會(huì)被公布.
    6. CBAdvertisementDataTxPowerLevelKey 一個(gè)包含外設(shè)發(fā)射功率NSNumber的數(shù)字,如果外設(shè)在廣播的數(shù)據(jù)包中,提供了他的`Tx`功率級(jí)別時(shí)候,這個(gè)屬性是可用的. 使用這個(gè)`RSSI` 值和電臺(tái)功率,計(jì)算出路徑損耗是有可能.
    7. CBAdvertisementDataIsConnectable 一個(gè)布爾值,標(biāo)示公布事件類(lèi)型是否為可連接的,對(duì)應(yīng)這個(gè)Key是一個(gè) NSNumber對(duì)象,你可用使用這個(gè)值來(lái)檢查一個(gè)外設(shè)當(dāng)前是否為連接狀態(tài)
    8. CBAdvertisementDataSolicitedServiceUUIDsKey 一個(gè)代表著一個(gè)或多個(gè)服務(wù)的`UUID`

但是對(duì)于外設(shè)管理器只支持兩個(gè)key  `CBAdvertisementDataLocalNameKey`和 `CBAdvertisementDataServiceUUIDsKey`. 

當(dāng)你在你的本地外設(shè)開(kāi)始公布公布你的外設(shè)數(shù)據(jù)的時(shí)候,會(huì)調(diào)用外設(shè)管理器代理的`peripheralManagerDidStartAdvertising:error: `,你可以通過(guò)實(shí)現(xiàn)這個(gè)方法,來(lái)查看你公布數(shù)據(jù)時(shí)的錯(cuò)誤
    
  • (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
    error:(NSError *)error {

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


> `注意:` 數(shù)據(jù)發(fā)布是基于`最大努力`的,由于帶寬是有限并且多個(gè)app可能同時(shí)發(fā)布,更多信息參考`CBPeripheralManager `的`startAdvertising: ` 的注釋.
'當(dāng)你應(yīng)用后臺(tái)也可以進(jìn)行發(fā)布行為,這個(gè)話(huà)題參照`core bluetooth`文檔的` Core Bluetooth Background Processing for iOS Apps`

一旦你開(kāi)始公布數(shù)據(jù),遠(yuǎn)程的中心設(shè)備就可用發(fā)現(xiàn)并連接上你.

###5. 響應(yīng)來(lái)自`中心設(shè)備`的`讀`和`寫(xiě)`請(qǐng)求

當(dāng)你連接上一個(gè)或多個(gè)中心設(shè)備后,你便開(kāi)始接受來(lái)自他們的讀或?qū)懻?qǐng)求,請(qǐng)你確保以合適方式來(lái)響應(yīng)的這些請(qǐng)求,下面的例子來(lái)描述如何相應(yīng)這些請(qǐng)求

當(dāng)一個(gè)中心管理器對(duì)去某個(gè)特征中的值的時(shí)候,會(huì)調(diào)用外設(shè)管理器的代理方法`peripheralManager:didReceiveReadRequest: `,這個(gè)方法傳入了一個(gè)`CBATTRequest`的請(qǐng)求,他提供很多屬來(lái)滿(mǎn)足這個(gè)請(qǐng)求

例如當(dāng)你接受到一個(gè)單獨(dú)讀取某個(gè)特征值的請(qǐng)求,代理方法傳入的`CBATTRequest`對(duì)象的屬性課用來(lái)判斷你設(shè)備數(shù)據(jù)中的特征是否與遠(yuǎn)程中心設(shè)備請(qǐng)求的特征是否匹配,你可以向這樣的來(lái)實(shí)現(xiàn)這個(gè)方法
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {

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

如果特征是匹配的,那么下一步是確保請(qǐng)求讀取數(shù)據(jù)的索引沒(méi)有超出特征值的范圍.下面的例子向你展示如何使用`CBATTRequest`對(duì)象的`offset` 來(lái)確保取得的索引沒(méi)有超出特征值的邊界

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


假定已經(jīng)驗(yàn)證通過(guò),你可以通過(guò)本地外設(shè)的`特征的值`給`請(qǐng)求`設(shè)置值(它默認(rèn)是nil)
request.value = [myCharacteristic.value
subdataWithRange:NSMakeRange(request.offset,
myCharacteristic.value.length - request.offset)];```

在你設(shè)置值之后,你需要告知遠(yuǎn)程的中心設(shè)置已經(jīng)成功設(shè)置,通過(guò)明確調(diào)用CBPeripheralManagerrespondToRequest:withResult:方法,像這樣:

[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
    ...```

每次在代理對(duì)象的`peripheralManager:didReceiveReadRequest:`方法中明確調(diào)用` respondToRequest:withResult: `

> `注意`: 如果特征的`UUID`不匹配或因任何原因?qū)е碌淖x不能完成.你不要試圖來(lái)填充請(qǐng)求的值,而是立即調(diào)用` respondToRequest:withResult:`方法提供一個(gè)失敗的原因.詳情參照`CBATTError`枚舉
    

處理來(lái)自中央設(shè)備的寫(xiě)請(qǐng)求是簡(jiǎn)單的,當(dāng)一個(gè)中心設(shè)備發(fā)送對(duì)象一個(gè)或多個(gè)特征值的寫(xiě)請(qǐng)求時(shí),外設(shè)管理器就會(huì)調(diào)用其代理`peripheralManager:didReceiveWriteRequests: `方法,在這個(gè)方法中傳入了一個(gè)`CBATTRequest `對(duì)象的數(shù)組.每一個(gè)都代表著一個(gè)寫(xiě)請(qǐng)求.在確認(rèn)請(qǐng)示是可以被滿(mǎn)足的后,你可以寫(xiě)特征的值,像這樣:
myCharacteristic.value = request.value;```

盡管上面的例子中沒(méi)有演示,當(dāng)寫(xiě)入你的特征值時(shí),如何確保寫(xiě)入的偏移量.但是在你真實(shí)的app中需要進(jìn)行處理.

和是響應(yīng)讀請(qǐng)求一樣,在代理方法peripheralManager:didReceiveWriteRequests:明確調(diào)用一次respondToRequest:withResult: 方法.這也就是說(shuō)第一個(gè)參數(shù)是單獨(dú)一個(gè)請(qǐng)求對(duì)象,盡管你可能在peripheralManager:didReceiveWriteRequests:方法中受到了多個(gè)請(qǐng)求對(duì)象.你應(yīng)該傳入數(shù)組中的第一個(gè)請(qǐng)求作為參數(shù).像這樣:

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

> `注意`:對(duì)于多個(gè)請(qǐng)求對(duì)象只有一次請(qǐng)求,只要任意的一個(gè)請(qǐng)求對(duì)象得不到滿(mǎn)足,你任意一個(gè)都不要滿(mǎn)足,而是立即調(diào)用`respondToRequest:withResult:`方法提供一個(gè)結(jié)果,告訴失敗的原因.

###6. 發(fā)送更新特征的值給已經(jīng)訂閱的中心設(shè)備

通常情況,連接上的中央設(shè)備將訂閱你的一個(gè)或多個(gè)特征值, 當(dāng)他們這么做的時(shí)候,你有責(zé)任當(dāng)某個(gè)訂閱特征的值發(fā)生改變了通知他們.下面的例子描述如何做.

當(dāng)一個(gè)連接上的中心管理器訂閱了你的特征上的某個(gè)值,那么外設(shè)管理器就會(huì)調(diào)用其代理的`peripheralManager:central:didSubscribeToCharacteristic:`方法調(diào)用
- (void)peripheralManager:(CBPeripheralManager *)peripheral
              central:(CBCentral *)central

didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {

NSLog(@"Central subscribed to characteristic %@", characteristic);
...
   
使用上面的方法作為一個(gè)開(kāi)始發(fā)送更新中心設(shè)備數(shù)據(jù)的引子,接下獲取特征的更新值并調(diào)用` CBPeripheralManager` 的 `updateValue:forCharacteristic:onSubscribedCentrals: `把更新數(shù)據(jù)發(fā)送給中心設(shè)備

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

當(dāng)你通過(guò)上面的方法給訂閱的中心設(shè)備發(fā)送更新數(shù)據(jù)時(shí),你可以通過(guò)最后一個(gè)參數(shù)指定更新那些中心設(shè)備,如果傳入nil,表示所有訂閱的中心設(shè)備都更新數(shù)據(jù)(連接上沒(méi)有訂閱的中心設(shè)備講被忽略).該方法返回一個(gè)Bool,來(lái)指示是否發(fā)送成功,如果用于傳入更新值的隊(duì)列是滿(mǎn)的,那么該方法返回NO,當(dāng)傳輸隊(duì)列可用的時(shí),會(huì)調(diào)用外設(shè)管理器的代理peripheralManagerIsReadyToUpdateSubscribers:,你可以實(shí)現(xiàn)這個(gè)方法再次傳送數(shù)據(jù)

注意:使用這個(gè)方法發(fā)送通知給訂閱的中心設(shè)備管理器一個(gè)單獨(dú)的數(shù)據(jù)包,當(dāng)你更新數(shù)據(jù)的應(yīng)該把整個(gè)數(shù)據(jù)作為一個(gè)通知發(fā)送.只調(diào)用一次updateValue:forCharacteristic:onSubscribedCentrals:方法,如果你的數(shù)據(jù)不能通過(guò)一次通知傳遞過(guò)去,解決方案就是在中心設(shè)備一邊,通過(guò) CBPeripheralreadValueForCharacteristic:方法,來(lái)獲取數(shù)據(jù),這個(gè)方法可以取回整個(gè)數(shù)據(jù).

外設(shè)流程流程圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容