iOS蓝牙开发(CoreBluetooth)

目前在iOS中蓝牙开发框架主要有以下几种

  • GameKit.framework:iOS7之前的蓝牙通讯框架,从iOS7开始过期.
  • MultipeerConnectivity.framework:iOS7开始引入的新的蓝牙通讯开发框架,用于取代GameKit。
  • CoreBluetooth.framework:功能强大的蓝牙开发框架,要求设备必须支持蓝牙4.0。
  • 前两个框架使用起来比较简单,但是缺点也比较明显:仅仅支持iOS设备,传输内容仅限于沙盒或者照片库中用户选择的文件,并且第一个框架只能在同一个应用之间进行传输(一个iOS设备安装应用A,另一个iOS设备上安装应用B是无法传输的)。CoreBluetooth就摆脱了这些束缚,它不再局限于iOS设备之间进行传输,你可以通过iOS设备向Android、Windows Phone以及其他安装有蓝牙4.0芯片的智能设备传输,因此也是目前智能家居、无线支付等热门智能设备所推崇的技术。本文主要介绍CoreBluetooth的相关开发流程.

CoreBluetooth开发模式

CoreBluetooth设计类似于客户端-服务器端的设计,peripheral和central, 可以理解成外设和中心。对应他们分别有一组相关的API和类.


iOS蓝牙开发(CoreBluetooth)_第1张图片
CoreBluetooth架构
  • 左侧叫做中心模式,以app作为中心,连接其他的外设的,而右侧称为外设模式,使用手机作为外设别其他中心设备操作的场景。
  • 服务和特征,特征的属性(service and characteristic):
    蓝牙设备添加若干服务,服务內添加若干特征,特征就是具体键值对,提供对数据的读取和写。蓝牙中心和外设数据的交换基于服务的特征.


    iOS蓝牙开发(CoreBluetooth)_第2张图片
    外设和服务特征的关系

