如何在iOS 7.1的后台和前台中使用蓝牙LE检测附近的设备?

Mar*_*uro 18 iphone bluetooth ios core-bluetooth bluetooth-lowenergy

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

  1. 使设备充当iBeacons并检测范围内的iBeacons
  2. 使用CoreBluetooth(就像在近处实现这里)来创建一个BLE外设,通告和扫描外设

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

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

方案2似乎要走了,但实施方面存在一些困难:

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

关于这一点,我有以下问题:

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

Mar*_*uro 21

我找到了一种方法让这项工作成为Core Bluetooth(选项2),程序大致如下:

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

广告的工作原理如下:

  • 为了能够识别这个设备的其他设备,我使用每个设备唯一的UUID(我正在使用Urban Airship [UAUtils deviceID],因为它也是程序其他部分的设备标识符 - 但你也可以使用任何唯一的ID实现).
  • 当应用程序正在运行的前景,我可以通过该设备唯一的ID 直接在通告报文,通过使用CBAdvertisementDataLocalNameKey.标准的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;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 当应用程序运行后台时,广告数据包将被剥离,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];
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

扫描工作如下:

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

    [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:DEVICE_IDENTIFIER_SERVICE_UUID]]
        options:scanOptions];
    
    Run Code Online (Sandbox Code Playgroud)
  • - (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;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 如果转换失败,你知道的广播设备是在后台,你需要连接到设备读取的特性,它提供的唯一标识符.为此,您需要使用[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);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)