必须要了解的
1、CBCentralManager 蓝牙中心管理器(作为中心开发使用)
CBPeripheral 蓝牙外设
CBService 蓝牙服务(一个外设有多个服务)
CBCharacteristic 蓝牙服务特性(一个服务有多个特性)
CBUUID 唯一标识符(服务和特性都有)
2、中心和外设是一对多的关系,中心可以连接多个外设,但外设只能被一个中心连接。
3、每个服务和特性都有一个CBUUID,如果项目需要监听或写入多个特性,那么开发中需要对服务和特性的CBUUID进行记录,用于判断消息是从哪个特性传过来的。反之则不太需要关注CBUUID参数。
4、如果不想搜索出无关的蓝牙设备,则需要在搜索外设时传入服务和特性的CBUUID数组进行筛选。
1、导入头文件和协议
#import <CoreBluetooth/CoreBluetooth.h>
@interface WWTBlueToothModule()<CBCentralManagerDelegate,CBPeripheralDelegate>
2、实例化CentralManager
-(instancetype)init
{
instance = [super init];
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
NSDictionary * options = @{CBCentralManagerOptionShowPowerAlertKey:@YES};
_BT_CenterManager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue() options:options];
_maxLength = MAXLENGTH;//设置数据写入的默认最大长度
});
return instance;
}
3、判断蓝牙状态
/*
typedef NS_ENUM(NSInteger, CBManagerState) {
CBManagerStateUnknown = 0,//未知状态
CBManagerStateResetting,//蓝牙正在重置
CBManagerStateUnsupported,//不支持蓝牙
CBManagerStateUnauthorized,//未授权
CBManagerStatePoweredOff,//蓝牙未开启
CBManagerStatePoweredOn,//蓝牙已开启
} NS_ENUM_AVAILABLE(10_13, 10_0);
*/
/*
*获取Central状态
*/
-(CBManagerState)GetCentralManagerState
{
return _BT_CenterManager.state;
}
/*
*Central状态更新时触发
*在CentralManager创建时会自动调用该方法
*/
-(void)centralManagerDidUpdateState:(CBCentralManager *)central
{
[[NSNotificationCenter defaultCenter] postNotificationName:kCentralManagerStateUpdateNoticiation object:@{@"central":central}];
}
4、若蓝牙状态为CBManagerStatePoweredOn,则开始搜索外设。UUIDs为服务UUID的集合,传入后只会搜索出带有这些服务的外设,传nil则返回全部外设。
//这里只给出了关键代码,需要根据自身的代码选择调用时机
[_BT_CenterManager scanForPeripheralsWithServices:uuids options:options];
5、搜索到外设时会进入CBCentralManagerDelegate的代理方法didDiscoverPeripheral
每当搜索到一个外设时会进入didDiscoverPeripheral方法,要注意会重复搜索出同一个外设,所以在加入外设数组时,需要先判断是否已经存在数组中了。
//RSSI--信号量
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
}
6、连接外设
//这里只给出了关键代码,需要根据自身的代码选择调用时机
[_BT_CenterManager connectPeripheral:peripheral options:connectOptions];
7、连接成功后会进入代理方法didConnectPeripheral
//改变当前蓝牙连接状态 _connectStatus = BETConnecting;
//停止搜索[_BT_CenterManager stopScan];
//查看外设的全部服务peripheral.services
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
}
8、若连接失败,则进入didFailToConnectPeripheral
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error
{
}
9、连接成功后设置外设peripheral代理
peripheral.delegate = self;
10、根据serviceUUIDs搜索服务
//serviceUUIDs为nil时搜索全部服务
[_BT_Peripheral discoverServices:_serviceUUIDs];
11、搜索到服务会触发peripheral的代理方法didDiscoverServices
/*
*发现服务触发
*服务在peripheral.services里面
*/
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
}
12、根据搜索到的服务继续搜索服务的特性
[_BT_Peripheral discoverCharacteristics:_characteristicUUIDs forService:service];
13、发现特性后会触发peripheral的代理方法didDiscoverCharacteristicsForService
//特性在service.characteristics里面
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error
{
}
14、接下来有几个分支
(1)搜索特性描述
[_BT_Peripheral discoverDescriptorsForCharacteristic:character];
搜索到后会进入didDiscoverDescriptorsForCharacteristic方法
/*
*描述在characteristic.descriptors里面
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
}
读取特性描述
[_BT_Peripheral readValueForDescriptor:descriptor];
从didUpdateValueForDescriptor获取描述的值
/*
*获取描述会触发
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error
{
if (error) {
if (_readValueForDescriptorBlock) {
_readValueForDescriptorBlock(descriptor,nil,error);
}
return;
}
NSData *data = descriptor.value;
if (_readValueForDescriptorBlock) {
_readValueForDescriptorBlock(descriptor,data,nil);
}
}
(2)获取特性的值
[_BT_Peripheral readValueForCharacteristic:character];
触发didUpdateValueForCharacteristic读取特性的值,这里注意需要注意订阅和读取特性值都是从这个方法返回的
// 读取特性中的值
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
if (_readValueForCharacteristicBlock) {
_readValueForCharacteristicBlock(characteristic,nil,error);
}
return;
}
NSData *data = characteristic.value;
if (_readValueForCharacteristicBlock) {
_readValueForCharacteristicBlock(characteristic,data,nil);
}
}
(3)订阅特性
[_BT_Peripheral setNotifyValue:YES forCharacteristic:character];
需要在didUpdateNotificationStateForCharacteristic中判断订阅是否成功
/*
*特性订阅状态回调
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
if (error) {
if (_notifyCharacteristicBlock) {
_notifyCharacteristicBlock(peripheral,characteristic,error);
}
return;
}
if (_notifyCharacteristicBlock) {
_notifyCharacteristicBlock(peripheral,characteristic,nil);
}
}
最后当订阅的特征的值更新时触发didUpdateValueForCharacteristic方法
(4)向特性写值
[_BT_Peripheral writeValue:Data forCharacteristic:characteristic type:type];
这里要注意特性允许单次写入的最大长度!!
iOS 9.0以上可以使用maximumWriteValueLengthForType方法进行查询最大写入长度
//往特性写入数据(传输数据)
-(void)WriteValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic andType:(CBCharacteristicWriteType)type
{
_writeCount = 0;
_responseCount = 0;
if ([_BT_Peripheral respondsToSelector:@selector(maximumWriteValueLengthForType:)]) {
_maxLength = [_BT_Peripheral maximumWriteValueLengthForType:type];
}
if (_maxLength <= 0) {
[_BT_Peripheral writeValue:data forCharacteristic:characteristic type:type];
_writeCount ++;
return;
}
if (data.length <= _maxLength) {
[_BT_Peripheral writeValue:data forCharacteristic:characteristic type:type];
_writeCount ++;
}
else
{
NSInteger Dataindex = 0;
for (Dataindex = 0; Dataindex < data.length - _maxLength; Dataindex += _maxLength) {
NSData * pieceOfData = [data subdataWithRange:NSMakeRange(Dataindex, _maxLength)];
[_BT_Peripheral writeValue:pieceOfData forCharacteristic:characteristic type:type];
_writeCount ++;
}
NSData * leftData = [data subdataWithRange:NSMakeRange(Dataindex, data.length - Dataindex)];
if (leftData) {
[_BT_Peripheral writeValue:leftData forCharacteristic:characteristic type:type];
_writeCount ++;
}
}
}
写入后会触发didWriteValueForCharacteristic,在这里判断数据是否写入成功
/*
*向特性写入值后触发
*根据error判断是否成功
*/
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
if (!_writeValueForCharacteristicBlock) {
return;
}
_responseCount ++;
if (_writeCount != _responseCount) {
return;
}
_writeValueForCharacteristicBlock(characteristic,error);
}
(5)向特性的描述写值
[_BT_Peripheral writeValue:value forDescriptor:descriptor];
写入后会触发didWriteValueForCharacteristic,在这里判断数据是否写入成功
/*
*写入描述后触发
*根据error判断是否成功
*/
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error
{
if (error) {
if (_writeValueForDescriptorBlock) {
_writeValueForDescriptorBlock(descriptor, error);
}
return;
}
if (_writeValueForDescriptorBlock) {
_writeValueForDescriptorBlock(descriptor, nil);
}
}
15、断开连接
[_BT_CenterManager cancelPeripheralConnection:_BT_Peripheral];
主动或被动断开连接都会触发didDisconnectPeripheral
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
_BT_Peripheral = nil;
_connectStatus = BETDisconnect;
NSLog(@"断开连接");
}
最后说一些问题
1、上面代码也有说到,当蓝牙连接成功后就停止搜索外设,但是如果用户点击进入蓝牙列表界面,但是没连接蓝牙又POP出去了,那么蓝牙是一直处于搜索状态的。所以需要在列表界面viewWillDisappear的时候StopScan。
-(void)viewWillDisappear:(BOOL)animated
{
[[WWTBlueToothModule ShareInstance]StopScan];
[super viewWillDisappear:animated];
}
2、似乎第一次搜索外设是必须要在centralManagerDidUpdateState方法中进行的,这个不太确定,反正我这么写了。另外把搜索方法写在了这里还是不够的,因为centralManagerDidUpdateState只会在蓝牙状态改变时和创建CentralManager时调用,将该模块写成单例模式时,第二次进入外设列表界面需要手动Scan。
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear: animated];
//获取当前设备蓝牙状态
if ([BET_ShareInstance GetCentralManagerState] == CBManagerStatePoweredOn) {
//若设备蓝牙状态为可用,则开始扫描
[BET_ShareInstance ScanForPeripheralWithServiceUUIDs:_ServiceUUIDs options:nil];
}
else
{
//提示检查设备蓝牙
}
}
网友评论