美文网首页iOS点点滴滴
iOS BLE蓝牙基础详解(两种模式)

iOS BLE蓝牙基础详解(两种模式)

作者: GCS_DEVELOPER | 来源:发表于2017-09-24 22:53 被阅读53次

iOS BLE框架详解

概述

使用蓝牙开发无外乎两种,当做主设备(Central)或者外设(Peripheral)。主设备可以获取外设携带的信息,和指定的外设连接(Connect),并在连接的基础上进行数据的读写(Read&Write)、监听(Notify)等操作。外设可以广播数据(advertise)并在主设备设法读取和监听数据时进行响应。

一、Be Peripheral(当做外设使用)

使用场景:通过iOS设备对外传输数据

使用限制: iOS设备当做外设时,只能广播两个字段UUID(CBAdvertisementDataServiceUUIDsKey)和外设名(CBAdvertisementDataLocalNameKey)

数据组成: 外设(Peripheral)包含若干个服务(Service),每个服务又包含若干个特征(Characteristic),每个特征又包含若干个描述信息(Descriptor),而往往我们需要获取的值是在Descriptor中

使用方法及对应回调详解

//1.引入BLE框架
#import <CoreBluetooth/CoreBluetooth.h>
//2.添加代理
@interface PeripheralManager()<CBPeripheralManagerDelegate>
//3.设置为全局
@interface PeripheralManager : NSObject
{
     CBPeripheralManager *myPeripheral;
}
 //4.设置外设的服务

在设置外设的服务前,再明确一下服务(Service)的组成,服务拥有唯一的识别号(CBUUID),并且包含若干个特征(Characteristic),每个特征也拥有唯一识别号(CBUUID),并包含如果干描述信息(Descriptor),除此之外,特征还需要设置权限(properties),通过设置的权限,来允许对特征的值进行相应操作。

//4.1初始化特征(CBMutableCharacteristic)
- (instancetype)initWithType:(CBUUID *)UUID properties:(CBCharacteristicProperties)properties value:(nullable NSData *)value permissions:(CBAttributePermissions)permissions 

UUID:唯一识别码

properties:特征权限

CBCharacteristicPropertyRead        读
CBCharacteristicPropertyWrite       写
CBCharacteristicPropertyNotify      通知

value:通常置为nil,可以根据需求动态的设置

permissions:value的权限

CBAttributePermissionsReadable      读
CBAttributePermissionsWriteable     写

初始化特征时注意事项

1. UUID通常为16进制格式(如:FFF0)

2. 定义多个权限(如:读写)可以通过位运算符 | 实现

3. value为NSData类型,如果设置value值,该数据会被缓存起来,而且value的权限必须是只读类型。但value不置为nil,即使设置为只读类型,仍可能会使程序崩溃,提示信息为;Characteristics with cached values must be read-only

//4.2初始化描述信息(CBMutableDescriptor)
- (instancetype)initWithType:(CBUUID *)UUID value:(nullable id)value

UUID:描述信息的UUID,用来指定value的类型

CBUUIDCharacteristicUserDescriptionString   value为字符串类型
  //4.3初始化服务
- (instancetype)initWithType:(CBUUID *)UUID primary:(BOOL)isPrimary

UUID:唯一识别码

isPrimary:服务的类型(YES:主要的服务,NO:非主要的服务),如:想发给主设备一条关于天气信息的服务,那么天气信息的服务为主要服务,其他附带信息(如:外设的电量等)为非主要的服务

    //初始化特征
    CBMutableCharacteristic *characteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:@"FFE0"] properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyWrite  value:nil permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable];
    //初始化描述
    CBUUID *CBUUIDCharacteristicUserDescriptionStringUUID = [CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString];
    CBMutableDescriptor *descriptor = [[CBMutableDescriptor alloc]initWithType: CBUUIDCharacteristicUserDescriptionStringUUID value:@"iOS BLE详解"];
    //特征添加描述
    [characteristicset Descriptors:@[descriptor]];
  //初始化服务
    CBMutableService *service =[[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:@"FFF0"] primary:YES];
  //服务添加特征
    NSMutableArray *characteristics = [service.characteristics mutableCopy];
    [characteristics addObject:characteristic];
    service.characteristics = [characteristics copy];
