Подтвердить что ты не робот

Как обнаружить соседние устройства с Bluetooth LE в iOS 7.1 как на заднем плане, так и на переднем плане?

У меня есть приложение, которое должно обнаруживать соседние (в диапазоне для Bluetooth LE) устройства с одним и тем же приложением и iOS 7.1. Я рассмотрел две альтернативы для обнаружения:

  • Наличие устройств iBeacons и обнаружение iBeacons в диапазоне
  • Использование CoreBluetooth (например, в реализации Vicinity здесь) для создания периферийного устройства BLE, рекламы и сканирования периферийных устройств.

Кажется, что вариант 1 не может быть и речи, потому что:

  • Для iOS может потребоваться не менее 15 минут для обнаружения входа в зону маяка, когда приложение работает под управлением фона (iOS 7.1).

Вариант 2 кажется способным, но есть некоторые трудности в реализации:

  • Кажется, что iOS изменяет периферийный UUID в рекламных пакетах через определенный промежуток времени (около 15 минут?). Это означает, что невозможно напрямую идентифицировать рекламное устройство от рекламного сигнала вещания.

В связи с этим у меня есть следующие вопросы:

  • Есть ли какие-либо другие способы реализации обнаруженного устройства, которые я не рассматривал?
  • Можно ли идентифицировать устройство посредством рекламы (или каким-либо другим способом), чтобы вариант 2 работал?
4b9b3361

Ответ 1

Я нашел способ выполнить эту работу Core Bluetooth (вариант 2), процедура примерно следующая:

  • Приложение объявляет себя с уникальным идентификатором кодированного устройства в CBAdvertisementDataLocalNameKey (когда приложение широковещания работает на переднем плане) и характеристикой, которая предоставляет уникальный идентификатор устройства через службу Bluetooth LE (когда приложение широковещательной передачи запускает фон)
  • В то же время приложение сканирует другие периферийные устройства с одной и той же службой.

Реклама работает следующим образом:

  • Для того, чтобы другие устройства могли идентифицировать это устройство, я использую уникальный UUID для каждого устройства (я использую Urban Airship [UAUtils deviceID], потому что это идентификатор устройства в других частях программы, а также - но вы также может использовать любую уникальную реализацию 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;
    }
    
  • Когда приложение работает в фоновом режиме, пакет рекламы убирается и 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);
        }
    }