How to detect nearby devices with Bluetooth LE in iOS 7.1 both in background and foreground?

前端 未结 1 766
野的像风
野的像风 2020-12-13 15:05

I have an app that needs to detect a nearby (in range for Bluetooth LE) devices running the same application and iOS 7.1. I\'ve considered two alternatives for the detection

相关标签:
1条回答
  • 2020-12-13 15:37

    I found a way to make this work Core Bluetooth (option 2), the procedure is roughly the following:

    • The application advertises itself with an encoded device unique identifier in CBAdvertisementDataLocalNameKey (when the broadcasting application runs foreground) and a characteristic that provides the device unique identifier through a Bluetooth LE service (when the broadcasting application runs background)
    • At the same time, the application scans other peripherals with the same service.

    The advertising works as follows:

    • For the other devices to be able to identify this device, I use a per-device unique UUID (I'm using Urban Airship's [UAUtils deviceID], because it's the device identifier in other parts of the program, also - but you might as well use any unique ID implementation).
    • When the application is running foreground, I can pass the device unique ID directly in the advertisement packet by using CBAdvertisementDataLocalNameKey. The standard UUID representation is too long, so I use a shortened form of the UUID as follows:

      + (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;
      }
      
    • When the application is running background, the advertisement packet gets stripped and CBAdvertisementDataLocalNameKey is not passed along anymore. For this, the application needs to publish a characteristic that provides the unique device identifier:

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

    The scanning works as follows:

    • You start to scan peripherals with the certain service UUID as follows (notice that you need to specify the service UUID, because otherwise background scan fails to find the device):

      [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:DEVICE_IDENTIFIER_SERVICE_UUID]]
          options:scanOptions];
      
    • When a device is discovered at - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI you check that if advertisementData[CBAdvertisementDataLocalNameKey] exists and try to convert it back to UUID form like this:

      + (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;
      }
      
    • If the conversion fails you know the broadcasting device is in background, and you need to connect to the device to read the characteristic that provides the unique identifier. For this you need to use [self.central connectPeripheral:peripheral options:nil]; (with peripheral.delegate = self; and implement a chain of delegate methods as follows:

      - (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);
          }
      }
      
    0 讨论(0)
提交回复
热议问题