iOS蓝牙开发(CoreBluetooth)

作者: 我系哆啦 | 来源:发表于2016-04-11 00:06 被阅读2875次

    目前在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和类.


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


      外设和服务特征的关系

    中心的开发

    • 流程
      <pre>
    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)
      </pre>
    • 步骤
      CoreBluetooth中不管是中心还是外设的开发,均是上面的流程,通过一步步的代理回调实现的,按照流程跟着走,一步一步实现代理.
      <pre>
      //初始化
      _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<NSString *,id> *)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;
    }
    </pre>

    外设的开发

    • 流程
      <pre>
      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
        </pre>
    • 步骤
      <pre>
      //初始化
      _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<CBATTRequest *> *)requests;
      </pre>

    UIBackgroundModes下的蓝牙数据传输

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

    • 设置info.plist
      

    <pre>
    <key>UIBackgroundModes</key>
    <array>
    <string>bluetooth-central</string>
    <string>bluetooth-peripheral</string>
    </array>
    </pre>

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

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

    <pre>

    • (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *,id> *)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;
    }
    

    }
    </pre>

    <pre>

    • (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];
      }
      }
      </pre>

    <pre>

    • (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;
      }
      </pre>

    Demo

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

    主界面

    ![中心设备]IMG_3660.PNG](http:https://img.haomeiwen.com/i1093584/e66b42d6c1c7133f.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    外设

    相关文章

      网友评论

        本文标题:iOS蓝牙开发(CoreBluetooth)

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