iOS端的藍(lán)牙開發(fā),大部分都是采用中心模式,故此文章就詳細(xì)說說iOS連接外設(shè)的代碼實(shí)現(xiàn)(看了很多藍(lán)牙相關(guān)的文章,某些點(diǎn)都不是講的特別詳細(xì),故此文的目的是讓啥都不懂的小白都搞懂iOS藍(lán)牙中心模式的實(shí)現(xiàn))。請配合Demo閱讀(BleDemo工程下面的BeCentralVewController類)。
中心模式的代碼實(shí)現(xiàn)流程
1. 建立中心角色
2. 檢測中央設(shè)備狀態(tài)(CBCentralManagerDelegate的required方法 【代理方法】
3. 發(fā)起掃描外設(shè)(scanForPeripheral)
4. 發(fā)現(xiàn)外設(shè)(didDiscoverPeripheral)【代理方法】
5. 發(fā)起連接外設(shè)(connectPeripheral)
6. 連接外設(shè)成功(didConnectPeripheral)【代理方法】
7. 設(shè)置外設(shè)代理,發(fā)起掃描服務(wù)(setDelegate, discoverServices)
8. 發(fā)現(xiàn)外設(shè)的服務(wù)(didDiscoverServices) 【代理方法】
9. 發(fā)起發(fā)現(xiàn)外設(shè)的服務(wù)的特征(discoverCharacteristics: forService)
10. 發(fā)現(xiàn)外設(shè)的服務(wù)的特征(discoverCharacteristicsForService)【代理方法】
11. 發(fā)起讀取特征值(readValueForCharacteristic)
12. 發(fā)現(xiàn)特征值(didUpdateValueForCharacteristic) 【代理方法】
13. 發(fā)起寫入特征值(writeValue: forCharacteristic:)
14. 寫入特征值成功(didWriteValueForCharacteristic) 【代理方法】
15. 發(fā)起設(shè)置或取消某個(gè)特征的監(jiān)聽通知(setNotifyValue: forCharacteristic:)
16. 監(jiān)聽某個(gè)特征的通知的狀態(tài)變更 (didUpdateNotificationStateForCharacteristic) 【代理方法】
以下為可選
17. 發(fā)起發(fā)現(xiàn)特征的描述
18. 發(fā)現(xiàn)特征的描述 【代理方法】
19. 發(fā)起讀取特征的描述值
20. 更新特征的描述值【代理方法】
21. 發(fā)起寫入特征的描述值
22. 寫入特征的描述值【代理方法】
23. 斷開藍(lán)牙連接
首先說下這個(gè)流程的規(guī)律,一般都是一個(gè)方法加一個(gè)代理方法的模式,前面12步都是一環(huán)套一環(huán),流程中以"發(fā)起"開頭的都是主動(dòng)調(diào)用方法,目的是為了區(qū)別代理方法。
1. 建立中心角色
#import "BeCentralVewController.h"
#import <CoreBluetooth/CoreBluetooth.h>
@interface BeCentralVewController () <CBCentralManagerDelegate,CBPeripheralDelegate>{
//系統(tǒng)藍(lán)牙設(shè)備管理對象,可以把他理解為主設(shè)備,通過他,可以去掃描和鏈接外設(shè)
CBCentralManager *_manager;
//用于保存被發(fā)現(xiàn)設(shè)備
NSMutableArray *_discoverPeripherals;
}
@end
@implementation BeCentralVewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
/*
設(shè)置主設(shè)備的委托,CBCentralManagerDelegate
必須實(shí)現(xiàn)的:
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;//主設(shè)備狀態(tài)改變的委托,在初始化CBCentralManager的適合會打開設(shè)備,只有當(dāng)設(shè)備正確打開后才能使用
其他選擇實(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;//斷開外設(shè)的委托
*/
//初始化并設(shè)置委托和線程隊(duì)列,最好一個(gè)線程的參數(shù)可以為nil,默認(rèn)會就main線程
// 1. 建立中心角色
_manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
//持有發(fā)現(xiàn)的設(shè)備,如果不持有設(shè)備會導(dǎo)致CBPeripheralDelegate方法不能正確回調(diào)
_discoverPeripherals = [[NSMutableArray alloc]init];
}
2. 檢測中央設(shè)備狀態(tài)(CBCentralManagerDelegate的required方法) 【代理方法】 3. 發(fā)起掃描外設(shè)(scanForPeripheral)
// 2. 檢測中央設(shè)備狀態(tài)(CBCentralManagerDelegate的required方法) 【代理方法】
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBCentralManagerStateUnknown:
NSLog(@">>>CBCentralManagerStateUnknown");
break;
case CBCentralManagerStateResetting:
NSLog(@">>>CBCentralManagerStateResetting");
break;
case CBCentralManagerStateUnsupported:
NSLog(@">>>CBCentralManagerStateUnsupported");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@">>>CBCentralManagerStateUnauthorized");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@">>>CBCentralManagerStatePoweredOff");
break;
case CBCentralManagerStatePoweredOn:
NSLog(@">>>CBCentralManagerStatePoweredOn");
//開始掃描周圍的外設(shè)
/*
第一個(gè)參數(shù)nil就是掃描周圍所有的外設(shè),掃描到外設(shè)后會進(jìn)入
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
*/
// 3. 發(fā)起掃描外設(shè)(scanForPeripheral)
[central scanForPeripheralsWithServices:nil options:nil];
break;
default:
break;
}
}
4. 發(fā)現(xiàn)外設(shè)(didDiscoverPeripheral)【代理方法】 5. 發(fā)起連接外設(shè)(connectPeripheral)
// 4. 發(fā)現(xiàn)外設(shè)(didDiscoverPeripheral)【代理方法】
//掃描到設(shè)備會進(jìn)入方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"當(dāng)掃描到設(shè)備:%@",peripheral.name);
//接下連接我們的測試設(shè)備,如果你沒有設(shè)備,可以下載一個(gè)app叫l(wèi)ightbule的app去模擬一個(gè)設(shè)備
//這里自己去設(shè)置下連接規(guī)則,我設(shè)置的是P開頭的設(shè)備
// if ([peripheral.name hasPrefix:@"P"]){
/*
一個(gè)主設(shè)備最多能連7個(gè)外設(shè),每個(gè)外設(shè)最多只能給一個(gè)主設(shè)備連接,連接成功,失敗,斷開會進(jìn)入各自的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設(shè)成功的委托
- (void)centra`lManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設(shè)連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設(shè)的委托
*/
//找到的設(shè)備必須持有它,否則CBCentralManager中也不會保存peripheral,那么CBPeripheralDelegate中的方法也不會被調(diào)用??!
[_discoverPeripherals addObject:peripheral];
// 5. 發(fā)起連接外設(shè)(connectPeripheral)
[central connectPeripheral:peripheral options:nil];
// }
}
6. 連接外設(shè)成功(didConnectPeripheral)【代理方法】 7. 設(shè)置外設(shè)代理,發(fā)起掃描服務(wù)(setDelegate, discoverServices)
//連接到Peripherals-成功
// 6. 連接外設(shè)成功(didConnectPeripheral)【代理方法】
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@">>>連接到名稱為(%@)的設(shè)備-成功",peripheral.name);
//設(shè)置的peripheral委托CBPeripheralDelegate
//@interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
[peripheral setDelegate:self];
//掃描外設(shè)Services,成功后會進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
// 7. 設(shè)置外設(shè)代理,發(fā)起掃描服務(wù)(setDelegate, discoverServices)
[peripheral discoverServices:nil];
}
8. 發(fā)現(xiàn)外設(shè)的服務(wù)(didDiscoverServices) 【代理方法】 9. 發(fā)起發(fā)現(xiàn)外設(shè)的服務(wù)的特征(discoverCharacteristics: forService)
//掃描到Services
// 8. 發(fā)現(xiàn)外設(shè)的服務(wù)(didDiscoverServices) 【代理方法】
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
// NSLog(@">>>掃描到服務(wù):%@",peripheral.services);
if (error)
{
NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
return;
}
for (CBService *service in peripheral.services) {
NSLog(@"%@",service.UUID);
//掃描每個(gè)service的Characteristics,掃描到后會進(jìn)入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
// 9. 發(fā)起發(fā)現(xiàn)外設(shè)的服務(wù)的特征(discoverCharacteristics: forService)
[peripheral discoverCharacteristics:nil forService:service];
}
}
10. 發(fā)現(xiàn)外設(shè)的服務(wù)的特征(discoverCharacteristicsForService)【代理方法】 11. 發(fā)起讀取特征值(readValueForCharacteristic)
//掃描到Characteristics
// 10. 發(fā)現(xiàn)外設(shè)的服務(wù)的特征(discoverCharacteristicsForService)【代理方法】 11. 發(fā)起讀取特征值(readValueForCharacteristic)
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error)
{
NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics)
{
NSLog(@"service:%@ 的 Characteristic: %@",service.UUID,characteristic.UUID);
}
for (CBCharacteristic *characteristic in service.characteristics){
{
if (characteristic.properties & CBCharacteristicPropertyRead) {
//獲取Characteristic的值,讀到數(shù)據(jù)會進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
// 11. 發(fā)起讀取特征值(readValueForCharacteristic)
[peripheral readValueForCharacteristic:characteristic];
}
}
}
//搜索Characteristic的Descriptors,讀到數(shù)據(jù)會進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
// 17. 發(fā)起發(fā)現(xiàn)特征的描述
for (CBCharacteristic *characteristic in service.characteristics){
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
12. 發(fā)現(xiàn)特征值(didUpdateValueForCharacteristic) 【代理方法】
//獲取的charateristic的值
// 12. 發(fā)現(xiàn)特征值(didUpdateValueForCharacteristic) 【代理方法】
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//打印出characteristic的UUID和值
//!注意,value的類型是NSData,具體開發(fā)時(shí),會根據(jù)外設(shè)協(xié)議制定的方式去解析數(shù)據(jù)
NSLog(@"characteristic uuid:%@ value:%@",characteristic.UUID,characteristic.value);
}
讀取外設(shè)的特征值到第12步已經(jīng)結(jié)束了。下面說說寫特征和通知以及特征描述的內(nèi)容。
13. 發(fā)起寫入特征值(writeValue: forCharacteristic:)
//寫數(shù)據(jù)
-(void)writeCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic
value:(NSData *)value{
//打印出 characteristic 的權(quán)限,可以看到有很多種,這是一個(gè)NS_OPTIONS,就是可以同時(shí)用于好幾個(gè)值,常見的有read,write,notify,indicate,知道這幾個(gè)基本就夠用了,前兩個(gè)是讀寫權(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)限才可以寫
if(characteristic.properties & CBCharacteristicPropertyWrite){
/*
最好一個(gè)type參數(shù)可以為CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithoutResponse,區(qū)別是是否會有反饋
*/
// 13. 發(fā)起寫入特征值(writeValue: forCharacteristic:)
[peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}else{
NSLog(@"該字段不可寫!");
}
}
第13步跟前面的區(qū)別是它是我們自己寫的一個(gè)方法,作為接口,作用是寫入外設(shè)的特征值。
14. 寫入特征值成功(didWriteValueForCharacteristic) 【代理方法】
// 14. 寫入特征值成功(didWriteValueForCharacteristic) 【代理方法】
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
NSLog(@"didWriteValueForCharacteristic");
}
再下面說說如果你需要監(jiān)聽某個(gè)特征的情況,這也是2個(gè)自己寫的方法,作為接口。
15. 發(fā)起設(shè)置或取消某個(gè)特征的監(jiān)聽通知(setNotifyValue: forCharacteristic:)
//設(shè)置通知
-(void)notifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
//設(shè)置通知,數(shù)據(jù)通知會進(jìn)入:didUpdateValueForCharacteristic方法
// 15. 發(fā)起設(shè)置或取消某個(gè)特征的監(jiān)聽通知(setNotifyValue: forCharacteristic:)
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
//取消通知
-(void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
}
16. 監(jiān)聽某個(gè)特征的通知的狀態(tài)變更 (didUpdateNotificationStateForCharacteristic) 【代理方法】
// 16. 監(jiān)聽某個(gè)特征的通知的狀態(tài)變更 (didUpdateNotificationStateForCharacteristic) 【代理方法】
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
}
下面說說CBDescriptor(以前看過很多文章,都不太清楚這個(gè)是干嘛的,后來看了BabyBluetooth的源碼才弄明白)。這個(gè)類其實(shí)是個(gè)輔助類,它的作用是描述特征用的(反正我實(shí)際的藍(lán)牙開發(fā)中都沒有用到,如果業(yè)務(wù)需要用到就用)?;氐降?0步的發(fā)現(xiàn)外設(shè)的服務(wù)的特征的代理方法。
查了蘋果官方文檔,關(guān)于此類的說明
(CBDescriptor代表一種的特征描述符。特別是CBDescriptor對象代表一個(gè)遠(yuǎn)程外圍的特征描述符(遠(yuǎn)程外圍設(shè)備都由CBPeripheral對象表示)。描述符提供有關(guān)特征值的進(jìn)一步信息。例如,它們可以描述人類可讀的形式的值,并描述如何為呈現(xiàn)目的格式化值。特征描述符還指示特征值是否配置在服務(wù)器(外圍設(shè)備)上,以便在特征變化值指示或通知客戶端(中央)時(shí)。)
17. 發(fā)起發(fā)現(xiàn)特征的描述
//掃描到Characteristics
// 10. 發(fā)現(xiàn)外設(shè)的服務(wù)的特征(discoverCharacteristicsForService)【代理方法】
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error)
{
NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics)
{
NSLog(@"service:%@ 的 Characteristic: %@",service.UUID,characteristic.UUID);
}
for (CBCharacteristic *characteristic in service.characteristics){
{
if (characteristic.properties & CBCharacteristicPropertyRead) {
//獲取Characteristic的值,讀到數(shù)據(jù)會進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
// 11. 發(fā)起讀取特征值(readValueForCharacteristic)
[peripheral readValueForCharacteristic:characteristic];
}
}
}
//搜索Characteristic的Descriptors,讀到數(shù)據(jù)會進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
// 17. 發(fā)起發(fā)現(xiàn)特征的描述
for (CBCharacteristic *characteristic in service.characteristics){
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
18. 發(fā)現(xiàn)特征的描述 【代理方法】 19. 發(fā)起讀取特征的描述值
//搜索到Characteristic的Descriptors
// 18. 發(fā)現(xiàn)特征的描述 【代理方法】 19. 發(fā)起讀取特征的描述值
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//打印出Characteristic和他的Descriptors
NSLog(@"characteristic uuid:%@",characteristic.UUID);
for (CBDescriptor *d in characteristic.descriptors) {
// 19. 發(fā)起讀取特征的描述值
[peripheral readValueForDescriptor:d];
NSLog(@"Descriptor uuid:%@",d.UUID);
}
}
20. 更新特征的描述值【代理方法】
//獲取到Descriptors的值
// 20. 更新特征的描述值【代理方法】
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
//打印出DescriptorsUUID 和value
//這個(gè)descriptor都是對于characteristic的描述,一般都是字符串,所以這里我們轉(zhuǎn)換成字符串去解析
NSLog(@"characteristic uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}
接下來的寫入描述值用的就更少了
21. 發(fā)起寫入特征的描述值
-(void)writeCharacteristic:(CBPeripheral *)peripheral value:(NSData *)data forDescriptor:(CBDescriptor *)descriptor
{
// 21. 發(fā)起寫入特征的描述值
[peripheral writeValue:data forDescriptor:descriptor];
}
22. 寫入特征的描述值【代理方法】
// 22. 寫入特征的描述值【代理方法】
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error
{
}
23. 斷開藍(lán)牙連接
//停止掃描并斷開連接
// 23. 斷開藍(lán)牙連接
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
peripheral:(CBPeripheral *)peripheral{
//停止掃描
[centralManager stopScan];
//斷開連接
[centralManager cancelPeripheralConnection:peripheral];
}
上面就是最最簡單的中心模式的流程,實(shí)際項(xiàng)目中會有一些異常和超時(shí)處理,還有通過UUID過濾等。不過萬變不離其宗,最最核心的還是上面這套流程。
延伸閱讀
ios藍(lán)牙開發(fā)(二)ios連接外設(shè)的代碼實(shí)現(xiàn) (很多基礎(chǔ)概念都在這篇文章里,由于篇幅關(guān)系我就不寫在本文里了。剛接觸iOS藍(lán)牙的可以看看這位大神的文章)