執(zhí)行常規(guī)的中心角色(Central Role)任務(wù)
在低功耗藍牙(以下簡稱“藍牙”)通信中,充當中心角色的設(shè)備執(zhí)行著幾個常規(guī)任務(wù)——例如,發(fā)現(xiàn)和鏈接有效的外部設(shè)備(以下簡稱“外設(shè)”),探測和交互外設(shè)提供的數(shù)據(jù)。充當外設(shè)角色的設(shè)備也執(zhí)行著常規(guī)但是不同的任務(wù)——例如,發(fā)布和廣告服務(wù),響應(yīng)來自連接中心的讀,寫和訂閱請求。
在這一章中,你將學(xué)會如何使用Core Bluetooth框架去執(zhí)行藍牙任務(wù)的中心部分的大多數(shù)常規(guī)任務(wù)。伴隨基于代碼的例子將幫你開發(fā)應(yīng)用,以實現(xiàn)在你的本地設(shè)備上實現(xiàn)中心角色。主要的,你會學(xué)到:
- 開啟一個中心管理器對象
- 發(fā)現(xiàn)和鏈接正在廣告的外設(shè)
- 連接上外設(shè)之后,探測它上面的數(shù)據(jù)
- 向外設(shè)服務(wù)中特性的值發(fā)送讀寫請求
- 訂閱特性的值,以察覺它的更新
下一章,你學(xué)會如何在你的本地設(shè)備上開發(fā)實現(xiàn)了外設(shè)角色的應(yīng)用。
你在本章中找到的代碼例子是簡化和抽象的;你也許需要適當修改才能將它包含到你現(xiàn)實世界的應(yīng)用中。更多相于實現(xiàn)中心角色高級主題——包括建議,技巧和最佳實踐——包含在以后的章節(jié)中,Background Processing for iOS Apps 和 Best Practices for Interacting with a Remote Peripheral Device。
開啟中心管理器(Central Manager)
由于CBCentralManager對象是本地中心設(shè)備的Core Bluetooth面向?qū)ο蟮拇?,在你開始執(zhí)行任何藍牙事務(wù)之前你要開辟和初始化一個中心管理器實例。通過調(diào)用中心管理器的initWithDelegate:queue:options:方法初始化它:
myCentralManager =
[[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
在這個例子中, self被設(shè)為代理,以接受各種中心角色的事件。通過指定派發(fā)隊列為nil,中心管理器使用主隊列派發(fā)中心角色事件。
當你創(chuàng)建了一個中心管理器,它會調(diào)用其代理對象的方法centralManagerDidUpdateState:。你必須實現(xiàn)這個代理方法以確保中心設(shè)備藍牙的有效性。更多關(guān)于何實現(xiàn)這個代理方法的信息,請見CBCentralManagerDelegate Protocol Reference。
發(fā)現(xiàn)正在廣告的外設(shè)
一旦完成初始化,中心管理器的第一個任務(wù)是發(fā)現(xiàn)外設(shè)。正如在Centrals Discover and Connect to Peripherals That Are Advertising中提到的,外設(shè)以廣告的方式來被察覺。你的應(yīng)用發(fā)現(xiàn)附近正在廣告的外設(shè)的方式是,調(diào)用中心管理器的scanForPeripheralsWithServices:options:方法:
[myCentralManager scanForPeripheralsWithServices:nil options:nil];
注意:如果你指定
nil為第一個參數(shù),那么中心管理器返回所有發(fā)現(xiàn)的外設(shè),忽視它們所支持的服務(wù)。在真實的應(yīng)用中,你通常指定的是一個包含CBUUID對象的數(shù)組,其中每一個代表外設(shè)正在廣播的服務(wù)的通用唯一識別碼(UUID)。當你指定了服務(wù)UUID的數(shù)組,中心管理器只返回廣播了那些服務(wù)的外設(shè),讓你只掃描到你可能感興趣的設(shè)備。
UUID,以及它們的代表CBUUID,將會在Services and Characteristics Are Identified by UUIDs詳細討論。
每當中心管理器發(fā)現(xiàn)一個外設(shè),它會調(diào)用其代理的方法centralManager:didDiscoverPeripheral:advertisementData:RSSI:。剛被發(fā)現(xiàn)的外設(shè)以一個CBPeripheral對象返回。如果你打算連接它,持有一個對它的強引用,遮掩系統(tǒng)就不會釋放它。以下例子的場景是,用一個類的屬性維持對一個已發(fā)現(xiàn)的外設(shè)的引用:
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSLog(@"Discovered %@", peripheral.name);
self.discoveredPeripheral = peripheral;
...
如果你想連接多個外設(shè),你可以相應(yīng)地持有一個外設(shè)的數(shù)組。任何情況下,一旦你已經(jīng)發(fā)現(xiàn)了所有你想連接的外設(shè),停止對其他設(shè)備的掃描以省電:
[myCentralManager stopScan];
連接你發(fā)現(xiàn)的外設(shè)
發(fā)現(xiàn)你想要的外設(shè)之后,調(diào)用中心管理器的connectPeripheral:options:方法以請求連接到它,傳入你想連接的外設(shè):
[myCentralManager connectPeripheral:peripheral options:nil];
如果連接成功,中心管理器會調(diào)用其代理的centralManager:didConnectPeripheral:方法。在與外設(shè)交互之前,你要設(shè)置它的代理以確保代理能收到準確的回調(diào):
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"Peripheral connected");
peripheral.delegate = self;
...
發(fā)現(xiàn)你連接的設(shè)備中的服務(wù)
同一個外設(shè)建立連接之后,你可以探測它的數(shù)據(jù)。探測數(shù)據(jù)的第一步是發(fā)現(xiàn)它有效的服務(wù)。因為一個外設(shè)可以廣告的數(shù)據(jù)大小是有限的,你可能會發(fā)現(xiàn)一個外設(shè)擁有的服務(wù)總數(shù)超過它所廣告的(在它的廣告包中)。你可以調(diào)用方法discoverServices:來發(fā)現(xiàn)外設(shè)提供的所有服務(wù),像這樣:
[peripheral discoverServices:nil];
注意:在現(xiàn)實的應(yīng)用中,你通常不會傳
nil作為參數(shù),因為這樣做會返回外設(shè)上所有的服務(wù)。因為一個外設(shè)包含的服務(wù)可能比你想要的多,全部發(fā)現(xiàn)它們會浪費電池壽命和時間。相反的,你通常會指定你感興趣的服務(wù)的UUID,像Explore a Peripheral’s Data Wisely中所展示的一樣。
當指定的設(shè)備被發(fā)現(xiàn)了,外設(shè)(我們連接的CBPeripheral對象)就回調(diào)用其代理的peripheral:didDiscoverServices:方法。Core Bluetooth創(chuàng)建了一個CBService數(shù)組——包含著外設(shè)上發(fā)現(xiàn)的服務(wù)。如下所示,你可以實現(xiàn)這個代理方法來以獲取已發(fā)現(xiàn)服務(wù)的數(shù)組。
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error {
for (CBService *service in peripheral.services) {
NSLog(@"Discovered service %@", service);
...
}
...
發(fā)現(xiàn)服務(wù)中的特性
當你發(fā)現(xiàn)了你感興趣的服務(wù),探測外設(shè)的下一步就是要發(fā)現(xiàn)服務(wù)的所有特性。發(fā)現(xiàn)所有的特性只需調(diào)用外設(shè)的discoverCharacteristics:forService:方法,同時傳入合適的服務(wù),像這樣:
NSLog(@"Discovering characteristics for service %@", interestingService);
[peripheral discoverCharacteristics:nil forService:interestingService];
注意:在現(xiàn)實的應(yīng)用中,你通常不會傳
nil作為第一個參數(shù),因為這樣做會返回外設(shè)的服務(wù)中所有的特性。因為一個外設(shè)的服務(wù)所包含的特性可能比你想要的多,全部發(fā)現(xiàn)它們會浪費電池壽命和時間。相反的,你通常會指定你感興趣的特性的UUID。
目標服務(wù)的特性被發(fā)現(xiàn)的時候,外設(shè)就會調(diào)用其代理的peripheral:didDiscoverCharacteristicsForService:error: 方法。Core Bluetooth創(chuàng)建了一個CBCharacteristic數(shù)組——包含了被發(fā)現(xiàn)的特性。以下的例子展示了你如何可以實現(xiàn)這個代理方法來打印被發(fā)現(xiàn)的特性:
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
error:(NSError *)error {
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"Discovered characteristic %@", characteristic);
...
}
...
獲取特性的值
每個特性包含了一個單一的值,描述一個外設(shè)的服務(wù)的信息。例如,一個體溫計服務(wù)的溫度測量值特性可以有一個以攝氏度為單位的溫度值。獲取特性的值可以通過直接讀取或訂閱它。
讀取特性的值
當你已經(jīng)找到你感興趣的特性,要從中讀值可以通過調(diào)用外設(shè)的readValueForCharacteristic:方法,同時傳入適當?shù)奶匦?,像這樣:
NSLog(@"Reading value for characteristic %@", interestingCharacteristic);
[peripheral readValueForCharacteristic:interestingCharacteristic];
當你試圖讀取特性的值,外設(shè)會調(diào)用其代理的peripheral:didUpdateValueForCharacteristic:error:方法來獲取。如果成功獲取,你可以通過特性的value屬性來存取它,像這樣:
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
NSData *data = characteristic.value;
// parse the data as needed
...
注意:并非所有的特性都是可讀的。通過檢查特性的properties屬性是否含有常量CBCharacteristicPropertyRead,你可以知道它是否可讀。如果你試圖讀取一個不可讀的特性的值,代理方法peripheral:didUpdateValueForCharacteristic:error:將返回一個對應(yīng)的錯誤。
訂閱特性的值
雖然使用readValueForCharacteristic:方法可以有效的讀取特性的靜態(tài)值,但這并不是讀取動態(tài)值最有效的方式。通過訂閱的方式獲取隨時變化的特性值——例如,你的心率。當你訂閱了特性的值,每當它發(fā)生變化,你就會收到通知。
訂閱你感興趣的特性的方法是調(diào)用外設(shè)的setNotifyValue:forCharacteristic:方法,第一個參數(shù)指定為YES,像這樣像這樣:
[peripheral setNotifyValue:YES forCharacteristic:interestingCharacteristic];
當你訂閱(或者取消訂閱)一個特性值,外設(shè)會調(diào)用其代理的setNotifyValue:forCharacteristic:,像這樣:
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
if (error) {
NSLog(@"Error changing notification state: %@",
[error localizedDescription]);
}
...
注意:并非所有特性提供訂閱。通過檢查特性的properties屬性是否含有常量CBCharacteristicPropertyNotify或CBCharacteristicPropertyIndicate可以知道它是否提供訂閱。
完成對特性的值的訂閱之后,外設(shè)會通知你的app特性的值的改變。每當只發(fā)生變化,外設(shè)會調(diào)用其代理的peripheral:didUpdateValueForCharacteristic:error:方法。為了獲取更新后的值,你可以按上述Reading the Value of a Characteristic的方法實現(xiàn)。
書寫特性的值
偶爾需要書寫特性值。例如,你的應(yīng)用和一個藍牙數(shù)字恒溫器交互,你可能需要提供恒溫器一個房間的溫度。如果特性的值是可書寫的,你可以使用data(NSData對象)書寫它的值,通過調(diào)用外設(shè)的writeValue:forCharacteristic:type:方法,像這樣:
NSLog(@"Writing value for characteristic %@", interestingCharacteristic);
[peripheral writeValue:dataToWrite forCharacteristic:interestingCharacteristic
type:CBCharacteristicWriteWithResponse];
書寫特性的值,你要指定你執(zhí)行書寫的類型。上面的例子中,書寫的類型是CBCharacteristicWriteWithResponse,它讓外設(shè)通知你的應(yīng)用書寫是否成功,通過調(diào)用其代理的peripheral:didWriteValueForCharacteristic:error:方法。你要實現(xiàn)這個代理方法來處理錯誤的情況,如下所示:
- (void)peripheral:(CBPeripheral *)peripheral
didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
if (error) {
NSLog(@"Error writing characteristic value: %@",
[error localizedDescription]);
}
...
如果相反你指定的書寫類型是CBCharacteristicWriteWithoutResponse,那么書寫操作以盡力而為的方式執(zhí)行,而且交付過程既沒有保證也沒有報告。外設(shè)不會回調(diào)代理方法。更多關(guān)于Core Bluetooth框架支持的書寫類型的信息,參考CBPeripheral Class Reference中的枚舉BCharacteristicWriteType。
注意:一些特性可能只支持某幾種書寫類型,甚至完全不支持。查看特性的書寫類型,要通過檢查它的properties屬性是否具有CBCharacteristicPropertyWriteWithoutResponse或者CBCharacteristicPropertyWrite常量。