基础用法先说明一下,自己创建一个叫BluetoothManager的单例,在头文件BluetoothManager.h
导入系统蓝牙框架,分别创建几个数组,在使用时调用:
#import <CoreBluetooth/CoreBluetooth.h>
@interface BluetoothManager : NSObject
@property (nonatomic, strong) NSMutableArray *findPeripherals; //查找到设备(不包含用户列表里的设备)
@property (nonatomic, strong) NSMutableArray *connectPeripherals; //连接上的设备
@property (nonatomic, strong) CBCentralManager *cbCentralManager;
/**
单例实现方法
*/
+ (BluetoothManager *)share;
@end
在.m文件里面创建单例,并且添加蓝牙模块的协议,并为签订代理:
#import "BluetoothManager.h"
@interface BluetoothManager()<CBCentralManagerDelegate,CBPeripheralDelegate,CBPeripheralManagerDelegate>
@property (nonatomic, strong) NSMutableArray *offlineperipherals;
@property (nonatomic, strong) NSMutableArray *tempLists;
@property (nonatomic, strong) CBPeripheral *peripheral;
@property (nonatomic, strong) CBPeripheralManager *peripheralmanager;
@implementation BlueInfo
@end
@implementation BluetoothManager
//单例生成
+(BluetoothManager *)share {
static BluetoothManager *shareInstance_ = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareInstance_ = [[self alloc] init];
});
return shareInstance_;
}
//初始化中心设备CBCentraManager(管理者)和CBPeripheralManager(外设管理者)
-(id) init {
self = [super init];
if (self) {
_cbCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
_peripheralmanager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
}
return self;
}
-(void) setDelegate:(id<BluetoothManagerDelegate>)delegate
{
if (!_delegate) {
_delegate = delegate;
}
}
下面是中心设备的代理方法,步骤基本要按照下面的走:
1.检查设备蓝牙服务是否打开(可用),关于中心设备的状态central.state
有以下6个枚举
- (void) centralManagerDidUpdateState:(CBCentralManager *)central{
BOOL state = false;
NSString *msg = @"";
switch (central.state) {
case CBCentralManagerStatePoweredOn:{
msg = @"Bluetooth is currently powered on";
state = YES;
}
break;
case CBCentralManagerStatePoweredOff:{
msg = @"Bluetooth is currently powered off.";
}
break;
case CBCentralManagerStateUnauthorized:{
msg = @"The application is not authorized to use the Bluetooth Low Energy Central/Client role.";
}
break;
case CBCentralManagerStateUnsupported:{
msg = @"The platform doesn't support the Bluetooth Low Energy Central/Client role.";
}
break;
case CBCentralManagerStateResetting:{
msg = @"The connection with the system service was momentarily lost, update imminent.";
}
break;
case CBCentralManagerStateUnknown:{
msg = @"State unknown, update imminent.";
}
break;
}
}
NSLog(@"%@",msg);
只有在CBCentralManagerStatePoweredOn
的状态下,才可以进行下步操作——其他状态,可以根据自身情况做相应的提示高告诉用户;
2.搜索中心设备周边的外设:
在上面代理方法的CBCentralManagerStatePoweredOn
的case
下写搜索方法,可以直接把系统方法写到该case
,此处把系统的搜索方法封装到一个自定义方法,代码更整洁(创建布尔值state
,用于标记设备蓝牙成功与否)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
BOOL state = false;
NSString *msg = @"";
switch (central.state) {
case CBCentralManagerStatePoweredOn:{
msg = @"Bluetooth is currently powered on";
[self scanDevice];//搜索设备方法
state = YES;
}
break;
// ...... 下面的状态省略......
}
}
下面是搜索设备封装方法:
-(void) scanBlueServes {
NSDictionary *optionsDict = @{CBCentralManagerScanOptionAllowDuplicatesKey:@YES};
[self.cbCentralManager scanForPeripheralsWithServices:nil options:optionsDict];
//CBUUID *uuid = [CBUUID UUIDWithString:@"FFF0"];
//[self.cbCentralManager scanForPeripheralsWithServices:@[uuid] options:optionsDict];
}
方法中,需要注意的是:
- 注释的那部分代码是用于指定搜索设备特定服务的uuid,nil则无差别搜索;
- 如果想要改变默认行为,可以指定
CBCentralManagerScanOptionAllowDuplicatesKey
作为扫描选项。此时,central管理器会在每次收到peripheral端的广告包时都触发一个事件。在某些情况下关闭默认行为很有用处,但记住指定CBCentralManagerScanOptionAllowDuplicatesKey
扫描选项不利于电池的寿命及程序性能。因此,只在需要的时候使用这个选项以完成特定的任务。
原文如下:
- A Boolean value that specifies whether the scan should run without duplicate filtering.
The value for this key is an NSNumber object. If true, filtering is disabled and a discovery event is generated each time the central receives an advertising packet from the peripheral. Disabling this filtering can have an adverse effect on battery life and should be used only if necessary. If false, multiple discoveries of the same peripheral are coalesced into a single discovery event. If the key is not specified, the default value is false.
3.检测到外设后,进入下面的代理方法,连接设备(这里开始加入了一些个人的方法)
- 当搜索到设备进入这个代理方法:
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
,搜索到多个设备,则此方法多次调用;
由于iOS端禁止获取蓝牙设备Mac地址,所以和硬件工程师协商好,将Mac地址放到指定广播包里,从此方法中advertisementData
以NSData传过来,而这个key是苹果指定的,不能乱写;
(余下的除了'central'分别是搜索到的设备:peripheral
,设备信号:RSSI
(数据类型参考方法)). - 我们可通过外设的名字
peripheral.name
或者刚刚说的蓝牙Mac地址来过滤设备,这就需要和硬件开发协商了,这里还做了一步处理,将已经被其他设备连接的外设通过连接状态的判断也过滤掉
if (peripheral.state == CBPeripheralStateConnected) {
return;
}
- 过滤之后,将设备添加到
self.findPeripherals
数组中传到界面(通过UITableView
)展示出来:
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
if (peripheral.state == CBPeripheralStateConnected) {
return;
}
NSData *data = [advertisementData objectForKey:@"kCBAdvDataManufacturerData"];
if ((peripheral.name && ([peripheral.name hasPrefix:@"BBCare"]) || [self checkMacAddress]){
[self.findPeripherals addObject:peripheral];
}
4.通过界面方法,实现蓝牙类方法;
一般情况下,我们(其实是我)会在tableView通过BluetoothManager
单例获取到findPeripherals数组,展示到界面,然后在tableview的协议方法:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
里面获取要连接的设备peripheral
,传入如下方法实现连接设备:
[[BluetoothManager share].cbCentralManager connectPeripheral:peripheral options:nil];
5.连接后的处理(划重点了!)
- 连接失败:
有时候,蓝牙搜索到设备时设备还在,点击连接时设备不在附近或没电(就不详细说了),类似这些情况,连接会失败,会进入如下方法,NSLog一下,会打印出具体的失败原因:
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@">>>连接到名称为(%@)的设备-失败,原因:%@",[peripheral name],[error localizedDescription]);
}
- 连接成功后,签订外设代理方法
别以为连接成功了数据就会自己找你了,并不是,后面还有不少的坑呢.
首先,连接成功后,会进入另外一个代理方法",这里我把连接的设备添加进self.connectperipherals
数组以备不时之需,然后该设备需签订peripheral的代理,然后开始一起去了解外设的代理方法吧
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
NSLog(@">>>连接到设备(%@ >> %@)-- 成功",peripheral.name,peripheral.identifier.UUIDString);
if (self.connectperipherals == nil) {
self.connectperipherals = [NSMutableArray array];
}
if (![self.connectperipherals containsObject:peripheral]) {
[self.connectperipherals addObject:peripheral];
}
[peripheral setDelegate:self];
[peripheral discoverServices:nil];
}
- 发现外设服务
CBService
来到这里,就要打开硬件工程师写给你的硬件对接的文档,看看他们给你的设备里面有什么服务了
外设一般都会有几个服务,每个服务都会有几个特征,服务和特征都是用不同的 UUID 来标识,每个特征的properties
是不同的,就是说有不同的功能属性后面会说到,不清楚的话,可以谷歌一下 "蓝牙","服务","特征码"等关键字;
而peripheral.services
就是一个存有所有服务码的数组,forin一下,看看里面有没有你需要的?当然,代码里面的那个特征码的UUID@"FFF1"
是我乱写的,大家可以根据自己的硬件去搜索特征码,用到的方法是:
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:@"FFF1"]] forService:service];
//完整代码
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
NSLog(@">>>扫描到服务:%@",peripheral.services);
if (error){
NSLog(@">>>发现服务 %@ 错误: %@", peripheral.name, [error localizedDescription]);
return;
}
for (CBService *service in peripheral.services) {
NSLog(@">>>扫描到服务 = %@",service.UUID);
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:@"FFF1"]] forService:service];
}
}
- 发现外设服务中的特征码
CBCharacteristic
刚也说到,和服务类似,特征码也是以不同的 UUID 来标识,我们要对蓝牙的操作,其实就是对这些特征码搞事情了特征码的属性是一个枚举类型:
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
};
在我的项目中用到的分别是:
CBCharacteristicPropertyWrite, //可写入
CBCharacteristicPropertyWriteWithoutResponse, //可写入并带回执
CBCharacteristicPropertyRead, //可读
CBCharacteristicPropertyNotify, //可订阅1
CBCharacteristicPropertyIndicate //可订阅2
会一点英文的同学看看类型后面大概能猜到,第一二个是写入,一个带回执,一个不带;第三个是可读;第四第五个是骚骚不同的两种订阅,具体根据不同的情况使用,其他的好像我也没用到过...不过看后缀的话好像能发现些什么吧?
- 发现外设的特征码
CBCharacteristic
代理方法
这也是通过forinservice.characteristics
,判断特征码的UUID,对应不同的特征码做不同的操作,比如:
setNotifyValue forCharacteristic //订阅方法`;
writeValue: forCharacteristic: type: //写入方法,其中type类型有无回执
peripheral readValueForCharacteristic: //读取方法
整段的代码如下:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error) {
NSLog(@"%s,%@",__PRETTY_FUNCTION__,error);
}
else{
for (CBCharacteristic *characteristic in service.characteristics{
NSLog(@"特征码 == %@",characteristic.UUID);
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"FFF4"]]){
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"FFF3"]]){
[peripheral writeValue:[NSData data] forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
}
}
}
不知不觉写了这么多,还是分开上下部说吧...
iOS关于蓝牙框架BLE的开发--基础用法(下)
- 操作特征码后的回调——订阅/读取回调与失败回调
- 操作特征码后的回调——写入回调与失败回调)