在iOS開發(fā)中榄审,實現(xiàn)藍牙通信有兩種方式链患,一種是使用傳統(tǒng)的GameKit.framework,另一種就是使用在iOS 5中加入的CoreBluetooth.framework蛹批。
利用CoreBluetooth框架仰泻,我們可以輕松實現(xiàn)兩個iOS設(shè)備荆陆、iOS設(shè)備與非iOS藍牙設(shè)備的交互。要注意的一點是目前這個框架只能支持藍牙4.0BLE標準集侯,所以對硬件上是有一定要求的被啼,iPhone 4S及以后的設(shè)備,第三代iPad及以后的設(shè)備是支持這一標準的棠枉。
術(shù)語解釋
我們首先看一下CoreBluetooth框架的結(jié)構(gòu)浓体。這是CoreBluetooth/CoreBluetooth.h文件中的聲明。
#ifndef?_CORE_BLUETOOTH_H_
#define?_CORE_BLUETOOTH_H_
#endif
#import?#import?#import?#import?#import?#import?#import?#import?#import?#import?#import
其中辈讶,CBCentral開頭的都是中心設(shè)備類命浴,CBPeripheral開頭的都是外設(shè)類。這就要講到藍牙設(shè)備的兩個角色了荞估。
中心設(shè)備:中心設(shè)備可以理解為是處理數(shù)據(jù)的iOS設(shè)備,比如你的 iPhone稚新、iPad 等勘伺。
外設(shè):外設(shè)顧名思義,就是產(chǎn)生數(shù)據(jù)的外部設(shè)備褂删。這個外部設(shè)備可以是單片機飞醉、嵌入式設(shè)備甚至是另一個iOS設(shè)備等等。外設(shè)可以通過其傳感器等產(chǎn)生有用數(shù)據(jù)屯阀,數(shù)據(jù)后通過藍牙傳給中心設(shè)備使用缅帘。
在建立連接的之前,外設(shè)向外發(fā)出廣播數(shù)據(jù)(advertisementData难衰,官方描述“A dictionary containing any advertisement and scan response data.”)钦无,廣播數(shù)據(jù)是一個字典類數(shù)據(jù),中心設(shè)備可以獲取一定范圍內(nèi)的外設(shè)發(fā)出的廣播數(shù)據(jù)盖袭。
現(xiàn)在開始
初始化
為了使用CoreBluetooth框架中的回調(diào)方法失暂,我們要使用CBCentralManagerDelegate、CBPeripheralDelegate這兩個協(xié)議鳄虱。
我們先要初始化一個CBCentralManager類的對象弟塞。代碼如下:
@interface?SKBluetoothManager?:?NSObject{
CBCentralManager?*manager;
id?delegate;
}
manager?=?[[CBCentralManager?alloc]?initWithDelegate:self?queue:nil];
manager.delegate?=?self;
掃描外設(shè)
初始化完成,我們就可以讓管理器開始掃描外設(shè)了:
[manager?scanForPeripheralsWithServices:nil?options:nil];
在設(shè)備發(fā)現(xiàn)周圍外設(shè)的advertisementData后拙已,會回調(diào)這個方法:
-?(void)centralManager:(CBCentralManager?*)central?didDiscoverPeripheral:(CBPeripheral?*)peripheral?advertisementData:(NSDictionary?*)advertisementData?RSSI:(NSNumber?*)RSSI;
其中參數(shù)central指回調(diào)這個方法的中心設(shè)備决记,peripheral指發(fā)現(xiàn)的外設(shè)對象CBPeripheral,advertisementData就是前面說的字典類型廣播數(shù)據(jù)倍踪,RSSI是當前外設(shè)的信號強度系宫,單位是dbm索昂。
剛開始對陌生的藍牙設(shè)備調(diào)試時,建議我們先用一個數(shù)組NSArray將所掃描到的外設(shè)進行保存:
[_peripherals?addObject:peripheral];
連接外設(shè)
保存后笙瑟,可根據(jù)設(shè)備的UUID來確定該設(shè)備是否為我們需要進行操作的藍牙設(shè)備楼镐,在確認外設(shè)身份后,即可發(fā)起對外設(shè)的連接操作:
if?([peripheral.identifier.UUIDString?isEqualToString:kPeripheralUUID])?{
[manager?stopScan];
[manager?connectPeripheral:peripheral?options:nil];
NSLog(@"連接外設(shè):%@",peripheral.description);
self.peripheral?=?peripheral;
}
在此步操作后往枷,我們完成了對藍牙設(shè)備的掃描工作框产,接下來的回調(diào)方法分為兩種情況:
連接到外設(shè)后
-?(void)centralManager:(CBCentralManager?*)central?didConnectPeripheral:(CBPeripheral?*)peripheral{
NSLog(@"已經(jīng)連接到:%@",?peripheral.description);
peripheral.delegate?=?self;
[central?stopScan];
[peripheral?discoverServices:nil];
}
一旦連接好外設(shè),我們就可以馬上停止掃描错洁。然后發(fā)起對服務的搜索:
-?(void)discoverServices:(NSArray?*)serviceUUIDs;
此處參數(shù)位需要掃描的服務的UUID的數(shù)組秉宿。文檔中特別提到,若該參數(shù)為nil屯碴,將會掃描所有的服務描睦。
連接失敗后
在連接外設(shè)失敗的回調(diào)方法中,提供了error參數(shù)导而,可根據(jù)實際需要來做異常處理忱叭,在此不做過多說明
-?(void)centralManager:(CBCentralManager?*)central?didFailToConnectPeripheral:(CBPeripheral?*)peripheral?error:(NSError?*)error?{
NSLog(@"連接%@失敗",peripheral);
}
在搜索到藍牙設(shè)備的服務后,將會回調(diào)
-?(void)peripheral:(CBPeripheral?*)peripheral?didDiscoverServices:(NSError?*)error
若有錯誤發(fā)生今艺,通過NSError異常處理韵丑。
掃描服務
由于服務在peripheral里是以NSArray的形式存在的,所以我們要對peripheral中的所有服務進行遍歷:
for?(CBService?*service?in?peripheral.services)?{
//發(fā)現(xiàn)服務
if?([service.UUID?isEqual:[CBUUID?UUIDWithString:kServiceUUID]])?{
NSLog(@"發(fā)現(xiàn)服務:%@",?service.UUID);
[peripheral?discoverCharacteristics:nil?forService:service];
break;
}
}
掃描特征值
在遍歷中虚缎,趁熱打鐵撵彻,直接對其特征值進行掃描,
[peripheral?discoverCharacteristics:nil?forService:service];
這里與掃描service是相同的实牡,若掃描所有的特征值陌僵,直接傳入nil作為參數(shù)即可。
這里的kServiceUUID是我們根據(jù)藍牙設(shè)備的具體情況创坞,提前設(shè)置好的UUID常量碗短。本文所有kXxxxxUUID均為預設(shè)的UUID常量。
同樣的题涨,characteristics也是一個數(shù)組豪椿,我們利用像遍歷services一樣的方式來遍歷所有的特征值。
-?(void)peripheral:(CBPeripheral?*)peripheral?didDiscoverCharacteristicsForService:(CBService?*)service?error:(NSError?*)error?{
if?(error)?{
NSLog(@"搜索特征%@時發(fā)生錯誤:%@",?service.UUID,?[error?localizedDescription]);
return;
}
NSLog(@"服務:%@",service.UUID);
for?(CBCharacteristic?*characteristic?in?service.characteristics)?{
//????????NSLog(@"特征:%@",characteristic);
//發(fā)現(xiàn)特征
if?([characteristic.UUID?isEqual:[CBUUID?UUIDWithString:kCharacteristicWriteUUID]])?{
_writeCharacteristic?=?characteristic;
}
if?([characteristic.UUID?isEqual:[CBUUID?UUIDWithString:kCharacteristicNotifyUUID]])?{
NSLog(@"監(jiān)聽特征:%@",characteristic);//監(jiān)聽特征
[self.peripheral?setNotifyValue:YES?forCharacteristic:characteristic];
_isConnected?=?YES;
}
}
}
特別要提到的是携栋,我們不同的藍牙設(shè)備有不同的服務和特征值搭盾。我的藍牙模塊的說明文檔中已經(jīng)說清楚了,write特征婉支、read特征鸯隅、notify特征,所以在此根據(jù)自身需要,來對不同的特征值進行操作蝌以。
設(shè)置監(jiān)聽
我在此要解釋一下炕舵,當我們試圖去讀取藍牙外設(shè)發(fā)過來的數(shù)據(jù)時,一定要找準特征值跟畅,用這個方法進行訂閱咽筋。每次特征值變化的時候,就會有回調(diào)方法執(zhí)行徊件,從而達到讀取數(shù)據(jù)的目的奸攻。容易出錯誤,一定分清楚到底哪個特征值該被訂閱虱痕。
在訂閱了特征值后睹耐,我們嘗試用藍牙外設(shè)發(fā)送一些數(shù)據(jù)出來,即可回調(diào)下一階段的方法:
-?(void)peripheral:(CBPeripheral?*)peripheral?didUpdateValueForCharacteristic:(CBCharacteristic?*)characteristic?error:(NSError?*)error?{
if?(error)?{
NSLog(@"更新特征值%@時發(fā)生錯誤:%@",?characteristic.UUID,?[error?localizedDescription]);
return;
}
//?收到數(shù)據(jù)
[delegate?didGetDataForString:[self?hexadecimalString:characteristic.value]];
//????NSLog(@"%@",[self?hexadecimalString:characteristic.value]);
}
數(shù)據(jù)的轉(zhuǎn)換
我們接收到的數(shù)據(jù)部翘,正是characteristic.value硝训,這是一個NSData類數(shù)據(jù),我們可以通過UTF8StringEncoding來轉(zhuǎn)化為NSString新思,為了代碼結(jié)構(gòu)清晰窖梁,我專門把NSData和NSString互轉(zhuǎn)寫成了兩個方法:
//將傳入的NSData類型轉(zhuǎn)換成NSString并返回
-?(NSString*)hexadecimalString:(NSData?*)data{
NSString?*result?=?[[NSString?alloc]?initWithData:data?encoding:NSUTF8StringEncoding];
return?result;
}
//將傳入的NSString類型轉(zhuǎn)換成NSData并返回
-?(NSData*)dataWithHexstring:(NSString?*)hexstring{
NSData?*aData;
return?aData?=?[hexstring?dataUsingEncoding:?NSASCIIStringEncoding];
}
在拿到字符串后,通過各種回調(diào)方法夹囚,處理UI變動纵刘。
結(jié)語
整個藍牙開發(fā)實現(xiàn)方便,但回調(diào)方法非常多崔兴,新手容易暈頭轉(zhuǎn)向彰导。按部就班把每個回調(diào)方法實現(xiàn)蛔翅,即可保證藍牙開發(fā)的順利進行敲茄。