中心的开发

  • 流程
  1. 建立中心角色
  2. 扫描外设(discover)
  3. 连接外设(connect)
  4. 扫描外设中的服务和特征(discover)
    • 4.1 获取外设的services
    • 4.2 获取外设的Characteristics,获取Characteristics的值,获取 Characteristics的Descriptor和Descriptor的值
  5. 与外设做数据交互(explore and interact)
  6. 订阅Characteristic的通知
  7. 断开连接(disconnect)
  • 步骤
    CoreBluetooth中不管是中心还是外设的开发,均是上面的流程,通过一步步的代理回调实现的,按照流程跟着走,一步一步实现代理.


    //初始化
    _centralManager = [[CBCentralManager alloc] initWithDelegate:self
    queue:nil
    options:@{CBCentralManagerOptionRestoreIdentifierKey:kRestoreIdentifierKey}];
    //监听中心设备状态
    -(void)centralManagerDidUpdateState:(CBCentralManager *)central{
    if (central.state == CBCentralManagerStatePoweredOn) {
    [self writeToLogWithText:@"中心设备已打开"];
    [_centralManager scanForPeripheralsWithServices:nil options:nil];
    } //中心设备CBCentralManagerStatePoweredOn状态下就可以开始搜索外设了
    }
    -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
    //搜索到指定的外设就可以开始连接外设了
    [_centralManager connectPeripheral:peripheral options:nil];
    }
    -(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    //连接成功后开始搜索服务
    [peripheral discoverServices:nil];
    }
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    //搜索服务成功后查找指定服务中的特征
    [peripheral discoverCharacteristics:nil forService:service];
    }
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(nonnull CBService *)service error:(nullable NSError *)error{
    //发现可用特征后,就可以进行相应的操作了,主要有以下三种
    //情景一:读取
    if (characteristic.properties & CBCharacteristicPropertyRead) {
    if ([characteristic.UUID.UUIDString isEqualToString:kReadUUID]) {
    [peripheral readValueForCharacteristic:characteristic];
    if (characteristic.value) {
    NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
    NSLog(@"读取到特征值:%@",value);
    }
    }
    }

      //情景二:通知
      if (characteristic.properties & CBCharacteristicPropertyNotify) {
          if ([characteristic.UUID.UUIDString isEqualToString:kNotifyUUID] || [characteristic.UUID.UUIDString isEqualToString:kWriteUUID]) {
              _curCharacter = characteristic;
              [peripheral setNotifyValue:YES forCharacteristic:characteristic];
              [self writeToLogWithText:@"已订阅特征通知"];
          }
      }
      
      //情景二:写数据
      if (characteristic.properties & CBCharacteristicPropertyWrite) {
          if ([characteristic.UUID.UUIDString isEqualToString:kWriteUUID]) {
              [peripheral writeValue:[@"hello,外设" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
              [self writeToLogWithText:@"写数据给外设"];
              
              _curPeripherals = peripheral;
              _curCharacter = characteristic;
          }
      }
    

}
//根据不同的场景,会调用以下三个方法
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
}

外设的开发

  • 流程
     
       
    1. 打开peripheralManager,设置peripheralManager的委托
  1. 创建characteristics,characteristics的description 创建service,把characteristics添加到service中,再把service添加到peripheralManager中
  2. 开启广播advertising
  3. 对central的操作进行响应
    • 4.1 读characteristics请求
    • 4.2 写characteristics请求
    • 4.4 订阅和取消订阅characteristics
  • 步骤


    //初始化
    _peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
    //启动外设
    -(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
    switch (peripheral.state) {
    case CBPeripheralManagerStatePoweredOn:
    //CBPeripheralManagerStatePoweredOn下添加服务和特征
    [self setupService];
    }
    //创建服务,特征并添加服务到外围设备
    -(void)setupService{

    //可通知的特征
    // CBUUID *characteristicUUID = [CBUUID UUIDWithString:kNotifyUUID];
    // _characteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

    //可读写的特征
    CBUUID *UUID2 = [CBUUID UUIDWithString:kWriteUUID];
    _characteristic = [[CBMutableCharacteristic alloc] initWithType:UUID2 properties:CBCharacteristicPropertyWrite|CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsWriteEncryptionRequired];
    //
    //只读的特征
    // CBUUID *UUID3 = [CBUUID UUIDWithString:kReadUUID];
    // NSData *characteristicValue = [@"aaron才" dataUsingEncoding:NSUTF8StringEncoding];
    // _characteristic = [[CBMutableCharacteristic alloc] initWithType:UUID3 properties:CBCharacteristicPropertyRead value:characteristicValue permissions:CBAttributePermissionsReadable];

    CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
    _service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
    [_service setCharacteristics:@[_characteristic]];

    [_peripheralManager addService:_service];
    }
    //添加服务后开始广播,等待中心设备的连接
    -(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(nullable NSError *)error{
    NSDictionary *dict = @{CBAdvertisementDataLocalNameKey:kPeripheralName};
    [_peripheralManager startAdvertising:dict];
    [self writeToLogWithText:@"向外围设备添加了服务"];
    }
    根据中心设备的响应,外设在代理中收到中心发来的信息,
    //订阅特征
    -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;
    //取消订阅
    -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;
    //中心设备读外设数据
    -(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request;
    //收到中心写来的数据
    -(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests;

UIBackgroundModes下的蓝牙数据传输

CoreBluetooth提供了非常友好的Background支持.按一下步骤,经过测试,app进入后台后,也能收到外设特征更新的值,据WWDC2013视频介绍,就算因为内存紧张,app在后台被杀掉,系统也会自动帮我们重新启动app进行蓝牙数据传输,不过这个就没测试到,不知道有没有人做过这方便的研究.

  • 设置info.plist
    


UIBackgroundModes

bluetooth-central
bluetooth-peripheral

  • 初始化方式中添加``options:@{CBCentralManagerOptionRestoreIdentifierKey:kRestoreIdentifierKey}``
    


_centralManager = [[CBCentralManager alloc] initWithDelegate:self
queue:nil
options:@{CBCentralManagerOptionRestoreIdentifierKey:kRestoreIdentifierKey}];

  • (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary *)dict{

// NSArray *scanServices = dict[CBCentralManagerRestoredStateScanServicesKey];
// NSArray *scanOptions = dict[CBCentralManagerRestoredStateScanOptionsKey];

NSArray *peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey];
for (CBPeripheral *peripheral in peripherals) {
    [self.peripherals addObject:peripheral];
    peripheral.delegate = self;
}

}

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

    if (central.state == CBCentralManagerStatePoweredOn) {
    [self writeToLogWithText:@"中心设备已打开"];
    [_centralManager scanForPeripheralsWithServices:nil options:nil];

      //03,检查是否restore connected peripherals
      for (CBPeripheral *peripheral in _peripherals) {
          if (peripheral.state == CBPeripheralStateConnected) {
              NSUInteger serviceIdx = [peripheral.services indexOfObjectPassingTest:^BOOL(CBService * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                  return [obj.UUID isEqual:kServiceUUID];
              }];
              
              if (serviceIdx == NSNotFound) {
                  [peripheral discoverServices:@[kServiceUUID]];
                  continue;
              }
              
              CBService *service = peripheral.services[serviceIdx];
              NSUInteger charIdx = [service.characteristics indexOfObjectPassingTest:^BOOL(CBCharacteristic * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                  return [obj.UUID isEqual:kNotifyUUID];
              }];
              
              if (charIdx == NSNotFound) {
                  [peripheral discoverCharacteristics:@[kNotifyUUID] forService:service];
                  continue;
              }
              
              CBCharacteristic *characteristic = service.characteristics[charIdx];
              if (!characteristic.isNotifying) {
                  [peripheral setNotifyValue:YES forCharacteristic:characteristic];
              }
          }
      }
    

    }else{
    [_peripherals removeAllObjects];
    }
    }

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    NSArray *peripheralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothPeripheralsKey];
    NSArray *centraManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];

    for (NSString *identifier in centraManagerIdentifiers) {
    if ([identifier isEqualToString:kRestoreIdentifierKey]) {

      }
    

    }

    return YES;
    }

Demo

不动手写代码的学框架都是耍流氓,为了更好的学习,我也是自己都敲了一遍代码,前文中的三种场景以及后台模式都实现了,可以实现中心设备和外设之前的订阅特征,读写数据等功能.希望大家都交流.下面是传送门以及界面展示
github传送门 CoreBluetoothDemo

iOS蓝牙开发(CoreBluetooth)_第3张图片
主界面

![中心设备]IMG_3660.PNG](http://upload-images.jianshu.io/upload_images/1093584-e66b42d6c1c7133f.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

iOS蓝牙开发(CoreBluetooth)_第4张图片
外设

你可能感兴趣的:(iOS蓝牙开发(CoreBluetooth))