zoukankan      html  css  js  c++  java
  • iOS学习笔记30-系统服务(三)蓝牙

    一、蓝牙

    随着蓝牙低功耗技术BLE(Bluetooth Low Energy)的发展,蓝牙技术正在一步步成熟,如今的大部分移动设备都配备有蓝牙4.0,相比之前的蓝牙技术耗电量大大降低。

    在iOS中进行蓝牙传输常用的框架有如下几种:
    1. GameKit.framework
      iOS7之前的蓝牙框架,只可用于同应用之间的蓝牙传输。
    2. MultipeerConnectivity.framework
      iOS7开始引入的蓝牙框架,用于取代GameKit,也有缺陷,仅支持iOS设备之间蓝牙传输。
    3. CoreBluetooth.framework
      功能强大的蓝牙框架,但要求设备必须支持蓝牙4.0,可以支持所有设备蓝牙传输,只要该设备支持蓝牙4.0。

    应用的比较多的是CoreBluetooth框架,这里就选择CoreBluetooth框架来讲。

    二、CoreBluetooth

    当前BLE应用相当广泛,不再仅仅是两个设备之间的数据传输,它还有很多其他应用市场,例如室内定位、无线支付、智能家居等等,这也使得CoreBluetooth成为当前最热门的蓝牙技术。
    我的理解中,CoreBluetooth蓝牙通信过程和网络通信过程比较类似。

    CoreBluetooth中,蓝牙传输都分为两个部分:
    1. 外围设备CBPeripheral
      负责发布并广播服务,告诉周围的中央设备它的可用服务和特征,类似于网络通信中的服务端。
    2. 中央设备CBCentral
      负责和外围设备建立连接,一旦连接成功就可以使用外围设备的服务和特征,类似于网络通信中的客户端

    外围设备和中央设备交互的桥梁是服务特征,两个都有一个唯一标识CBUUID来确定一个服务或者特征:

    • 服务CBService
      中央设备只有通过服务才能与外围设备进行数据传输,类似于客户端通过网址URL才能与服务器连接一样
    • 特征CBCharacteristic
      每个服务可以拥有多个特征,中央设备必须订阅外围设备服务的特征值,才能获取外围设备的数据,类似于GET请求可以请求获取服务器数据,POST请求可以向服务器传输数据一样。

    三、设备作为外围设备

    设备作为外围设备的创建步骤:
    1. 创建外围设备管理器CBPeripheralManager,并设置代理
    2. 创建一个特征CBCharacteristic,绑定一个CBUUID,设置特征属性
    3. 创建一个服务CBService,绑定一个CBUUID,设置服务的特征
    4. 调用外围设备管理器的对象方法,添加服务到外围设备上

      -(void)addService:(CBService *)service;
    5. 外围设备管理器开始广播服务

      -(void)startAdvertising:(NSDictionary *)dict;
    6. 在外围设备管理器的代理方法,处理与中央设备的交互

    外围设备管理器启动服务的相关代理方法:
    /* 外围设备管理器状态发生改变后调用,比如外围设备打开蓝牙的时候 */
    - (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;/* 写请求 */
    外围设备管理器CBPeripheralManager的常用对象方法:
    /* 添加服务 */
    - (void)addService:(CBService *)service;
    /* 开启广播服务,dict设置设备信息 */
    - (void)startAdvertising:(NSDictionary *)dict;
    /* 更新特征值,centrals为空,表示对所有连接的中央设备通知 */
    - (void)updateValue:(NSData *)value /* 特征的特征值 */
            forCharacteristic:(CBCharacteristic *)characteristic /* 特征 */
            onSubscribedCentrals:(NSArray *)centrals;/* 需要通知更新特征值的中央设备 */
    下面是设备作为外围设备的实例:
    #import "PeripheralViewController.h"
    #import <CoreBluetooth/CoreBluetooth.h>
    
    #define kPeripheralName         @"Liuting's Device" //外围设备名称,自定义
    #define kServiceUUID           @"FFA0-FFB0" //服务的UUID,自定义
    #define kCharacteristicUUID     @"FFCC-FFDD" //特征的UUID,自定义
    
    @interface PeripheralViewController ()<CBPeripheralManagerDelegate>
    @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的规范,中央设备就可以与之连接并寻找服务和订阅特征。

    设备作为中央设备的创建步骤:
    1. 创建中央设备管理器对象CBCentralManager,设置代理
    2. 扫描外围设备,发现外围设备CBPeripheral进行连接,保持连接的外围设备
    3. 在连接外围设备成功的代理方法中,设置外围设备的代理,调用外围设备的对象方法寻找服务
    4. 查找外围设备的服务和特征,查找到可用特征,则读取特征数据。
    • 记住这里是外围设备对象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<CBUUID *> *)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;
    外围设备CBPeripheral的常用对象方法:
    /* 寻找服务,传入的是服务的唯一标识CBUUID */
    - (void)discoverServices:(NSArray<CBUUID *> *)services;
    /* 寻找指定服务下的特征,特征数组也是传入特征的唯一标识CBUUID */
    - (void)discoverCharacteristics:(NSArray<CBUUID *> *)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;/* 写入类型 */
    写入类型目前只有2种方式:
    /* 写入类型,决定要不要调用代理方法 */
    typedef NS_ENUM(NSInteger, CBCharacteristicWriteType) { 
        CBCharacteristicWriteWithResponse = 0, //有回调的写入
        CBCharacteristicWriteWithoutResponse //没回调的写入
    };
    下面是设备作为中央设备的实例:
    #import "CentralViewController.h"
    #import <CoreBluetooth/CoreBluetooth.h>
    
    #define kPeripheralName         @"Liuting's Device" //外围设备名称
    #define kServiceUUID           @"FFA0-FFB0" //服务的UUID
    #define kCharacteristicUUID     @"FFCC-FFDD" //特征的UUID
    
    @interface CentralViewController ()<CBCentralManagerDelegate,CBPeripheralDelegate>
    @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
      在程序挂起后,收到 peripheral 数据时,给出系统提示。
    设置蓝牙后台模式:
    • 添加info.plist字段Required background nodes
    • 在该字段下添加字符串值:
    • App communicates using CoreBluetooth:表示支持设备作为中央设备后台运行
    • App shares data using CoreBluetooth:表示支持设备作为外围设备后台运行

    设备作为中央设备的后台运行和前台运行区别:
    • 会将发现的多个外围设备的广播数据包合并为一个事件,然后每找到一个外围设备都会调用发现外围设备的代理方法
    • 如果全部的应用都在后台搜索外围设备,那么每次搜索的时间间隔会更大。这会导致搜索到外围设备的时间变长
    设备作为外围设备的后台运行和前台运行区别:
      • 在广播时,广播数据将不再包含外围设备的名字
      • 外围设备只能被明确标识了搜索服务UUID的iOS设备找到
      • 如果所有应用都在后台发起广播,那么发起频率会降低
  • 相关阅读:
    2015531 网络攻防 Exp1 PC平台逆向破解(5)M
    2017-2018-1 20155331 嵌入式C语言
    20155330 《网络对抗》 Exp9 web安全基础实践
    20155330 《网络对抗》 Exp8 Web基础
    20155330 《网络对抗》 Exp7 网络欺诈防范
    20155330 《网络对抗》 Exp6 信息搜集与漏洞扫描
    20155330 《网络对抗》 Exp5 MSF基础应用
    20155330 《网络攻防》 Exp4 恶意代码分析
    20155330 《网络攻防》 Exp3 免杀原理与实践
    20155330 《网络对抗》 Exp2 后门原理与实践
  • 原文地址:https://www.cnblogs.com/ming1025/p/6069231.html
Copyright © 2011-2022 走看看