iOS CoreBluetooth

作者: HelloAda | 来源:发表于2018-05-07 22:37 被阅读94次

    CoreBluetooth

    在iOS和Mac应用中,CoreBluetooth框架用来与BLE(低功耗蓝牙)设备通信,我们的程序可以搜索并与低功耗蓝牙设备通信,如手环设备,甚者是其他iOS设备。这个框架支持蓝牙4.0的基本操作,隐藏了实现细节,我们可以方便的使用它与BLE设备交互。

    蓝牙通信中的角色

    在BLE通信中,主要有两个角色: CentralPeripheralPeripheral端是提供数据的一方,类似于服务端,Central端是使用Peripheral端提供的数据完成特定任务,例如展示数据。其实相当于客户端-服务端架构。

    在本文主要介绍iOS设备(手机)作为Central端,BLE设备(如手环)作为Peripheral端的使用。

    Peripheral端可以以广告包的形式来广播一些数据,比如peripheral设备的名字,设备UUID等。通常都是以16进制数据形式,并且字节数较少。

    Central端可以扫描到到带有广告包信息的Peripheral设备。同时一个Central端与Peripheral端建立成功建立连接后,数据的广播以及接收也需要一定的数据结构来表示。而服务就是这样一种数据结构。一个Peripheral端可能包含一个或多个服务,每个服务又是由一个或者多个特征组成的。Central端可以发现Peripheral端提供的完整的服务及特性的集合。一个Central也可以读写Peripheral端的服务特性的值。

    TreeOfServicesAndCharacteristics_Remote_2x.png

    手机作为Central端的操作

    Central端,本地Central设备由CBCentralManager对象表示。这个对象用于管理发现与连接Peripheral设备(CBPeripheral对象)的操作,包括扫描、查找和连接。当与Peripheral设备交互时,我们主要是在处理它的服务及特性,下文中出现的Central端指iPhone,Peripheral端指BLE设备。

    Central端的主要操作:

    1. 初始化一个Central端的管理对象(CBCentralManager)
    2. 搜索并连接正在广告的Peripheral设备(CBPeripheral)
    3. 连接成功后,获取所需的服务(CBService)和特征(CBCharacteristic)。
    4. 操作特征,如读取、写入、通知,来获取所需数据并进行处理。

    初始化Central端管理对象

    /*
    指定当前类为代理对象,需要实现CBCentralManagerDelegate
    queue为nil 代表使用主队列来发送事件
    options为管理器的配置,可以在CBCentralManagerConstants.h看到所需key
    */
    _centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
    

    当初始化CBCentralManager后,会调用代理方法centralManagerDidUpdateState:来判断当前的Central端是否支持BLE

    - (void)centralManagerDidUpdateState:(CBCentralManager *)central
    {
        //其他状态可以在CBManager.h查看
        switch (central.state) {
            case CBCentralManagerStatePoweredOn:
                NSLog(@"蓝牙可以使用");
                break;
            default:
                break;
        }
    }
    
    

    搜索正在广告的Peripheral设备

    Central端要发现Peripheral设备需要调用CBCentralManager实例的scanForPeripheralsWithServices:options:方法来发现正在广告的Peripheral设备

    /*
    扫描周围Peripheral设备
    services:传nil则代表扫描周围所有正在广告的设备,传UUID对象数组则扫描指定的设备
    options:扫描的配置 详情见CBCentralManagerConstants.h
    */
    [self.centralManager scanForPeripheralsWithServices:nil options:nil];
    
    

    调用这个方法后,CBCentralManager对象在每次发现设备时都会调用代理对象的centralManager:didDiscoverPeripheral:advertisementData:RSSI:

    /*
    发现设备回调
    peripheral:发现的设备
    advertisementData:广告包数据
    RSSI:信号强度
    */
    - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
    {
        NSLog(@"发现设备: %@", peripheral);
    }
    

    连接Peripheral设备

    现在我们已经发现了周围正在广告的设备了,所以需要连接到我们需要的设备了。
    可以调用CBCentralManager实例的connectPeripheral:options:方法来连接Peripheral设备。

    /*
    连接Peripheral设备
    peripheral:你需要的设备
    options:连接配置 详见CBCentralManagerConstants.h
    */
    [self.centralManager connectPeripheral:peripheral options:nil];
    

    如果连接成功则代理对象会调用centralManager:didConnectPeripheral:方法,在这个方法里我们可以来处理与Peripheral设备交互之前的一些操作,比如设置Peripheral对象的代理(CBPeripheralDelegate),确保可以接收到回调。

    //设备连接成功
    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
       [self.centralManager stopScan];//停止扫描
        self.peripheral = peripheral;
        self.peripheral.delegate = self;
    }
    

    发现所连接的Peripheral设备的服务和特征

    Central端与Peripheral端连接后,我们可以通过Peripheral设备提供的服务和特征来获取更多数据,这个数据比Peripheral设备的广告数据要多。调用peripheral实例的discoverServices:方法来查找服务

    /*
    传nil表示查找peripheral的所有服务
    一般情况下我们会指定感兴趣的服务的UUID数组
    */
    [peripheral discoverServices:nil];
    

    当上面的方法被调用了,peripheral会调用代理对象的peripheral:didDiscoverServices:方法

    //peripheral.services数组中是所有找到的服务
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
    {
        for (CBService *service in peripheral.services) {
            NSLog(@"发现服务: %@", service);
        }
    }
    

    现在我们已经找到了服务,接下来就是查找服务中的特征了,我们需要调用peripheral实例的discoverCharacteristics:forService:方法

    //查找该服务service的特征
    [peripheral discoverCharacteristics:nil forService:service];
    

    当调用这个方法后,peripheral会调用代理对象的peripheral:didDiscoverCharacteristicsForService:error:方法

    //service.characteristics数组是该服务下找到的特征
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
    {
        for (CBCharacteristic *characteristic in service.characteristics)
        {
            NSLog(@"发现特征:%@", characteristic);
        }
    }
    

    处理特征的值

    通常我们获取特征之后,CBCharacteristicCBCharacteristicProperties属性对应的值,这个特征告诉我们需要怎么去获取值,一般来说是读取、写入、通知这3种形式最多。可以在CBCharacteristic.h文件查看更多内容

    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(10_9, 6_0)   = 0x100,
        CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0) = 0x200
    };
    
    读取特征的值

    一个特征包含一个单一的值,我们要从特征中获取这个值,需要调用peripheral实例的readValueForCharacteristic:方法

    [peripheral readValueForCharacteristic:characteristic];
    

    获取成功后,peripheral会调用代理对象的peripheral:didUpdateValueForCharacteristic:error:方法来获取characteristic的值。

    //获取读取的特征值,通常来说这个方法是获取值的唯一途径。
    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    {
        NSLog(@"characteristic.value = %@", characteristic.value);
    }
    
    写入特征的值

    有些特征是允许写入值的,可以调用peripheral 实例的writeValue:forCharacteristic:type:方法来写入值。

    Byte byte[] = {0x01,0x02,0x03,0x04};
    NSData *data = [NSData dataWithBytes:byte length:4];
    [peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
    

    写入特征值的时候我们需要指定是否有返回值,比如上面是CBCharacteristicWriteWithResponse代表我们需要有返回值。这样peripheral会调用代理对象的peripheral:didWriteValueForCharacteristic:error:方法

    //写入特征,如果写入失败可以在这个方法中处理
    - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
        NSLog(@"characteristic.value = %@",characteristic.value);
    }
    
    订阅特征值

    特征值有一些是会改变的,我们需要通过订阅的方式来获取它们,peripheral实例调用setNotifyValue:forCharacteristic:方法来订阅需要的特征。

    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
    

    当我们订阅具有通知属性的特征时,peripheral会调用代理对象的peripheral:didUpdateNotificationStateForCharacteristic:error:方法来获取值。

    //获取订阅的特征值,通常来说这个方法是获取值的唯一途径。
    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    {
        NSLog(@"characteristic.value = %@", characteristic.value);
    }
    

    Core Bluetooth框架已经为我们封装了蓝牙通信的底层实现,我们只需要按照上面的步骤就可以来构建一个基本的蓝牙通讯过程(其实过程还是比较复杂的...)

    后台执行模式

    应用在后台时会有诸多资源的限制,蓝牙也不例外。我们可以在Info.plist文件中设置Core Bluetooth后台执行模式,让应用在后台可以继续执行一些蓝牙相关的任务。我们需要在Info.plist文件添加UIBackgroundModes键,同时添加以下两个值或其中之一:

    • bluetooth-central
    • bluetooth-peripheral
    bluetooth-central模式

    设置了bluetooth-central值,则我们的应用在后台时,仍然可以使用连接到Peripheral设备,并且也能处理相应的特征值。系统在CBCentralManagerDelegateCBPeripheralDelegate的代理方法被执行时,会唤醒应用来处理事件。

    但是也有一些不同,在扫描Peripheral设备时,

    • CBCentralManagerScanOptionAllowDuplicatesKey扫描选项会被忽略,同一个Peripheral端的多个事件会变成一个。
    • 扫描间隔会增加。
    • 扫描的时候scanForPeripheralsWithServices: options:的第一个值,不能传nil以扫描周围所有设备,必须传入对应的UUID数组来扫描特定的设备,否则不会扫描。。

    减少电池损耗

    • 只有当需要的时候才扫描设备,并且最好指定扫描设备的UUID就只扫描指定设备
    • 只有当扫描需要的时候才使用CBCentralManagerScanOptionAllowDuplicatesKey选项
    • 获取服务和特征的时候不要全部获取,应该只获取指定的特征和服务
    • 特征值经常改变的情况下,最好采用订阅的方式。
    • 不再需要数据的时候即使断开。

    另外还有一点,但是有利弊,如果BLE设备电池容量不是很大的情况下,但是又需要使用很久,可以使用广告包的形式来发送数据,不用与手机建立连接,但是这个有一个缺点,手机需要一直扫描。。感觉手机电池应该损耗比较大吧,希望有清楚的能指导一二。

    其他

    目前这些知识,已经能满足我自己在项目的使用了,但是使用代理的方式来实现蓝牙,在多个页面的时候,始终感觉有点麻烦,看了BabyBluetooth的实现方式后,确实逻辑就比较清晰了,但是里面有很多也用不上,就自己简单实现了一个手机作为Central端一对一连接的库DSBluetooth用来满足自己的需求,后续有需求的话,再慢慢完善这个库的。

    相关文章

      网友评论

        本文标题:iOS CoreBluetooth

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