【问题标题】:How to detect nearby devices with Bluetooth LE in iOS 7.1 both in background and foreground?如何在 iOS 7.1 中在后台和前台使用蓝牙 LE 检测附近的设备?
【发布时间】:2014-08-12 17:11:44
【问题描述】:

我有一个应用程序需要检测附近(在蓝牙 LE 范围内)运行相同应用程序和 iOS 7.1 的设备。我考虑了两种检测方法:

  1. 让设备充当 iBeacon 并检测范围内的 iBeacon
  2. 使用 CoreBluetooth(如在 Vicinity 实施 here 中)创建 BLE 外围设备,宣传该外围设备并扫描外围设备

看来选项1是不可能的,因为:

  • 当应用程序在后台运行时,iOS 可能需要至少 15 分钟才能检测到进入信标区域 (iOS 7.1)

选项 2 似乎可行,但在实施方面存在一些困难:

  • iOS 似乎会在一段时间后(大约 15 分钟?)更改广告数据包中的外围设备 UUID。这意味着无法直接从广告广播信号中识别出广告设备。

对此,我有以下问题:

  • 还有其他我没有考虑过的实现附近设备检测的方法吗?
  • 是否可以通过广告(或通过其他方式)识别设备,以便选项 2 起作用?

【问题讨论】:

    标签: ios iphone bluetooth bluetooth-lowenergy core-bluetooth


    【解决方案1】:

    我找到了一种方法来使这个工作核心蓝牙(选项2),过程大致如下:

    • 应用程序宣传自己,在CBAdvertisementDataLocalNameKey 中使用编码的设备唯一标识符(当广播应用程序在前台运行时)和通过蓝牙 LE 服务提供设备唯一标识符的特性(当广播应用程序在后台运行)
    • 同时,应用程序扫描其他外围设备使用相同的服务。

    广告工作如下:

    • 为了让其他设备能够识别此设备,我使用每个设备唯一的 UUID(我使用 Urban Airship 的 [UAUtils deviceID],因为它也是程序其他部分中的设备标识符 - 但你也可以使用任何唯一 ID 实现)。
    • 当应用程序在前台运行时,我可以使用CBAdvertisementDataLocalNameKey 直接在广告包中传递设备唯一ID。标准的 UUID 表示太长,所以我使用 UUID 的缩写形式如下:

      + (NSString *)shortenedDeviceID
      {
          NSString *deviceID = [UAUtils deviceID];
      
          NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:deviceID];
          uuid_t uuidBytes;
          [uuid getUUIDBytes:uuidBytes];
      
          NSData *data = [NSData dataWithBytes:uuidBytes length:16];
          NSString *base64 = [data base64EncodedStringWithOptions:0];
          NSString *encoded = [[[base64
                                 stringByReplacingOccurrencesOfString:@"/" withString:@"_"]
                                stringByReplacingOccurrencesOfString:@"+" withString:@"-"]
                               stringByReplacingOccurrencesOfString:@"=" withString:@""];
          return encoded;
      }
      
    • 当应用程序在后台运行时,广告包被剥离,CBAdvertisementDataLocalNameKey 不再传递。为此,应用程序需要发布提供唯一设备标识符的特征

      - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
      {
          if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
              [self startAdvertising];
      
              if (peripheralManager) {
                  CBUUID *serviceUUID = [CBUUID UUIDWithString:DEVICE_IDENTIFIER_SERVICE_UUID];
                  CBUUID *characteristicUUID = [CBUUID UUIDWithString:DEVICE_IDENTIFIER_CHARACTERISTIC_UUID];
                  CBMutableCharacteristic *characteristic =
                  [[CBMutableCharacteristic alloc] initWithType:characteristicUUID
                                                     properties:CBCharacteristicPropertyRead
                                                          value:[[MyUtils shortenedDeviceID] dataUsingEncoding:NSUTF8StringEncoding]
                                                    permissions:CBAttributePermissionsReadable];
                  CBMutableService *service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
                  service.characteristics = @[characteristic];
                  [peripheralManager addService:service];
              }
          }
      }
      

    扫描工作如下:

    • 您开始扫描具有特定服务UUID的外围设备,如下所示(注意您需要指定服务UUID,否则后台扫描无法找到设备):

      [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:DEVICE_IDENTIFIER_SERVICE_UUID]]
          options:scanOptions];
      
    • 当在- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI 发现设备时,您检查advertisementData[CBAdvertisementDataLocalNameKey] 是否存在并尝试将其转换回UUID 形式,如下所示:

      + (NSString *)deviceIDfromShortenedDeviceID:(NSString *)shortenedDeviceID
      {
          if (!shortenedDeviceID)
              return nil;
          NSString *decoded = [[[shortenedDeviceID
                                 stringByReplacingOccurrencesOfString:@"_" withString:@"/"]
                                stringByReplacingOccurrencesOfString:@"-" withString:@"+"]
                               stringByAppendingString:@"=="];
      
          NSData *data = [[NSData alloc] initWithBase64EncodedString:decoded options:0];
          if (!data)
              return nil;
      
          NSUUID *uuid = [[NSUUID alloc] initWithUUIDBytes:[data bytes]];
      
          return uuid.UUIDString;
      }
      
    • 如果转换失败,您知道广播设备在后台,您需要连接到设备以读取提供唯一标识符的特征。为此,您需要使用[self.central connectPeripheral:peripheral options:nil];(与peripheral.delegate = self; 并实现如下委托方法链:

      - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
      {
          [peripheral discoverServices:@[[CBUUID UUIDWithString:DEVICE_IDENTIFIER_SERVICE_UUID]]];
      }
      
      - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
      {
          if (!error) {
              for (CBService *service in peripheral.services) {
                  if ([service.UUID.UUIDString isEqualToString:DEVICE_IDENTIFIER_SERVICE_UUID]) {
                      NSLog(@"Service found with UUID: %@", service.UUID);
                      [peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:DEVICE_IDENTIFIER_CHARACTERISTIC_UUID]] forService:service];
                  }
              }
          }
      }
      
      - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
      {
          if (!error) {
              for (CBCharacteristic *characteristic in service.characteristics) {
                  if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:DEVICE_IDENTIFIER_CHARACTERISTIC_UUID]]) {
                      [peripheral readValueForCharacteristic:characteristic];
                  }
              }
          }
      }
      
      - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
      {
          if (!error) {
              NSString *shortenedDeviceID = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
              NSString *deviceId = [MyUtils deviceIDfromShortenedDeviceID:shortenedDeviceID];
              NSLog(@"Got device id: %@", deviceId);
          }
      }
      

    【讨论】:

    • 这篇文章非常有帮助,谢谢!请注意,在 didDiscoverPeripheral 中,如果发现的外围设备在发现时位于后台,则可以使用 CBAdvertisementDataOverflowServiceUUIDsKey 而不是 CBAdvertisementDataServiceUUIDsKey 在 adsData 中找到服务 UUID。
    • 第二个注意事项,在通过 connectPeripheral 连接之前,您需要保留对要连接的外围设备的引用。
    • 我发现LocalName 键在前台时只能可靠地为 8 个字符。我发现传输的长度也不同 - 可能是因为设备上的其他 BLE 应用程序。只是不要依赖它超过 8 个字符,当然它在后台不可用。
    • 有人可以确认此代码在最新的 iOS 上仍然有效吗?我一直无法让扫描 + 广告同时工作。谢谢
    • 我在 iOS 12+ 中的操作完全相同 遗憾的是,在后台运行了一段时间后,即使我尝试连接到外围设备,我也没有在 didDiscoverServices 委托中看到该服务。 @MarkusRautopuro 你有没有在后台使用设备测试过这段代码,几分钟后就可以发现它的服务?
    猜你喜欢
    • 2015-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-09
    • 2014-07-15
    • 2021-12-19
    • 2012-12-14
    • 1970-01-01
    相关资源
    最近更新 更多