//5.初始化外设
myPeripheral = [[CBPeripheralManager alloc]initWithDelegate:self queue:dispatch_get_global_queue(0, 0)];

至此,预备工作已经全部准备完成。


//6.实例化外设后进入的回调方法
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;

在这个方法中需要判断蓝牙的状态(perpheral.state)

CBManagerStatePoweredOff        蓝牙已关闭
CBManagerStatePoweredOn         蓝牙已启用
//6.1如果蓝牙启用,在外设实例中添加服务
- (void)addService:(CBMutableService *)service;

//7.添加服务后进入的回调方法
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(nullable NSError *)error;
//7.1开始广播
- (void)startAdvertising:(nullable NSDictionary<NSString *, id> *)advertisementData;

广播字段

CBAdvertisementDataServiceUUIDsKey      服务的UUID
CBAdvertisementDataLocalNameKey         外设名

注意: iOS设备当做外设,广播的数据包,只能包含两个字段: 服务的UUID和外设名 ,新增其他字段会导致程序的崩溃。也就是说,如果想要进行数据的传输,必须通过连接的方式进行

//8.开始广播后进入的回调方法
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(nullable NSError *)error;

现在,初始化蓝牙外设后自动调用的方法已经全部介绍完了

注意事项:

1.上述回调方法为依次调用,当其中一个方法error不为nil,那么接下来的回调方法并不会被调用

2.如果添加的服务为空,也不会调用 回调方法7

//实例化外设后进入的回调方法
    - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
switch (peripheral.state) {
    case CBManagerStatePoweredOn:
        NSLog(@"蓝牙已开启");
        //外设添加服务
        [myPeripheral addService:service];//可以事先将service设置为全局变量
        }
        break;
    case CBManagerStatePoweredOff:
        NSLog(@"蓝牙已关闭");
        break;
    default:
        break;
}
}
    
    //外设添加服务后的回调
    - (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
    if (error) {
    NSLog(@"添加服务失败:Error == %@", error);
    return;
    }       
  //开始广播
    [myPeripheral startAdvertising:@{
                                     CBAdvertisementDataServiceUUIDsKey :@[service.UUID],
                                     CBAdvertisementDataLocalNameKey : @"BLEPeripheral"
                                     }];
}

  //开始广播后进入的回调
  - (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
if (error == nil) {
    NSLog(@"发射广播成功");
}else{
    NSLog(@"发射广播失败:Error == %@", error);
}
}

下面几个方法为与主设备连接之后,回应主设备请求时进入的回调方法

