ios - corebluetooth - xcode bluetooth explorer
¿Cómo detectar dispositivos cercanos con Bluetooth LE en iOS 7.1 tanto en segundo plano como en primer plano? (1)
Tengo una aplicación que necesita detectar dispositivos cercanos (dentro del alcance de Bluetooth LE) que ejecuten la misma aplicación y iOS 7.1. He considerado dos alternativas para la detección:
- Hacer que los dispositivos actúen como iBeacons y detecten iBeacons dentro del alcance
- Uso de CoreBluetooth (como en la implementación de Vicinity here ) para crear un periférico BLE, anunciarlo y escanear los periféricos
Parece que la opción 1 está fuera de discusión porque:
- Puede tomar al menos 15 minutos para que iOS detecte el ingreso a una región de baliza cuando la aplicación se ejecuta en segundo plano (iOS 7.1)
La opción 2 parece ser el camino a seguir, pero existen algunas dificultades con respecto a la implementación:
- iOS parece cambiar el UUID periférico en los paquetes de publicidad después de un cierto período de tiempo (¿alrededor de 15 minutos?). Esto significa que no es posible identificar directamente el dispositivo de publicidad a partir de la señal de difusión del anuncio.
Respecto a esto, tengo las siguientes preguntas:
- ¿Hay otros métodos para implementar la detección de dispositivos cercanos que no haya considerado?
- ¿Es posible identificar el dispositivo a través de la publicidad (o por algún otro medio) para que la opción 2 funcione?
Encontré una manera de hacer que esto funcionara con Bluetooth (opción 2), el procedimiento es aproximadamente el siguiente:
- La aplicación se anuncia con un identificador único de dispositivo codificado en
CBAdvertisementDataLocalNameKey
(cuando la aplicación de difusión se ejecuta en primer plano) y una característica que proporciona el identificador único del dispositivo a través de un servicio Bluetooth LE (cuando la aplicación de difusión se ejecuta en segundo plano) - Al mismo tiempo, la aplicación escanea otros periféricos con el mismo servicio.
La publicidad funciona de la siguiente manera:
- Para que los otros dispositivos puedan identificar este dispositivo, uso un UUID único por dispositivo (también estoy usando el
[UAUtils deviceID]
Urban Airship[UAUtils deviceID]
, porque es el identificador del dispositivo en otras partes del programa, también. bien utilizar cualquier implementación de ID única). Cuando la aplicación se ejecuta en primer plano, puedo pasar la ID única del dispositivo directamente en el paquete de publicidad utilizando
CBAdvertisementDataLocalNameKey
. La representación estándar de UUID es demasiado larga, así que uso una forma abreviada de UUID de la siguiente manera:+ (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; }
Cuando la aplicación se está ejecutando en segundo plano, el paquete de publicidad se
CBAdvertisementDataLocalNameKey
yCBAdvertisementDataLocalNameKey
ya no se transmite. Para esto, la aplicación necesita publicar una característica que proporcione el identificador único del dispositivo:- (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]; } } }
El escaneo funciona de la siguiente manera:
Usted comienza a escanear periféricos con el UUID de cierto servicio de la siguiente manera (observe que necesita especificar el UUID de servicio, porque de lo contrario, el análisis de fondo no puede encontrar el dispositivo):
[self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:DEVICE_IDENTIFIER_SERVICE_UUID]] options:scanOptions];
Cuando se descubre un dispositivo en
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
comprueba si existe unadvertisementData[CBAdvertisementDataLocalNameKey]
y tratamos de Conviértelo de nuevo a la forma UUID como esta:+ (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; }
Si la conversión falla, sabe que el dispositivo de transmisión está en segundo plano y necesita conectarse al dispositivo para leer la característica que proporciona el identificador único. Para esto necesita usar
[self.central connectPeripheral:peripheral options:nil];
(conperipheral.delegate = self;
e implemente una cadena de métodos de delegado de la siguiente manera:- (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); } }