歡迎訪問(wèn)我的博客 muhlenXi,該文章出自我的博客, 歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明來(lái)源: http://muhlenxi.com/2017/05/01/iOS-Bluetooth-Low-Energy-Develop-Chapter3低剔。
導(dǎo)語(yǔ):
在這一節(jié)账蓉,你將會(huì)學(xué)到袱蚓,如何通過(guò) CoreBluetooth 框架來(lái)實(shí)現(xiàn) Local Peripheral 方面的功能和代理方法钞啸。
在 iOS BLE 開(kāi)發(fā)小記[2]中,你已經(jīng)學(xué)到了如何在 Central 方面去調(diào)用 BLE 的常用方法喇潘。在這一節(jié)中体斩,你將學(xué)習(xí)用 CoreBluetooth 框架來(lái)調(diào)用 Peripheral 方面 BLE 的常用方法。通過(guò)本文的示例代碼响蓉,將會(huì)引導(dǎo)你開(kāi)發(fā)一個(gè)將你的 Local 設(shè)備實(shí)現(xiàn)為 Local Peripheral硕勿。你將會(huì)從中學(xué)到:
- 如何創(chuàng)建一個(gè) Peripheral Manager 對(duì)象
- 如何為你的 Local Peripheral 設(shè)置 Services 和 Characteristics
- 如何發(fā)布你的 Services 和 Characteristics 數(shù)據(jù)
- 如何廣播你的設(shè)備
- 如何對(duì)連接的 Central 做讀寫請(qǐng)求響應(yīng)
- 如何發(fā)送更新后的值給訂閱的 Central
或許你發(fā)現(xiàn)示例代碼過(guò)于簡(jiǎn)單和抽象哨毁,你需要在你的 App 中做些恰當(dāng)?shù)木毩?xí)來(lái)掌握這些內(nèi)容。更高級(jí)的技巧和最佳實(shí)踐在后續(xù)的文章中將會(huì)講解。
Peripheral 實(shí)現(xiàn)詳情
創(chuàng)建一個(gè) Peripheral Manager 對(duì)象
在 Local Device(當(dāng)前設(shè)備)實(shí)現(xiàn) Peripheral 規(guī)范的第一步是分配(allocate)和初始化(initialize)一個(gè)周邊管理(Peripheral Manager)民傻,(用 CBPeripheralManager 對(duì)象表示)呀舔,通過(guò)調(diào)用 CBPeripheralManager 的 initWithDelegate:queue:options:
方法來(lái)創(chuàng)建管理對(duì)象,如下所示
myPeripheralManager =
[[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];
在示例代碼中话浇,設(shè)置 Delegate 為 self 是為了接收 Peripheral 的事件響應(yīng)脏毯,將參數(shù) dispatch queue 置為 nil。意味著 Peripheral Manager 將會(huì)在主隊(duì)列中分發(fā)事件幔崖。
當(dāng)你創(chuàng)建一個(gè) Peripheral Manger 對(duì)象時(shí)食店,Peripheral Manager 會(huì)通過(guò) peripheralManagerDidUpdateState:
方法來(lái)代理回調(diào),你必須實(shí)現(xiàn)這個(gè)代理方法來(lái)確保當(dāng)前設(shè)備是否支持 BLE 技術(shù)赏寇,關(guān)于代理方法的詳情可以查閱 CBPeripheralManagerDelegate Protocol Reference.
設(shè)置你的 Services 和 Characteristics
在第一節(jié)中吉嫩,我們了解到,一個(gè) Local Peripheral 采用樹(shù)形結(jié)構(gòu)來(lái)組織 Services 和 Characteristics 的數(shù)據(jù)嗅定。因此必須采用樹(shù)形結(jié)構(gòu)方式來(lái)設(shè)置 Local Peripheral 的 Services 和 Characteristics自娩。你第一步要做的是搞清和理解 Service 和 Characteristic 是如何標(biāo)識(shí)的。
通過(guò) UUID 標(biāo)識(shí) Services 和 Characteristics
Peripheral 的 Service 和 Characteristic 是通過(guò) 128 位的特定藍(lán)牙 UUID(通用唯一識(shí)別碼)來(lái)標(biāo)識(shí)的渠退,在 CoreBluetooth 中是用 CBUUID 對(duì)象來(lái)表示的忙迁。并不是所有的 UUID 都是通過(guò) Bluetooth Special Interest Group (藍(lán)牙特別興趣小組)預(yù)定義的。為了方便起見(jiàn)碎乃,Bluetooth SIG 定義和發(fā)布了許多通用的 16位 UUID姊扔。舉個(gè)例子,Bluetooth SIG 事先定義了一個(gè)16位的 UUID 用來(lái)標(biāo)識(shí)一個(gè)心率 Service梅誓,該 UUID 是 128位 UUID 0000180D-0000-1000-8000-00805F9B34FB 進(jìn)行縮減而來(lái)的恰梢,這是基于藍(lán)牙 4.0 規(guī)范中晨川,第 3 卷 F 部分第 3.2.1 節(jié)定義的藍(lán)牙基礎(chǔ) UUID。
CBUUID 提供了一個(gè)處理比較長(zhǎng)的 UUID 的工廠方法删豺,舉個(gè)例子共虑,生成一個(gè)表示心率 Service 的 UUID,可以調(diào)用 UUIDWithString
方法來(lái)通過(guò)預(yù)定義的 16位 UUID來(lái)創(chuàng)建 CBUUID 對(duì)象呀页。
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
當(dāng)你通過(guò)預(yù)定義的 16位 UUID 來(lái)創(chuàng)建 CBUUID 對(duì)象時(shí)妈拌,CoreBluetooth 會(huì)基于128位Bluetooth Base UUID 填充剩下的的 UUID 位。
為你定制的 Services 和 Characteristics 生成 UUID
你的 Service 和 Characteristic 的UUID也許可能沒(méi)有被 Bluetooth UUIDs 預(yù)定義蓬蝶,如果沒(méi)有被預(yù)定義尘分,你需要手動(dòng)生成你自己的 128位 UUID 來(lái)表示 Service 和 Characteristic。
通過(guò)命令行命令 uuidgen
可以生成 128位的 UUID丸氛,打開(kāi)你的 Terminal(終端)培愁,通過(guò)這種方式依次為你的 Services 和 Characteristics 生成一個(gè) UUID (用連字符連接起來(lái)的字符串)來(lái)標(biāo)識(shí)。舉例如下:
$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7
你可以用上面方法生成的 UUID 調(diào)用 UUIDWithString
方法來(lái)創(chuàng)建一個(gè) CBUUID 對(duì)象缓窜。
CBUUID *myCustomServiceUUID =
[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];
構(gòu)建你的 服務(wù)特征樹(shù)
當(dāng)你為每個(gè) Service 和 Characteristic 創(chuàng)建 CBUUID 對(duì)象后定续,你可以創(chuàng)建 mutable Service(可變服務(wù)) 和 mutable Characteristic(可變特征),然后以樹(shù)形的方式組織它們禾锤。舉個(gè)例子私股,如果你現(xiàn)在有一個(gè) Characteristic 的 UUID,你可以通過(guò)調(diào)用 CBMutableCharacteristic 類的 initWithType:properties:value:permissions:
方法生成一個(gè) mutable Characteristic恩掷。
myCharacteristic =
[[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID
properties:CBCharacteristicPropertyRead
value:myValue permissions:CBAttributePermissionsReadable];
當(dāng)你創(chuàng)建 mutable Characteristic 的時(shí)候倡鲸,你可以指定它的 properties(屬性)、value(值)和 permissions(權(quán)限許可)黄娘,你指定的 properties 和 permissions 決定這個(gè) Characteristic 的值是否可以讀或者寫峭状,或者連接的 Central 能否訂閱該 Characteristic 的值。下面的示例中逼争,Characteristic 的值是被指定為可讀的优床。關(guān)于 mutable Characteristic 的 properties 和 permissions 詳情可以查閱 CBMutableCharacteristic Class Reference.
提示:如果你指定了 Characteristic 的值,那么該值將被緩存并且該 Characteristic 的 properties 和 permissions 將被設(shè)置為可讀的氮凝。因此羔巢,如果你需要 Characteristic 的值是可寫的,或者你希望在 Service 發(fā)布后罩阵,Characteristic 的值在 lifetime(生命周期)中依然可以更改竿秆,你必須將該 Characteristic 的值指定為 nil。通過(guò)這種方式可以確保 Characteristic 的值,在 Peripheral Manager 收到來(lái)自連接的 Central 的讀或者寫請(qǐng)求的時(shí)候稿壁,能夠被動(dòng)態(tài)處理幽钢。
既然你創(chuàng)建了一個(gè) mutable Characteristic,你也能通過(guò)調(diào)用 CBMutableService
類的 initWithType:primary:
方法創(chuàng)建一個(gè) mutable Service傅是。如下所示:
myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];
在示例代碼中匪燕,第二個(gè)參數(shù)被指定為 YES蕾羊,用來(lái)表明該 Service 是 Primary(主要的),而不是 secondary(次要的)帽驯。一個(gè) Primary Service 用來(lái)描述這個(gè)設(shè)備的主要功能龟再,還可以用來(lái)引用其他的 Service。一個(gè) Secondary Service 用來(lái)描述的是上下文中相關(guān)的或者被引用的 Service尼变。舉個(gè)例子利凑,從心率傳感器中獲取心率的服務(wù)是 primary Service,而獲取傳感器電量的服務(wù)就可以被視為 secondary Service 嫌术。
當(dāng)你創(chuàng)建完 Service 后哀澈。你需要設(shè)置 Service 的 Characteristic 數(shù)組屬性,如下:
myService.characteristics = @[myCharacteristic];
發(fā)送你的 Services 和 Characteristics
當(dāng)你構(gòu)建好服務(wù)特征樹(shù)后度气,下一步就是按照 BLE 的規(guī)范發(fā)布到設(shè)備的服務(wù)特征庫(kù)中割按,用 CoreBluetooth 可以很輕松的完成這一步,只需要調(diào)用 CBPeripheralManager
類 的 addService:
方法就可以了磷籍。 示例代碼如下:
[myPeripheralManager addService:myService];
當(dāng)你調(diào)用該方法發(fā)布服務(wù)時(shí)适荣,Peripheral Manager 會(huì)調(diào)用 peripheralManager:didAddService:error:
方法進(jìn)行代理回調(diào),實(shí)現(xiàn)這個(gè)代理方法可以獲取到產(chǎn)生的錯(cuò)誤择示,示例代碼如下:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service
error:(NSError *)error {
if (error) {
NSLog(@"Error publishing service: %@", [error localizedDescription]);
}
// ...
}
提示:當(dāng)你發(fā)布 Service 和相關(guān)的 Characteristic 到 Peripheral 的數(shù)據(jù)庫(kù)中后束凑,設(shè)備已經(jīng)將數(shù)據(jù)緩存,你不能再改變它了栅盲。
廣播你的 Service
當(dāng)你發(fā)布你的 Service 和 Characteristic 到設(shè)備的服務(wù)特征庫(kù)時(shí),你可以廣播一些服務(wù)給正在監(jiān)聽(tīng)的 Central废恋,你可以通過(guò)調(diào)用 CBPeripheralManager
類的 startAdvertising:
方法來(lái)開(kāi)始廣播谈秫,傳入的字典是要廣播的數(shù)據(jù)。
[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
@[myFirstService.UUID, mySecondService.UUID] }];
在示例代碼中鱼鼓,傳入的字典中唯一的 key 是 CBAdvertisementDataServiceUUIDsKey
,用一個(gè)包含 CBUUID 對(duì)象的數(shù)組來(lái)表示你想要廣播的服務(wù)的 UUID拟烫。你在字典中可以指定的其他 key 在 Advertisement Data Retrieval Keys 中有詳細(xì)說(shuō)明。也就是說(shuō)迄本,僅有 CBAdvertisementDataLocalNameKey
和 CBAdvertisementDataServiceUUIDsKey
這兩個(gè) key 支持 Peripheral Manager 對(duì)象硕淑。
當(dāng)你在本地設(shè)備中廣播一些數(shù)據(jù)時(shí),Peripheral Manager 會(huì)通過(guò) peripheralManagerDidStartAdvertising:error:
方法來(lái)代理回調(diào)嘉赎。如果你的設(shè)備不能廣播而發(fā)生錯(cuò)誤時(shí)置媳,實(shí)現(xiàn)這個(gè)代理方法可以獲取產(chǎn)生的錯(cuò)誤:
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error {
if (error) {
NSLog(@"Error advertising: %@", [error localizedDescription]);
}
// ...
}
提示:廣播數(shù)據(jù)方法會(huì)被盡力執(zhí)行,因?yàn)榭臻g是有限的和多個(gè) APP 可能同時(shí)需要廣播數(shù)據(jù)公条,更多詳情可以查閱關(guān)于 startAdvertising: 方法的討論拇囊。
當(dāng)你的 APP 在后臺(tái)運(yùn)行時(shí)也會(huì)影響廣播的行為,這一內(nèi)容將會(huì)在下一篇中進(jìn)行討論靶橱。
響應(yīng) Central 的讀寫請(qǐng)求
當(dāng)你連接一個(gè)或多個(gè) Central 后寥袭,你可能會(huì)收到讀或者寫的請(qǐng)求路捧,對(duì)這些請(qǐng)求作出響應(yīng)需要采取恰當(dāng)?shù)姆绞剑旅娴氖纠a將會(huì)描述如何處理這些請(qǐng)求传黄。
當(dāng)一個(gè)連接的 Central 發(fā)送讀取某個(gè) Characteristic 數(shù)據(jù)的請(qǐng)求時(shí)杰扫,Peripheral Manager 會(huì)調(diào)用 peripheralManager:didReceiveReadRequest:
方法進(jìn)行代理回調(diào)。代理方法以 CBATTRequest
對(duì)象的方式來(lái)傳遞請(qǐng)求膘掰,它包含一些請(qǐng)求的屬性涉波。
比如,當(dāng)你收到一個(gè)讀取 Characteristic 值的簡(jiǎn)單請(qǐng)求時(shí)炭序,可以通過(guò)代理方法回調(diào)的 CBATTRequest
對(duì)像來(lái)判斷 Central 指定要讀取的 Characteristic 是否和設(shè)備服務(wù)庫(kù)中的 Characteristic 是否相匹配啤覆。你可以開(kāi)始實(shí)現(xiàn)這個(gè)代理方法,示例代碼如下:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
// ...
}
}
如果 Characteristic 的 UUID 能夠匹配惭聂,下一步就是確保讀取請(qǐng)求的位置沒(méi)有超出 Characteristic 的值的邊界窗声。如下面代碼所示,你可以通過(guò)使用 CBATTRequest
對(duì)象的 offset
屬性來(lái)確保讀取請(qǐng)求沒(méi)有嘗試讀取范圍之外的數(shù)據(jù)辜纲。
if (request.offset > myCharacteristic.value.length) {
[myPeripheralManager respondToRequest:request
withResult:CBATTErrorInvalidOffset];
return;
}
假如讀取請(qǐng)求的 offset(偏移)已經(jīng)確認(rèn)笨觅,現(xiàn)在就可以設(shè)置請(qǐng)求的 Characteristic 的屬性(默認(rèn)值為 nil)為你設(shè)備中的 Characteristic 的值了,你應(yīng)該重視讀取請(qǐng)求的偏移:
request.value = [myCharacteristic.value
subdataWithRange:NSMakeRange(request.offset,
myCharacteristic.value.length - request.offset)];
設(shè)置完值后耕腾,通過(guò)調(diào)用 respondToRequest:withResult:
方法并傳入 request(更新值后的)和 請(qǐng)求的結(jié)果參數(shù)來(lái)對(duì) Remote Central 的請(qǐng)求作出響應(yīng)表示請(qǐng)求已經(jīng)被成功處理见剩。示例代碼如下:
[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
// ...
只要代理方法 peripheralManager:didReceiveReadRequest:
方法被回調(diào),就需要準(zhǔn)確的調(diào)用 respondToRequest:withResult:
方法扫俺。
提示:如果 Characteristic 的 UUID 不匹配苍苞,或者因?yàn)槟撤N原因不能完全讀取,不必去填充請(qǐng)求狼纬,直接調(diào)用 respondToRequest:withResult:
方法并提供一個(gè)表示失敗的結(jié)果即可羹呵。你可能指定的結(jié)果列表見(jiàn) CBATTError Constants 常量枚舉。
處理連接的 Central 寫入請(qǐng)求也比較易懂疗琉。當(dāng) Central 發(fā)送一個(gè)寫入請(qǐng)求給一個(gè)或多個(gè)你的 Characteristic 時(shí)冈欢,Peripheral Manager 會(huì)通過(guò) peripheralManager:didReceiveWriteRequests:
方法來(lái)代理回調(diào)。這是盈简,代理方法會(huì)傳遞一個(gè)包含一個(gè)或多個(gè) CBATTRequest
對(duì)象的數(shù)組給你凑耻,數(shù)組中的每個(gè)對(duì)象都代表一個(gè)寫入請(qǐng)求。當(dāng)你確定寫入請(qǐng)求能夠處理時(shí)柠贤,你可以設(shè)置 Characteristic 的值香浩,示例代碼如下:
myCharacteristic.value = request.value;
雖然上述例子沒(méi)有證明這一點(diǎn),但當(dāng)你給 Characteristic 寫數(shù)據(jù)的時(shí)候种吸,你應(yīng)該確保請(qǐng)求的 offset 屬性的范圍有效弃衍。
就像你響應(yīng)讀取請(qǐng)求一樣,只要代理方法 peripheralManager:didReceiveWriteRequest:
方法被回調(diào)坚俗,就需要準(zhǔn)確無(wú)誤的調(diào)用 respondToRequest:withResult:
方法镜盯。也就是說(shuō)岸裙,respondToRequest:withResult:
方法期望有一個(gè) CBATTRequest
對(duì)象,即使你可能通過(guò) peripheralManager:didReceiveWriteRequests:
代理方法接收到一個(gè)包含 CBATTRequest
對(duì)象的數(shù)組速缆,你也應(yīng)該傳入數(shù)組中的第一個(gè)對(duì)象降允,示例代碼如下:
[myPeripheralManager respondToRequest:[requests objectAtIndex:0]
withResult:CBATTErrorSuccess];
提示:將多請(qǐng)求視為單一請(qǐng)求來(lái)對(duì)待,如果個(gè)別的請(qǐng)求不能被填充艺糜,你就不必填充其余的請(qǐng)求了剧董,直接調(diào)用 respondToRequest:withResult:
方法并提供一個(gè)表示失敗的結(jié)果即可。
發(fā)送更新 Characteristic 的通知給訂閱的 Central
連接的 Central 經(jīng)常會(huì)訂閱一個(gè)或多個(gè) Characteristic 的值破停,當(dāng)這些值發(fā)生變化時(shí)翅楼,你應(yīng)該發(fā)送通知給訂閱的 Central 。
當(dāng)一個(gè)連接的 Central 訂閱一個(gè)或多個(gè)你的 Characteristic 值時(shí)真慢,Peripheral Manager 會(huì)通過(guò) peripheralManager:central:didSubscribeToCharacteristic:
方法來(lái)代理回調(diào)毅臊。示例代碼如下:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"Central subscribed to characteristic %@", characteristic);
// ...
}
將上述的代理方法作為一個(gè)線索來(lái)開(kāi)始給 Central 發(fā)送更新后的值。
接著黑界,獲取更新后的 Characteristic 的值管嬉,通過(guò)調(diào)用 CBPeripheralManager
類的 updateValue:forCharacteristic:onSubscribedCentrals:
方法來(lái)給 Central 發(fā)送通知。示例代碼如下:
NSData *updatedValue = // fetch the characteristic's new value
BOOL didSendValue = [myPeripheralManager updateValue:updatedValue
forCharacteristic:characteristic onSubscribedCentrals:nil];
當(dāng)你調(diào)用這個(gè)方法給訂閱的 Central 發(fā)送通知時(shí)朗鸠,你可以通過(guò)最后的那個(gè)參數(shù)來(lái)指定要發(fā)送的 Central蚯撩,示例代碼中的參數(shù)為 nil,表明將會(huì)發(fā)送通知給所有連接且訂閱的 Central烛占,沒(méi)有訂閱的 Central 則會(huì)被忽略胎挎。
updateValue:forCharacteristic:onSubscribedCentrals:
方法會(huì)返回一個(gè) Boolean 類型的值來(lái)表示通知是否成功的發(fā)送給訂閱的 Central 了,如果 base queue (基礎(chǔ)隊(duì)列)滿載扰楼,該方法會(huì)返回 NO呀癣,當(dāng)傳輸隊(duì)列存在更多空間時(shí),Peripheral Manager 則會(huì)調(diào)用 peripheralManagerIsReadyToUpdateSubscribers:
代理方法進(jìn)行回調(diào)弦赖。你可以實(shí)現(xiàn)這個(gè)代理方法,在方法中再次調(diào)用 updateValue:forCharacteristic:onSubscribedCentrals:
方法發(fā)送通知給訂閱的 Central浦辨。
提示:用通知發(fā)送單個(gè)數(shù)據(jù)包給訂閱的 Central蹬竖,就是說(shuō),一旦訂閱的 Central 發(fā)行更新時(shí)流酬,你就應(yīng)該調(diào)用 updateValue:forCharacteristic:onSubscribedCentrals:
方法用單一通知發(fā)送全部的更新值币厕。
并不是所有的數(shù)據(jù)都是通過(guò)通知來(lái)傳輸?shù)模@主要取決于你的 Characteristic 的值的大小芽腾,只有當(dāng) Central 調(diào)用
CBPeripheral
類的 readValueForCharacteristic:
方法時(shí)旦装,你可以檢索全部的值。
參考文獻(xiàn)
1摊滔、Performing Common Peripheral Role Tasks
結(jié)束語(yǔ)
歡迎在本文下面留言一起交流心得...