当主设备执行订阅/取消订阅特征时:enabled(YES 订阅 NO 取消订阅)

  -(void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic;
//9.主设备订阅特征后进入的回调
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;

在初始化时,我们将特征的值置为nil,现在当主设备订阅特征后,可以实时的更新特征的值

- (BOOL)updateValue:(NSData *)value forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:(nullable NSArray<CBCentral *> *)centrals;
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
    timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(updateC) userInfo:characteristic repeats:YES];
    NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
    [runLoop addTimer:timer forMode:NSRunLoopCommonModes];
}       
    - (void)updateC
{
[myPeripheral updateValue:[@"1" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:(CBMutableCharacteristic *)[timer userInfo] onSubscribedCentrals:nil];
}

//9.1主设备取消订阅后进入的回调
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;

主设备读取特征的值时:

 -(void)readValueForCharacteristic:(CBCharacteristic *)characteristic;
//10.主设备读取特征的值后进入的回调
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request

主设备写入特征的值时:

-(void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;

//11.主设备写入特征的值后进入的回调
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests;

request:请求

请求中常用到几个属性

request.value 请求的值

request.characteristic 请求操作的特征

当进行读的回调时,可以把特征的值赋给请求的值

当进行写的回调时,可以把请求的值赋给特征的值

    //读取特征时进入的回调
    - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
    //判断特征是否有读的权限
if (request.characteristic.properties & CBCharacteristicPropertyRead) {
     [request setValue:request.characteristic.value];//当然也可以进行其他操作~
    //读取成功的响应
    [myPeripheral respondToRequest:validRequset withResult:CBATTErrorSuccess];
}else
    //读取失败的响应
    [myPeripheral respondToRequest:validRequset withResult:CBATTErrorWriteNotPermitted];
}
}
   //写入特征时进入的回调
   - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{
  //判断特征是否有写的权限
  if (request.characteristic.properties & CBCharacteristicPropertyWrite) {
    // <--写入前需要先转化为CBMutableCharacteristic--> //
        CBMutableCharacteristic *characteristic =(CBMutableCharacteristic *)request.characteristic;
        characteristic.value = request.value;
  //写入成功的响应
    [myPeripheral respondToRequest:request withResult:CBATTErrorSuccess];
}else{
 //写入失败的响应
    [myPeripheral respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
}
}

当主设备订阅某个特征后,会持续调用读取特征的值的方法:(readValueForCharacteristic)
外设也就会持续调用方法:(didReceiveReadRequest)

BLE外设小结

作为外设想要对外传送数据时,可以配合Light Blue进行测试,如果能够获取到数据,那么说明数据已经成功的发送啦~

二、Be Central(当做主设备使用)

使用场景:通过iOS设备获取到外部蓝牙的广播信息

实现方式: iOS设备获取数据可以分为两种方式,连接或不连接蓝牙外设。不连接:可以通过扫描外部蓝牙设备,直接拿到蓝牙对外广播的数据包(advertisementData);连接:与蓝牙设备连接,进行数据的读取和写入等相关操作

使用限制: 通过不连接的方式获取数据包时,数据包内容必须符合蓝牙标准协议,iOS会对蓝牙发出的数据进行初步处理,如果与蓝牙协议不符,那么是无法从advertisementData中读到相关字段的,这是同安卓设备的不同之处。

使用方法及对应回调详解

1.引入头文件,添加代理,设置全局
<CBCentralManagerDelegate, CBPeripheralDelegate>两个都要引入
@interface CentralManager : NSObject
{
CBCentralManager *myCentral;
}
2.初始化中心设备
myCentral = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_global_queue(0, 0)];
3.实例化中心设备后进入回调方法
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;

在该方法中判断当前设备的蓝牙状态,如果蓝牙开启成功(CBManagerStatePoweredOn),那么开始扫描蓝牙设备

3.1扫描蓝牙设备的方法
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;
    4.扫描外设后进入的回调方法
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;

advertisementData:外设携带的广播数据
RSSI:信号强度
在该方法中可以根据设备名(peripheral.name)或一些信息(如:信号强度)对外设进行过滤,只关心我们需要的数据,并可以和对应设备进行连接

4.1连接蓝牙设备的方法
- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
5.连接蓝牙设备失败后进入的回调方法
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;
6.与蓝牙设备连接断开进入的回调方法
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;

可以在这5.6两个方法中重新调用4.1方法进行重连

7.与蓝牙设备连接成功进入的回调方法
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;

在这里再说明一下蓝牙外设发出数据的结构:外设(Peripheral)包含若干个服务(Service),每个服务又包含若干个特征(Characteristic),每个特征又包含若干个描述信息(Descriptor)

连接成功后,设置外设代理,并开始扫描外设的服务

7.1设置代理
[peripheral setDelegate:self];

