本文主要參考如下鏈接:
http://blog.csdn.net/pony_maggie/article/details/26740237
http://blog.csdn.net/VictorMoKai/article/details/42395669
http://blog.csdn.net/m372897500/article/details/50662976
iOS 藍(lán)牙介紹
藍(lán)牙協(xié)議本身經(jīng)歷了從1.0到4.0的升級(jí)演變, 最新的4.0以其低功耗著稱(chēng),所以一般也叫BLE(Bluetoothlow energy)。
iOS 有兩個(gè)框架支持藍(lán)牙與外設(shè)連接。一個(gè)是 ExternalAccessory。從iOS3.0就開(kāi)始支持,也是在iPhone4S出來(lái)之前用的比較多的一種模式,但是它有個(gè)不好的地方,External Accessory需要拿到蘋(píng)果公司的MFI認(rèn)證。
另一個(gè)框架則是本文要介紹的CoreBluetooth,在iphone4s開(kāi)始支持,專(zhuān)門(mén)用于與BLE設(shè)備通訊(因?yàn)樗腁PI都是基于BLE的)。這個(gè)不需要MFI,并且現(xiàn)在很多藍(lán)牙設(shè)備都支持4.0,所以也是在IOS比較推薦的一種開(kāi)發(fā)方法。
CoreBluetooth介紹
CoreBluetooth框架的核心其實(shí)是兩個(gè)東西,Peripheral和Central, 可以理解成外設(shè)和中心。其中Peripheral外設(shè)相當(dāng)于Socket編程中的Server服務(wù)端,Central中心相當(dāng)于Client客戶(hù)端。
你可以理解外設(shè)是一個(gè)廣播數(shù)據(jù)的設(shè)備,它開(kāi)始告訴外面的世界說(shuō)它這兒有一些數(shù)據(jù),并且能提供一些服務(wù)。另一邊中心開(kāi)始掃描周邊有沒(méi)有合適的設(shè)備,如果發(fā)現(xiàn)后,會(huì)和外設(shè)做連接請(qǐng)求,一旦連接確定后,兩個(gè)設(shè)備就可以傳輸數(shù)據(jù)了。
在iOS6之后,iOS 設(shè)備可以是外設(shè),也可以是中心,就像Socket編程中一樣,你可以是服務(wù)端也可以是客戶(hù)端,但與Socket不同的是不能在同時(shí)間扮演兩個(gè)角色。
服務(wù)(service)和特征(characteristic)
每個(gè)藍(lán)牙4.0的設(shè)備都是通過(guò)服務(wù)和特征來(lái)展示自己的,一個(gè)設(shè)備必然包含一個(gè)或多個(gè)服務(wù),每個(gè)服務(wù)下面又包含若干個(gè)特征。特征是與外界交互的最小單位。比如說(shuō),智能音響設(shè)備,用服務(wù)A標(biāo)識(shí)播放模塊,特征A1來(lái)表示播放上一首,特征A2來(lái)表示播放下一首;服務(wù)B標(biāo)識(shí)設(shè)置模塊,特征B1設(shè)置彩燈顏色。這樣做的目的主要為了模塊化。
服務(wù)和特征都是用UUID來(lái)唯一標(biāo)識(shí)的,UUID的概念如果不清楚請(qǐng)自行g(shù)oogle,國(guó)際藍(lán)牙組織為一些很典型的設(shè)備(比如測(cè)量心跳和血壓的設(shè)備)規(guī)定了標(biāo)準(zhǔn)的service UUID。
CoreBluetooth框架的核心其實(shí)是兩個(gè)東西,peripheral和central, 可以理解成外設(shè)和中心。對(duì)應(yīng)他們分別有一組相關(guān)的API和類(lèi),如下圖所示:
上面說(shuō)了設(shè)備可以是外設(shè),也可以是中心,也就是 本地中心->遠(yuǎn)程外設(shè) 、本地外設(shè)->遠(yuǎn)程中心,
不過(guò)在智能家居開(kāi)發(fā)中,大部分硬件藍(lán)牙都是擔(dān)任外設(shè)的角色,也就是說(shuō)我們應(yīng)用只要扮演中心即可了。
本地中心與遠(yuǎn)程外設(shè)
在中心這邊,由CBCentralManager對(duì)象管理本地中心,來(lái)發(fā)現(xiàn)或連接遠(yuǎn)程外設(shè)。此時(shí)
正在連接的外設(shè)用CBPeripheral 對(duì)象表示。
遠(yuǎn)程外設(shè)的數(shù)據(jù)由CBService和CBCharacteristic對(duì)象表示
當(dāng)你與遠(yuǎn)程外設(shè)CBPeripheral對(duì)象進(jìn)行數(shù)據(jù)交互時(shí),是由一個(gè)服務(wù)與特征操作的。
實(shí)現(xiàn)細(xì)節(jié)
作為一個(gè)中心要實(shí)現(xiàn)完整的通訊,一般要經(jīng)過(guò)這樣幾個(gè)步驟:
- 建立中心角色
- 掃描外設(shè)(discover)
- 連接外設(shè)(connect)
- 掃描外設(shè)中的服務(wù)和特征(discover)
-獲取外設(shè)的services
-獲取外設(shè)的Characteristics,獲取Characteristics的值,獲取Characteristics的Descriptor和Descriptor的值 - 與外設(shè)做數(shù)據(jù)交互(explore and interact)
- 訂閱Characteristic的通知
- 斷開(kāi)連接(disconnect)
1.導(dǎo)入CoreBluetooth頭文件,建立主設(shè)備管理類(lèi),設(shè)置主設(shè)備委托
首先在我自己類(lèi)的頭文件中要包含CoreBluetooth的頭文件,并繼承兩個(gè)協(xié)議<CBCentralManagerDelegate,CBPeripheralDelegate>,代碼如下:
#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController (){
//系統(tǒng)藍(lán)牙設(shè)備管理對(duì)象,可以把他理解為主設(shè)備,通過(guò)他,可以去掃描和鏈接外設(shè)
CBCentralManager *manager;
}
- (void)viewDidLoad {
[super viewDidLoad];
/*
設(shè)置主設(shè)備的委托,CBCentralManagerDelegate
必須實(shí)現(xiàn)的:
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;//主設(shè)備狀態(tài)改變的委托,在初始化CBCentralManager的適合會(huì)打開(kāi)設(shè)備,只有當(dāng)設(shè)備正確打開(kāi)后才能使用
其他選擇實(shí)現(xiàn)的委托中比較重要的:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; //找到外設(shè)的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設(shè)成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設(shè)連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開(kāi)外設(shè)的委托
*/
//初始化并設(shè)置委托和線(xiàn)程隊(duì)列,最好一個(gè)線(xiàn)程的參數(shù)可以為nil,默認(rèn)會(huì)就main線(xiàn)程
manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
}
2. 掃描外設(shè)(discover),掃描外設(shè)的方法我們放在centralManager成功打開(kāi)的委托中,因?yàn)橹挥性O(shè)備成功打開(kāi),才能開(kāi)始掃描,否則會(huì)報(bào)錯(cuò)。
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state){
case CBCentralManagerStateUnknown:
NSLog(@">>>CBCentralManagerStateUnknown");
_statusLabel.text = @"CBCentralManagerStateUnknown";
break;
case CBCentralManagerStateResetting:
NSLog(@">>>CBCentralManagerStateResetting");
_statusLabel.text = @"CBCentralManagerStateResetting";
break;
case CBCentralManagerStateUnsupported:
NSLog(@">>>CBCentralManagerStateUnsupported");
_statusLabel.text = @"CBCentralManagerStateUnsupported";
break;
case CBCentralManagerStateUnauthorized:
NSLog(@">>>CBCentralManagerStateUnauthorized");
_statusLabel.text = @"CBCentralManagerStateUnauthorized";
break;
case CBCentralManagerStatePoweredOff:
NSLog(@">>>CBCentralManagerStatePoweredOff");
_statusLabel.text = @"CBCentralManagerStatePoweredOff";
break;
case CBCentralManagerStatePoweredOn:
{
NSLog(@">>>CBCentralManagerStatePoweredOn");
_statusLabel.text = @"CBCentralManagerStatePoweredOn";
//開(kāi)始掃描周?chē)耐庠O(shè)
/*第一個(gè)參數(shù)nil就是掃描周?chē)械耐庠O(shè),掃描到外設(shè)后會(huì)進(jìn)入
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
*/
NSDictionary * dic = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:false],CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[manager scanForPeripheralsWithServices:nil options:dic];
}
break;
default:
break;
}
}
- (BOOL)addBtPeripheralToArray:(CBPeripheral*)peripheral{
if (![_btPeripheralsArray containsObject:peripheral] && peripheral.name.length > 0) {
for (CBPeripheral* existPeripheral in _btPeripheralsArray) {
if ([existPeripheral.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]) {
return NO;
}
}
[_btPeripheralsArray addObject:peripheral];
return YES;
}
return NO;
}
//掃描到設(shè)備會(huì)進(jìn)入方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"掃描到設(shè)備:%@",peripheral.name);
if ([self addBtPeripheralToArray:peripheral]) {
[_btListView reloadData];
}
}
3. 連接外設(shè)(connect)
在剛剛存入數(shù)組的外設(shè)中選擇一個(gè)外設(shè),然后調(diào)用tryToConnectPeripheral方法
- (void)tryToConnectPeripheral:(CBPeripheral*)peripheral{
//接下來(lái)可以連接設(shè)備
//接下連接我們的測(cè)試設(shè)備,如果你沒(méi)有設(shè)備,可以下載一個(gè)app叫l(wèi)ightbule的app去模擬一個(gè)設(shè)備
/*
一個(gè)主設(shè)備最多能連7個(gè)外設(shè),每個(gè)外設(shè)最多只能給一個(gè)主設(shè)備連接,連接成功,失敗,斷開(kāi)會(huì)進(jìn)入各自的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設(shè)成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設(shè)連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開(kāi)外設(shè)的委托
*/
//連接設(shè)備
_statusLabel.text = @"連接中。。。";
[manager connectPeripheral:peripheral options:nil];
}
嘗試連接后,CoreBluetooth會(huì)把連接狀態(tài)通過(guò)代理發(fā)送給調(diào)用者
//連接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@">>>連接到名稱(chēng)為(%@)的設(shè)備-成功",peripheral.name);
//設(shè)置的peripheral委托CBPeripheralDelegate
//@interface ViewController : UIViewController
[peripheral setDelegate:self];
//掃描外設(shè)Services,成功后會(huì)進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
[peripheral discoverServices:nil];
}
//連接到Peripherals-失敗
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@">>>連接到名稱(chēng)為(%@)的設(shè)備-失敗,原因:%@",[peripheral name],[error localizedDescription]);
}
//Peripherals斷開(kāi)連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@">>>外設(shè)連接斷開(kāi)連接 %@: %@\n", [peripheral name], [error localizedDescription]);
}
4. 掃描外設(shè)中的服務(wù)和特征(discover)
設(shè)備連接成功后,就可以?huà)呙柙O(shè)備的服務(wù)了,同樣是通過(guò)委托形式,掃描到結(jié)果后會(huì)進(jìn)入委托方法。
但是這個(gè)委托已經(jīng)不再是主設(shè)備的委托(CBCentralManagerDelegate),而是外設(shè)的委托(CBPeripheralDelegate),這個(gè)委托包含了主設(shè)備與外設(shè)交互的許多回調(diào)方法,包括獲取services,獲取characteristics,獲取characteristics的值,獲取characteristics的Descriptor,和Descriptor的值,寫(xiě)數(shù)據(jù),讀rssi,用通知的方式訂閱數(shù)據(jù)等等。
4.1 獲取外設(shè)的services
//掃描到Services
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
NSLog(@">>>掃描到服務(wù):%@",peripheral.services);
NSLog(@"***begin<%s>***",__func__);
if (error)
{
NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
return;
}
for (CBService *service in peripheral.services) {
NSLog(@"UUID: %@",service.UUID);
//掃描每個(gè)service的Characteristics,掃描到后會(huì)進(jìn)入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
[peripheral discoverCharacteristics:nil forService:service];
}
NSLog(@"***end***");
}
4.2 獲取外設(shè)的Characteristics,獲取Characteristics的值,獲取Characteristics的Descriptor和Descriptor的值
//掃描到Characteristics
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error)
{
NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
return;
}
//獲取Characteristic的值,讀到數(shù)據(jù)會(huì)進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
for (CBCharacteristic *characteristic in service.characteristics){
{
[peripheral readValueForCharacteristic:characteristic];
}
}
//搜索Characteristic的Descriptors,讀到數(shù)據(jù)會(huì)進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
for (CBCharacteristic *characteristic in service.characteristics){
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
//獲取的charateristic的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//打印出characteristic的UUID和值
//!注意,value的類(lèi)型是NSData,具體開(kāi)發(fā)時(shí),會(huì)根據(jù)外設(shè)協(xié)議制定的方式去解析數(shù)據(jù)
NSLog(@"characteristic uuid:%@ value:%@",characteristic.UUID,characteristic.value);
}
//搜索到Characteristic的Descriptors
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//打印出Characteristic和他的Descriptors
NSLog(@"characteristic uuid:%@",characteristic.UUID);
for (CBDescriptor *d in characteristic.descriptors) {
NSLog(@"Descriptor uuid:%@",d.UUID);
}
}
//獲取到Descriptors的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
//打印出DescriptorsUUID 和value
//這個(gè)descriptor都是對(duì)于characteristic的描述,一般都是字符串,所以這里我們轉(zhuǎn)換成字符串去解析
NSLog(@"characteristic uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}
5. 把數(shù)據(jù)寫(xiě)到Characteristic中
//寫(xiě)數(shù)據(jù)
-(void)writeCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic
value:(NSData *)value{
//打印出 characteristic 的權(quán)限,可以看到有很多種,這是一個(gè)NS_OPTIONS,就是可以同時(shí)用于好幾個(gè)值,常見(jiàn)的有read,write,notify,indicate,知知道這幾個(gè)基本就夠用了,前連個(gè)是讀寫(xiě)權(quán)限,后兩個(gè)都是通知,兩種不同的通知方式。
/*
typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
CBCharacteristicPropertyBroadcast = 0x01,
CBCharacteristicPropertyRead = 0x02,
CBCharacteristicPropertyWriteWithoutResponse = 0x04,
CBCharacteristicPropertyWrite = 0x08,
CBCharacteristicPropertyNotify = 0x10,
CBCharacteristicPropertyIndicate = 0x20,
CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,
CBCharacteristicPropertyExtendedProperties = 0x80,
CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100,
CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200
};
*/
NSLog(@"%lu", (unsigned long)characteristic.properties);
//只有 characteristic.properties 有write的權(quán)限才可以寫(xiě)
if(characteristic.properties & CBCharacteristicPropertyWrite){
/*
最好一個(gè)type參數(shù)可以為CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,區(qū)別是是否會(huì)有反饋
*/
[peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}else{
NSLog(@"該字段不可寫(xiě)!");
}
}
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"characteristic uuid:%@ error:%@",characteristic.UUID,error);
}
6 訂閱Characteristic的通知
通常,與藍(lán)牙設(shè)備的交互是這樣進(jìn)行的:
監(jiān)聽(tīng)A特性(訂閱Characteristic的通知)->寫(xiě)B(tài)特性來(lái)請(qǐng)求數(shù)據(jù)->請(qǐng)求的數(shù)據(jù)從A特性返回。
這時(shí),就需要我們訂閱通知,訂閱通知以后,如果有數(shù)據(jù)更新,didUpdateValueForCharacteristic就會(huì)被調(diào)用
//設(shè)置通知
-(void)notifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
//設(shè)置通知,數(shù)據(jù)通知會(huì)進(jìn)入:didUpdateValueForCharacteristic方法
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
//取消通知
-(void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
}
7 斷開(kāi)連接(disconnect)
//停止掃描并斷開(kāi)連接
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
peripheral:(CBPeripheral *)peripheral{
//停止掃描
[centralManager stopScan];
//斷開(kāi)連接
[centralManager cancelPeripheralConnection:peripheral];
}