问题描述
我有一个应用程序需要检测附近(在蓝牙 LE 范围内)运行相同应用程序和 iOS 7.1 的设备.我已经考虑了两种检测方法:
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:
- 让设备充当 iBeacons 并检测范围内的 iBeacons
- 使用 CoreBluetooth(例如在 此处 中的 Vicinity 实现中)创建 BLE 外设、通告并扫描外设
- Having the devices act as iBeacons and detect iBeacons in range
- Using CoreBluetooth (like in Vicinity implementation here) to create a BLE peripheral, advertise that and scan the peripherals
似乎选项 1 是不可能的,因为:
It seems that the option 1 is out of the question because:
- 当应用在后台运行时,iOS 可能至少需要 15 分钟才能检测到进入信标区域 (iOS 7.1)
选项 2 似乎可行,但在实施方面存在一些困难:
Option 2 seems the way to go, but there are some difficulties regarding the implementation:
- iOS 似乎在一段时间后(大约 15 分钟?)更改了广告数据包中的外围 UUID.这意味着无法直接从广告广播信号中识别广告设备.
关于这个,我有以下问题:
Regarding this, I have the following questions:
- 是否还有其他我没有考虑过的实现附近设备检测的方法?
- 是否可以通过广告(或其他方式)来识别设备,以便选项 2 起作用?
推荐答案
我找到了一个方法让这个工作Core Bluetooth(选项2),程序大致如下:
I found a way to make this work Core Bluetooth (option 2), the procedure is roughly the following:
- 应用程序使用
CBAdvertisementDataLocalNameKey
中编码的设备唯一标识符(当广播应用程序在前台运行时)和一个通过蓝牙LE 提供设备唯一标识符的特性为自己做广告服务(当广播应用在后台运行时) - 同时,应用程序扫描其他外围设备,使用相同的服务.
- 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.
广告效果如下:
- 为了让其他设备能够识别此设备,我使用每个设备唯一的 UUID(我使用的是 Urban Airship 的
[UAUtils deviceID]
,因为它是其他部分的设备标识符程序,但您也可以使用任何唯一 ID 实现). 当应用程序在前台运行时,我可以使用
CBAdvertisementDataLocalNameKey
直接在广告包中传递设备唯一ID.标准的 UUID 表示太长,所以我使用了 UUID 的缩写形式,如下所示:
- 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;
}
当应用程序在后台运行时,广告数据包被剥离并且CBAdvertisementDataLocalNameKey
不再被传递.为此,应用程序需要发布一个特性来提供唯一的设备标识符:
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];
}
}
}
扫描工作如下:
你开始扫描带有特定服务UUID的外设如下(注意需要指定服务UUID,否则后台扫描找不到设备):
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];
当在 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral 发现设备时adsData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
你检查 advertisementData[CBAdvertisementDataLocalNameKey]
是否存在并尝试将其转换回 UUID 形式,如下所示:
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;
}
如果转换失败,您知道广播设备在后台,您需要连接到设备以读取提供唯一标识符的特性.为此,您需要使用 [self.central connectPeripheral:peripheral options:nil];
(使用 peripheral.delegate = self;
并实现一系列委托方法,如下所示:
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);
}
}
这篇关于如何在后台和前台在 iOS 7.1 中使用蓝牙 LE 检测附近的设备?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!