7.2扫描外设服务的方法
- (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs;

serviceUUIDs :设置为nil,扫描全部服务,也可以指定扫描特定的服务,如果外设不存在服务,或指定的服务不存在,那么是不会进入到下面的代理方法中的

8.扫描到外设服务后进入的方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error;

扫描到服务后开始扫描特征

8.1扫描服务特征的方法
- (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs forService:(CBService *)service;

characteristicUUIDs:设置为nil,扫描全部特征,也可以指定扫描特定的特征,如果服务不存在特征,或指定的特征不存在,那么是不会进入到下面的代理方法中的

9.扫描到服务特征后进入的方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error

9.1扫描服务描述的方法
- (void)discoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic;
10.扫描到服务描述后进入的方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

注意: 此时如果想获取特征或描述的值,是获取不到的,获取值要使用读取方法,在下面会介绍到读取特征和描述的方法,另外只有在扫描到特征(描述)的回调方法里才能读取特征(描述)。

11.读取特征值的方法
- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic;

characteristic:应为方法9中扫描到的特征

11.1读取到特征的值后进入的回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;
12.读取描述值的方法
- (void)readValueForDescriptor:(CBDescriptor *)descriptor;

descriptor:应为方法10中扫描到的描述

12.1读取到描述的值后进入的回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error;
13.订阅特征的方法
- (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic;

enabled:YES 订阅  NO 取消订阅

订阅特征后,会自动持续的调用读取特征值的方法(方法11)

13.1订阅特征后进入的回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

/**
实例化蓝牙进入的回调方法

@param central 中心设备
 */
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{

switch (central.state) {
    case CBManagerStateUnknown:
        NSLog(@"蓝牙开启失败");
        break;
    case CBManagerStateResetting:
        NSLog(@"蓝牙开启失败");
        break;
    case CBManagerStateUnsupported:
        NSLog(@"蓝牙开启失败,当前设备不能设置为中心设备");
        break;
    case CBManagerStateUnauthorized:
        NSLog(@"蓝牙开启失败,当前设备不支持低功耗蓝牙模式");
        break;
    case CBManagerStatePoweredOff:
        NSLog(@"蓝牙开启失败,请启动蓝牙开关");
        break;
    case CBManagerStatePoweredOn:
        NSLog(@"蓝牙开启成功");
        [central scanForPeripheralsWithServices:nil options:nil];//扫描外设
        break;
    default:
        break;
}
}
/**
 蓝牙开启成功进入的回调,开始扫描外设

@param central 中心设备
@param peripheral 外设
@param advertisementData 外设携带的信息
@param RSSI 外设信号
 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
 if ([peripheral.name containsString:@"leoolin"]) {//过滤外设
            [central connectPeripheral:peripheral options:nil];
        }     
}
/**
外设连接失败的回调方法

@param central 中心设备
@param peripheral 外设
@param error 错误信息
*/
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
}
/**
与外设断开连接的回调方法

@param central 中心设备
@param peripheral 外设
@param error 错误信息
*/
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
}
/**
 与外设连接成功进入的回调方法,并开始扫描服务

@param central 中心设备
@param peripheral 外设
*/
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
      [peripheral discoverServices:nil];
}
/**
 扫描到服务进入的回调,并开始扫描特征

 @param peripheral 外设
 @param error 错误信息
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
     for (CBService *service in peripheral.services) {//取出查询的服务
        [peripheral discoverCharacteristics:nil forService:service];
      //  [peripheral readValueForCharacteristic:characteristic];
      //  [peripheral setNotifyValue:YES forCharacteristic:characteristic];
           }
}
/**
扫描到特征进入的回调,并开始扫描特征的描述

@param peripheral 外设
@param service 服务
@param error 错误信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
for (CBCharacteristic *characteristic in service.characteristics){
    [peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}

/**
 获取到描述进入的回调方法


 @param peripheral 外设
 @param characteristic 特征
 @param error 错误信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
  [peripheral readValueForDescriptor:descriptor];
}
/**
读取到特征的值时进入的回调
(readValueForCharacteristic的回调)

@param peripheral 外设
@param characteristic 特征
@param error 错误信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    NSLog(@"读取特征:%@",characteristic.value);
}

/**
读取到描述的值时进入的回调
 (readValueForDescriptor的回调)

 @param peripheral 外设
 @param descriptor 描述
 @param error 错误信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error
 {
    NSLog(@"descriptor value:%@",descriptor.value);
}

相关文章

网友评论

    本文标题:iOS BLE蓝牙基础详解(两种模式)

    本文链接:https://www.haomeiwen.com/subject/vsilextx.html