美文网首页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