iOS 蓝牙

作者: 海边的遐想 | 来源:发表于2016-04-14 18:10 被阅读644次

    随着蓝牙低功耗技术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.调用外围设备管理器的对象方法,添加服务到外围设备上

    1.-(void)addService:(CBService *)service;

    //外围设备管理器开始广播服务

    -(void)startAdvertising:(NSDictionary *)dict;

    4.在外围设备管理器的代理方法,处理与中央设备的交互

    外围设备管理器启动服务的相关代理方法:

    /* 外围设备管理器状态发生改变后调用,比如外围设备打开蓝牙的时候 */

    -(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

    #define kPeripheralName         @"Liuting's Device"

    //外围设备名称,自定义

    #define kServiceUUID            @"FFA0-FFB0" //服务的UUID,自定义#define kCharacteristicUUID     @"FFCC-FFDD" //特征的UUID,自定义

    @interface PeripheralViewController ()

    @property(strong,nonatomic) CBPeripheralManager *peripheralManager;

    /* 外围设备管理器 */

    @property(strong,nonatomic)NSMutableArray*centralM;

    /* 订阅的中央设备 */

    @property(strong,nonatomic) CBMutableCharacteristic *characteristicM;

    /* 特征 */

    @end

    @implementation PeripheralViewController

    - (void)viewDidLoad{

    [superviewDidLoad];

    self.centralM= [NSMutableArrayarray];

    //创建外围设备管理器

    self.peripheralManager= [[CBPeripheralManager alloc] initWithDelegate:selfqueue:nil];

    }

    #pragma mark - UI事件

    /* 点击更新特征值 */

    - (IBAction)changeCharacteristicValue:(id)sender {

    //特征值,这里是更新特征值为当前时间

    NSString*valueStr = [NSStringstringWithFormat:@"%@",[NSDatedate]];

    NSData*value = [valueStr dataUsingEncoding:NSUTF8StringEncoding];

    //更新特征值

    [self.peripheralManagerupdateValue:value

    forCharacteristic:self.characteristicM

    onSubscribedCentrals:nil];

    }

    #pragma mark - 私有方法

    /* 创建特征、服务并添加服务到外围设备 */

    - (void)addMyService{

    /*1.创建特征*/

    //创建特征的UUID对象

    CBUUID*characteristicUUID= [CBUUIDUUIDWithString:kCharacteristicUUID];

    /* 创建特征

    * 参数

    * uuid:特征标识

    * properties:特征的属性,例如:可通知、可写、可读等

    * value:特征值

    * permissions:特征的权限

    */

    CBMutableCharacteristic *characteristicM =

    [[CBMutableCharacteristic alloc]initWithType:characteristicUUIDproperties:CBCharacteristicPropertyNotifyvalue:nilpermissions:CBAttributePermissionsReadable];

    self.characteristicM= characteristicM;

    //创建服务UUID对象

    CBUUID*serviceUUID= [CBUUIDUUIDWithString:kServiceUUID];

    //创建服务

    CBMutableService *serviceM = [[CBMutableService alloc] initWithType:serviceUUIDprimary:YES];

    //设置服务的特征

    [serviceM setCharacteristics:@[characteristicM]];

    //将服务添加到外围设备

    [self.peripheralManageraddService:serviceM];

    }

    #pragma mark - CBPeripheralManager代理方法

    /* 外围设备状态发生变化后调用 */

    - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{

    //判断外围设备管理器状态

    switch(peripheral.state) {

    caseCBPeripheralManagerStatePoweredOn:

    {

    NSLog(@"BLE已打开.");

    //添加服务

    [selfaddMyService];

    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.peripheralManagerstartAdvertising: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.centralMcontainsObject:central]) {

    [self.centralMaddObject: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 *)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 *)services;/* 寻找指定服务下的特征,特征数组也是传入特征的唯一标识CBUUID */

    -(void)discoverCharacteristics:(NSArray *)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种方式:

    /* 写入类型,决定要不要调用代理方法 */

    typedefNS_ENUM(NSInteger, CBCharacteristicWriteType) {

    CBCharacteristicWriteWithResponse =0,//有回调的写入

    CBCharacteristicWriteWithoutResponse//没回调的写入

    };

    下面是设备作为中央设备的实例:

    #import "CentralViewController.h"

    #import

    #define kPeripheralName         @"Liuting's Device" //外围设备名称#define kServiceUUID            @"FFA0-FFB0" //服务的UUID

    #define kCharacteristicUUID     @"FFCC-FFDD" //特征的UUID

    @interface CentralViewController ()

    @property(strong,nonatomic) CBCentralManager *centralManager;/* 中央设备管理器 */

    @property(strong,nonatomic)NSMutableArray*peripherals;/* 连接的外围设备 */

    @end

    @implementation CentralViewController

    #pragma mark - UI事件

    - (void)viewDidLoad{

    [superviewDidLoad];

    self.peripherals= [NSMutableArrayarray];

    //创建中心设备管理器并设置当前控制器视图为代理

    self.centralManager= [[CBCentralManager alloc] initWithDelegate:selfqueue:nil];

    }

    #pragma mark - CBCentralManager代理方法

    /* 中央设备管理器状态更新后调用 */

    - (void)centralManagerDidUpdateState:(CBCentralManager *)central{

    switch(central.state) {

    caseCBPeripheralManagerStatePoweredOn:

    NSLog(@"BLE已打开.");

    //扫描外围设备

    [central scanForPeripheralsWithServices:niloptions: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.nameisEqualToString:kPeripheralName]) {

    //添加保存外围设备,因为在此方法调用完外围设备对象就会被销毁

    if(![self.peripheralscontainsObject:peripheral]){

    [self.peripheralsaddObject:peripheral];

    }

    NSLog(@"开始连接外围设备...");

    [self.centralManagerconnectPeripheral:peripheral options:nil];

    }

    }

    /* 中央设备管理器成功连接到外围设备后调用 */

    - (void)centralManager:(CBCentralManager *)central

    didConnectPeripheral:(CBPeripheral *)peripheral

    {

    NSLog(@"连接外围设备成功!");

    //停止扫描

    [self.centralManagerstopScan];

    //设置外围设备的代理为当前视图控制器

    peripheral.delegate=self;

    //外围设备开始寻找服务

    [peripheral discoverServices:@[[CBUUIDUUIDWithString: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= [CBUUIDUUIDWithString:kServiceUUID];

    CBUUID*characteristicUUID= [CBUUIDUUIDWithString:kCharacteristicUUID];

    for(CBService *serviceinperipheral.services) {

    if([service.UUIDisEqual:serviceUUID]){

    //外围设备查找指定服务中的特征,characteristics为nil,表示寻找所有特征

    [peripheral discoverCharacteristics:nilforService:service];

    }

    }

    }

    /* 外围设备寻找到特征后调用 */

    - (void)peripheral:(CBPeripheral *)peripheral

    didDiscoverCharacteristicsForService:(CBService *)service

    error:(NSError*)error

    {

    NSLog(@"已发现可用特征...");

    //遍历服务中的特征

    CBUUID*serviceUUID= [CBUUIDUUIDWithString:kServiceUUID];

    CBUUID*characteristicUUID= [CBUUIDUUIDWithString:kCharacteristicUUID];

    if([service.UUIDisEqual:serviceUUID]) {

    for(CBCharacteristic *characteristicinservice.characteristics) {

    if([characteristic.UUIDisEqual:characteristicUUID]) {

    //情景一:通知

    /* 找到特征后设置外围设备为已通知状态(订阅特征):

    * 调用此方法会触发代理方法peripheral:didUpdateValueForCharacteristic:error:

    * 调用此方法会触发外围设备管理器的订阅代理方法

    */

    [peripheral setNotifyValue:YESforCharacteristic:characteristic];

    //情景二:读取

    //调用此方法会触发代理方法peripheral:didUpdateValueForCharacteristic:error:

    //[peripheral readValueForCharacteristic:characteristic];

    }

    }

    }

    }

    /* 外围设备读取到特征值后调用 */

    - (void)peripheral:(CBPeripheral *)peripheral

    didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic

    error:(NSError*)error

    {

    if(characteristic.value) {

    NSString*value = [[NSStringalloc] initWithData:characteristic.value

    encoding:NSUTF8StringEncoding];

    NSLog(@"读取到特征值:%@",value);

    }else{

    NSLog(@"未发现特征值.");

    }

    }@end

    五、蓝牙后台运行

    除非去申请后台权限,否则app都是只在前台运行的,程序在进入后台不久便会切换到挂起状态。挂起后,程序将无法再接收任何蓝牙事件。

    中央设备管理器连接外围设备的方法中的options属性,可以设置如下字典值:

    ·CBConnectPeripheralOptionNotifyOnConnectionKey:

    在连接成功后,程序被挂起,给出系统提示。

    ·CBConnectPeripheralOptionNotifyOnDisconnectionKey:

    在程序挂起,蓝牙连接断开时,给出系统提示。

    ·CBConnectPeripheralOptionNotifyOnNotificationKey:

    在程序挂起后,收到 peripheral 数据时,给出系统提示。

    设置蓝牙后台模式:

    ·添加info.plist字段Required background nodes

    ·在该字段下添加字符串值:

    oApp communicates using CoreBluetooth:表示支持设备作为中央设备后台运行

    oApp shares data using CoreBluetooth:表示支持设备作为外围设备后台运行

    后台运行设置info.plist

    设备作为中央设备的后台运行和前台运行区别:

    ·会将发现的多个外围设备的广播数据包合并为一个事件,然后每找到一个外围设备都会调用发现外围设备的代理方法

    ·如果全部的应用都在后台搜索外围设备,那么每次搜索的时间间隔会更大。这会导致搜索到外围设备的时间变长

    设备作为外围设备的后台运行和前台运行区别:

    ·在广播时,广播数据将不再包含外围设备的名字

    ·外围设备只能被明确标识了搜索服务UUID的iOS设备找到

    ·如果所有应用都在后台发起广播,那么发起频率会降低

    相关文章

      网友评论

        本文标题:iOS 蓝牙

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