隨著藍牙低功耗技術BLE(Bluetooth Low Energy)的發展,藍牙技術正在一步步成熟,如今的大部分移動設備都配備有藍牙4.0,相比之前的藍牙技術耗電量大大降低。
GameKit.framework
:MultipeerConnectivity.framework
:CoreBluetooth.framework
:應用的比較多的是CoreBluetooth
框架,這裡就選擇CoreBluetooth
框架來講。
當前BLE應用相當廣泛,不再僅僅是兩個設備之間的數據傳輸,它還有很多其他應用市場,例如室內定位、無線支付、智能家居等等,這也使得CoreBluetooth
成為當前最熱門的藍牙技術。
我的理解中,CoreBluetooth
藍牙通信過程和網絡通信過程比較類似。
CoreBluetooth
中,藍牙傳輸都分為兩個部分:CBPeripheral
:CBCentral
:外圍設備和中央設備交互的橋梁是服務和特征<喎?/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPqOswb249ra809DSu7j2zqjSu7HqyrY8Y29kZT5DQlVVSUQ8L2NvZGU+wLTIt7ao0ru49rf+zvG78tXfzNjV96O6PGJyIC8+DQoqILf+zvE8Y29kZT5DQlNlcnZpY2U8L2NvZGU+o7o8YnIgLz4NCtbQ0evJ6LG41rvT0M2ouf23/s7xssXE3NPrzeLOp8nosbi9+NDQyv2+3bSryuSjrMDgy8bT2r/Nu6e2y82ouf3N+Na3VVJMssXE3NPrt/7O8cb3way909K70fk8YnIgLz4NCiogzNjV9zxjb2RlPkNCQ2hhcmFjdGVyaXN0aWM8L2NvZGU+o7o8YnIgLz4NCsO/uPa3/s7xv8nS1NO109C24Lj2zNjV96Os1tDR68nosbix2NDrtqnUxM3izqfJ6LG4t/7O8bXEzNjV99a1o6yyxcTcu/HIoc3izqfJ6LG4tcTK/b7do6zA4MvG09pHRVTH68fzv8nS1Mfrx/O78ciht/7O8cb3yv2+3aOsUE9TVMfrx/O/ydLUz/K3/s7xxve0q8rkyv2+3dK70fmhozwvcD4NCjxwPjxpbWcgYWx0PQ=="" src="/uploadfile/Collfiles/20160418/20160418154719509.png" title="\" />
CBPeripheralManager
,並設置代理 創建一個特征CBCharacteristic
,綁定一個CBUUID
,設置特征屬性 創建一個服務CBService
,綁定一個CBUUID
,設置服務的特征 調用外圍設備管理器的對象方法,添加服務到外圍設備上
-(void)addService:(CBService *)service;
外圍設備管理器開始廣播服務
-(void)startAdvertising:(NSDictionary *)dict;
在外圍設備管理器的代理方法,處理與中央設備的交互
/* 外圍設備管理器狀態發生改變後調用,比如外圍設備打開藍牙的時候 */
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;
/* 外圍設備管理器添加了服務後調用,一般在這裡進行廣播服務 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service /* 添加的服務 */
error:(NSError *)error;/* 添加服務錯誤信息 */
/* 啟動廣播服務後調用 */
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error;/* 啟動服務錯誤信息 */
/* 外圍設備恢復狀態時調用 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral
willRestoreState:(NSDictionary *)dict;
/* 中央設備訂閱外圍設備的特征時調用 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral /* 外圍設備管理器 */
central:(CBCentral *)central /* 中央設備 */
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;/* 訂閱的特征 */
/* 中央設備取消訂閱外圍設備的特征時調用 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral /* 外圍設備管理器 */
central:(CBCentral *)central /* 中央設備 */
didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;/* 特征 */
/* 外圍設備收到中央設備的寫請求時調用 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveWriteRequests:(CBATTRequest *)request;/* 寫請求 */
/* 添加服務 */
- (void)addService:(CBService *)service;
/* 開啟廣播服務,dict設置設備信息 */
- (void)startAdvertising:(NSDictionary *)dict;
/* 更新特征值,centrals為空,表示對所有連接的中央設備通知 */
- (void)updateValue:(NSData *)value /* 特征的特征值 */
forCharacteristic:(CBCharacteristic *)characteristic /* 特征 */
onSubscribedCentrals:(NSArray *)centrals;/* 需要通知更新特征值的中央設備 */
#import "PeripheralViewController.h"
#import
#define kPeripheralName @"Liuting's Device" //外圍設備名稱,自定義
#define kServiceUUID @"FFA0-FFB0" //服務的UUID,自定義
#define kCharacteristicUUID @"FFCC-FFDD" //特征的UUID,自定義
@interface PeripheralViewController ()
@property (strong, nonatomic) CBPeripheralManager *peripheralManager;/* 外圍設備管理器 */
@property (strong, nonatomic) NSMutableArray *centralM;/* 訂閱的中央設備 */
@property (strong, nonatomic) CBMutableCharacteristic *characteristicM;/* 特征 */
@end
@implementation PeripheralViewController
- (void)viewDidLoad{
[super viewDidLoad];
self.centralM = [NSMutableArray array];
//創建外圍設備管理器
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self
queue:nil];
}
#pragma mark - UI事件
/* 點擊更新特征值 */
- (IBAction)changeCharacteristicValue:(id)sender {
//特征值,這裡是更新特征值為當前時間
NSString *valueStr = [NSString stringWithFormat:@"%@",[NSDate date]];
NSData *value = [valueStr dataUsingEncoding:NSUTF8StringEncoding];
//更新特征值
[self.peripheralManager updateValue:value
forCharacteristic:self.characteristicM
onSubscribedCentrals:nil];
}
#pragma mark - 私有方法
/* 創建特征、服務並添加服務到外圍設備 */
- (void)addMyService{
/*1.創建特征*/
//創建特征的UUID對象
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
/* 創建特征
* 參數
* uuid:特征標識
* properties:特征的屬性,例如:可通知、可寫、可讀等
* value:特征值
* permissions:特征的權限
*/
CBMutableCharacteristic *characteristicM =
[[CBMutableCharacteristic alloc] initWithType:characteristicUUID
properties:CBCharacteristicPropertyNotify
value:nil
permissions:CBAttributePermissionsReadable];
self.characteristicM = characteristicM;
//創建服務UUID對象
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
//創建服務
CBMutableService *serviceM = [[CBMutableService alloc] initWithType:serviceUUID
primary:YES];
//設置服務的特征
[serviceM setCharacteristics:@[characteristicM]];
//將服務添加到外圍設備
[self.peripheralManager addService:serviceM];
}
#pragma mark - CBPeripheralManager代理方法
/* 外圍設備狀態發生變化後調用 */
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
//判斷外圍設備管理器狀態
switch (peripheral.state) {
case CBPeripheralManagerStatePoweredOn:
{
NSLog(@"BLE已打開.");
//添加服務
[self addMyService];
break;
}
default:
{
NSLog(@"此設備不支持BLE或未打開藍牙功能,無法作為外圍設備.");
break;
}
}
}
/* 外圍設備恢復狀態時調用 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral
willRestoreState:(NSDictionary *)dict
{
NSLog(@"狀態恢復");
}
/* 外圍設備管理器添加服務後調用 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service
error:(NSError *)error
{
//設置設備信息dict,CBAdvertisementDataLocalNameKey是設置設備名
NSDictionary *dict = @{CBAdvertisementDataLocalNameKey:kPeripheralName};
//開始廣播
[self.peripheralManager startAdvertising:dict];
NSLog(@"向外圍設備添加了服務並開始廣播...");
}
/* 外圍設備管理器啟動廣播後調用 */
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error
{
if (error) {
NSLog(@"啟動廣播過程中發生錯誤,錯誤信息:%@",error.localizedDescription);
return;
}
NSLog(@"啟動廣播...");
}
/* 中央設備訂閱特征時調用 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral
central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
{
NSLog(@"中心設備:%@ 已訂閱特征:%@.",central,characteristic);
//把訂閱的中央設備存儲下來
if (![self.centralM containsObject:central]) {
[self.centralM addObject:central];
}
}
/* 中央設備取消訂閱特征時調用 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral
central:(CBCentral *)central
didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic
{
NSLog(@"中心設備:%@ 取消訂閱特征:%@",central,characteristic);
}
/* 外圍設備管理器收到中央設備寫請求時調用 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveWriteRequests:(CBATTRequest *)request
{
NSLog(@"收到寫請求");
}
@end
更多的時候,我們需要的是一個中央設備,外圍設備不一定是iOS設備,所以上面外圍設備的創建不一定會用到,比如外圍設備是GPS導航儀、心率儀等,這些只要遵循BLE4.0的規范,中央設備就可以與之連接並尋找服務和訂閱特征。
CBCentralManager
,設置代理 掃描外圍設備,發現外圍設備CBPeripheral
進行連接,保持連接的外圍設備 在連接外圍設備成功的代理方法中,設置外圍設備的代理,調用外圍設備的對象方法尋找服務
查找外圍設備的服務和特征,查找到可用特征,則讀取特征數據。
**記住這裡是外圍設備對象CBPeripheral
,CBPeripheralManager
**
/* 中央設備管理器狀態改變後調用,比如藍牙的打開與關閉 */
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;
/* 開啟掃描後,中央設備管理器發現外圍設備後調用 */
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral /* 外圍設備 */
advertisementData:(NSDictionary *)advertisementData /* 設備信息 */
RSSI:(NSNumber *)RSSI; /* 信號強度 */
/* 中央設備管理器成功連接到外圍設備後調用 */
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral;/* 外圍設備 */
/* 中央設備管理器連接外圍設備失敗後調用 */
- (void)centralManager:(CBCentralManager *)central
didFailToConnectPeripheral:(CBPeripheral *)peripheral /* 外圍設備 */
error:(NSError *)error;/* 連接失敗的錯誤信息 */
/* 掃描外圍設備,可以指定含有指定服務的外圍設備 */
- (void)scanForPeripheralsWithServices:(NSArray *)services
options:(NSDictionary *)options;
/* 停止掃描 */
- (void)stopScan;
/* 連接外圍設備 */
- (void)connectPeripheral:(CBPeripheral *)peripheral
options:(NSDictionary *)options;
/* 斷開外圍設備 */
- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;
/**
* 1.成功訂閱外圍設備的服務後調用,在該代理方法中尋找服務的特征
* @param peripheral 連接到的設備
* @param error 錯誤信息
*/
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error;
/**
* 2.成功找到外圍設備服務的特征後調用,在該代理方法中設置訂閱方式
* @param peripheral 連接的設備
* @param service 擁有的服務
* @param error 錯誤信息
*/
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
error:(NSError *)error;
/**
* 3.外圍設備讀取到特征值就會調用
* @param peripheral 連接的設備
* @param characteristic 改變的特征
* @param error 錯誤信息
*/
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error;
/**
* 4.向外圍設備的特征對象寫操作完成後調用
* 特別:當寫操作為CBCharacteristicWriteWithoutResponse時不會調用
* @param peripheral 連接的設備
* @param characteristic 要寫入的特征
* @param error 錯誤信息
*/
- (void)peripheral:(CBPeripheral *)peripheral
didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error;
/* 尋找服務,傳入的是服務的唯一標識CBUUID */
- (void)discoverServices:(NSArray *)services;
/* 尋找指定服務下的特征,特征數組也是傳入特征的唯一標識CBUUID */
- (void)discoverCharacteristics:(NSArray *)characteristics
forService:(CBService *)service;/* 服務 */
/* 設置是否向特征訂閱數據實時通知,YES表示會實時多次會調用代理方法讀取數據 */
- (void)setNotifyValue:(BOOL)value
forCharacteristic:(CBCharacteristic *)characteristic;
/* 讀取特征的數據,調用此方法後會調用一次代理方法讀取數據 */
- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic;
/* 向特征寫入數據,看type類型,決定調不調用寫入數據後回調的代理方法 */
- (void)writeValue:(NSData *)value /* 寫入數據 */
forCharacteristic:(CBCharacteristic *)characteristic /* 特征 */
type:(CBCharacteristicWriteType)type;/* 寫入類型 */
/* 寫入類型,決定要不要調用代理方法 */
typedef NS_ENUM(NSInteger, CBCharacteristicWriteType) {
CBCharacteristicWriteWithResponse = 0, //有回調的寫入
CBCharacteristicWriteWithoutResponse //沒回調的寫入
};
#import "CentralViewController.h"
#import
#define kPeripheralName @"Liuting's Device" //外圍設備名稱
#define kServiceUUID @"FFA0-FFB0" //服務的UUID
#define kCharacteristicUUID @"FFCC-FFDD" //特征的UUID
@interface CentralViewController ()
@property (strong, nonatomic) CBCentralManager *centralManager;/* 中央設備管理器 */
@property (strong, nonatomic) NSMutableArray *peripherals;/* 連接的外圍設備 */
@end
@implementation CentralViewController
#pragma mark - UI事件
- (void)viewDidLoad{
[super viewDidLoad];
self.peripherals = [NSMutableArray array];
//創建中心設備管理器並設置當前控制器視圖為代理
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
#pragma mark - CBCentralManager代理方法
/* 中央設備管理器狀態更新後調用 */
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBPeripheralManagerStatePoweredOn:
NSLog(@"BLE已打開.");
//掃描外圍設備
[central scanForPeripheralsWithServices:nil options:nil];
break;
default:
NSLog(@"此設備不支持BLE或未打開藍牙功能,無法作為中央設備.");
break;
}
}
/*
* 發現外圍設備調用
* @param central 中央設備管理器
* @param peripheral 外圍設備
* @param advertisementData 設備信息
* @param RSSI 信號質量(信號強度)
*/
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
{
NSLog(@"發現外圍設備...");
//連接指定的外圍設備,匹配設備名
if ([peripheral.name isEqualToString:kPeripheralName]) {
//添加保存外圍設備,因為在此方法調用完外圍設備對象就會被銷毀
if(![self.peripherals containsObject:peripheral]){
[self.peripherals addObject:peripheral];
}
NSLog(@"開始連接外圍設備...");
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
/* 中央設備管理器成功連接到外圍設備後調用 */
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"連接外圍設備成功!");
//停止掃描
[self.centralManager stopScan];
//設置外圍設備的代理為當前視圖控制器
peripheral.delegate = self;
//外圍設備開始尋找服務
[peripheral discoverServices:@[[CBUUID UUIDWithString:kServiceUUID]]];
}
/* 中央設備管理器連接外圍設備失敗後調用 */
- (void)centralManager:(CBCentralManager *)central
didFailToConnectPeripheral:(CBPeripheral *)peripheral
error:(NSError *)error
{
NSLog(@"連接外圍設備失敗!");
}
#pragma mark - CBPeripheral 代理方法
/* 外圍設備尋找到服務後調用 */
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error
{
NSLog(@"已發現可用服務...");
//遍歷查找到的服務
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
for (CBService *service in peripheral.services) {
if([service.UUID isEqual:serviceUUID]){
//外圍設備查找指定服務中的特征,characteristics為nil,表示尋找所有特征
[peripheral discoverCharacteristics:nil forService:service];
}
}
}
/* 外圍設備尋找到特征後調用 */
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
error:(NSError *)error
{
NSLog(@"已發現可用特征...");
//遍歷服務中的特征
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
if ([service.UUID isEqual:serviceUUID]) {
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:characteristicUUID]) {
//情景一:通知
/* 找到特征後設置外圍設備為已通知狀態(訂閱特征):
* 調用此方法會觸發代理方法peripheral:didUpdateValueForCharacteristic:error:
* 調用此方法會觸發外圍設備管理器的訂閱代理方法
*/
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
//情景二:讀取
//調用此方法會觸發代理方法peripheral:didUpdateValueForCharacteristic:error:
//[peripheral readValueForCharacteristic:characteristic];
}
}
}
}
/* 外圍設備讀取到特征值後調用 */
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error
{
if (characteristic.value) {
NSString *value = [[NSString alloc] initWithData:characteristic.value
encoding:NSUTF8StringEncoding];
NSLog(@"讀取到特征值:%@",value);
}else{
NSLog(@"未發現特征值.");
}
}
@end
除非去申請後台權限,否則 app 都是只在前台運行的,程序在進入後台不久便會切換到掛起狀態。掛起後,程序將無法再接收任何藍牙事件。
options
屬性,可以設置如下字典值:CBConnectPeripheralOptionNotifyOnConnectionKey
:CBConnectPeripheralOptionNotifyOnDisconnectionKey
:CBConnectPeripheralOptionNotifyOnNotificationKey
:info.plist
字段Required background nodes
在該字段下添加字符串值:App communicates using CoreBluetooth
:表示支持設備作為中央設備後台運行 App shares data using CoreBluetooth
:表示支持設備作為外圍設備後台運行