Objective-c 蓝牙说它已连接但 CBCentralManager 没有发现它
Objective-c bluetooth says it's connected but CBCentralManager does not discover it
在我的应用程序中,我首先使用委托方法 retrieveConnectedPeripheralsWithServices
搜索以前连接的设备,如果没有找到,然后扫描附近的设备。这是代码
- (void)searchForPeripherals {
NSLog(@"*** scanning for Bluetooth peripherals... ***");
//Check to see if any devices were connected already
if(self.ourPeripheral != nil) {
[self connectToDevice];
} else {
//Search for previously paired devices...
bool foundPairedDevices = [self checkForAndConnectToPreviouslyPairedDevices];
//None found, scan for new devices nearby...
if(!foundPairedDevices) {
NSLog(@"No paired boxes found, checking device powered state...");
//If the app user has "bluetooth" option turned on
if(self.centralManager.state == CBCentralManagerStatePoweredOn) {
NSLog(@"--- central manager powered on.");
NSLog(@"...begin scanning...");
[self.centralManager scanForPeripheralsWithServices:nil
options:@{CBCentralManagerScanOptionAllowDuplicatesKey: @(YES)}];
//No, user has "bluetooth" turned off
} else {
NSLog(@"--- central manager is NOT powered on.");
}
}
}
}
- (bool)checkForAndConnectToPreviouslyPairedDevices {
NSLog(@"our peripheral device: %@", self.ourPeripheral);
NSArray *dcBoxesFound = [self.centralManager retrieveConnectedPeripheralsWithServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]];
NSLog(@"Previously paired DC boxes?: %@", dcBoxesFound);
//Are there any previously paired devices?
if(dcBoxesFound != nil && [dcBoxesFound count] > 0) {
CBPeripheral *newestBoxFound = [dcBoxesFound firstObject];
newestBoxFound.delegate = self;
self.ourPeripheral = newestBoxFound;
self.deviceUUID = [CBUUID UUIDWithString:[newestBoxFound.identifier UUIDString]];
[self connectToDevice];
return YES;
} else {
return NO;
}
}
- (void)connectToDevice {
if(self.ourPeripheral != nil) {
[self.centralManager connectPeripheral:self.ourPeripheral options:nil];
}
}
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSLog(@"scanned and found this peripheral: %@", peripheral);
NSLog(@"advertisment data: %@", advertisementData);
if(!self.isConnectedToDevice && [[advertisementData objectForKey:@"kCBAdvDataLocalName"] localizedCaseInsensitiveContainsString:@"dc-"]) {
NSLog(@"\n");
NSLog(@"-------------------------------------------");
NSLog(@"DC peripheral found! Attempting to connect to the following...: %@", peripheral);
peripheral.delegate = self;
self.ourPeripheral = peripheral;
self.deviceUUID = [CBUUID UUIDWithString:[peripheral.identifier UUIDString]];
[self connectToDevice];
NSLog(@"-------------------------------------------");
NSLog(@"\n");
}
}
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"--- didConnectPeripheral");
peripheral.delegate = self;
[peripheral discoverServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]];
}
问题
当我启动应用程序时,它通常连接正常。一切都很好。然后在非常不可预测和奇怪的时候,BT 连接以某种方式获得 "stuck"。我所说的卡住的意思是它永远不会真正连接起来。我的盒子上的蓝灯亮着,就好像系统已连接到它一样,但在我的应用程序中却发生了这种情况。
1) retrieveConnectedPeripheralsWithServices
方法找到以前连接的设备的空数组,告诉调用它的方法没有找到以前连接的设备。
2) scanForPeripheralsWithServices:nil
方法被触发并开始使用 didDiscoverPeripheral
方法扫描。
3) didDiscoverPeripheral
从来没有在附近发现过 "dc-" 某物或其他
中前缀为 kCBAdvDataLocalName
的任何盒子
如果我要进入 iOS 设置 -> 蓝牙 -> 忘记这个设备(必须按两次,这让我觉得它是 "stuck" 不知何故)然后实际关闭BT 选项一起...等待 2 秒,然后再次打开它...然后重新启动应用程序,一切正常。
1) 应用启动
2) 查看之前没有连接过的设备
3) 扫描外围设备并找到我的前缀框名称"dc-whatever"
4) 要求我与 BT 设备配对,然后我恢复了全部功能
如果我随后关闭应用程序并重新启动它,retrieveConnectedPeripheralsWithServices
会毫无问题地找到我之前连接的盒子并无缝连接...
所以...这是怎么回事?为什么它有时似乎随机 "stuck"?
编辑:
我确实意识到配对和连接不是一回事,所以有些方法的命名非常糟糕。
这是蓝牙 LE 编程的一个常见问题,由于 API 都是异步的,因此变得更加困难。
典型的解决方案是让代码在某些事情未完成时重试。而且因为您不一定会收到错误消息(您可能不会收到下一个回调),您最终要做的是设置计时器以在合理的时间段后开始重试——比如 5-10 秒。
如您所见,在某些情况下(例如在后台,如果设备已连接,或以其他方式停止广告),您只会收到一个回调 didDiscoverPeripheral
。因此,您确实需要保存 CBPeripheral
对象的副本,以便在发生超时时可以重新使用它。这是我将使用的基本逻辑:
- 当您在
didDiscoverPeripheral
中发现一个新的外围设备时,将对象实例保存在一个名为 peripheralForConnectionAttempt
的变量中,然后启动一个 5 秒计时器。
- 如果连接在
didConnectPeripheral
中成功完成,请将 peripheralForConnectionAttempt
的值设置为 nil。
- 当计时器关闭时,检查
peripheralForConnectionAttempt
是否不为零,如果是,则重试此对象,就好像 didDiscoverPeripheral
已被调用一样。您可能希望有一个计数器将重试次数限制为 10 次或类似次数。
旁注:
当你说蓝灯卡住时,这可能意味着BLE Peripheral设备认为它已经建立了BLE连接。执行此操作的外围设备通常会停止广播,直到连接断开。如果 iOS 设备认为它没有连接而外围设备连接了,这会让你处于一个糟糕的境地,你必须以某种方式断开连接。不幸的是,CoreLocation 没有提供 API 来执行此操作。
在一个项目中,我在蓝牙外设固件中通过在发生通信超时时断开连接来实现这一点。但是如果你不控制固件,你显然不能这样做。
如您所见,重新打开蓝牙电源会中断任何活动连接。但是在 iOS 上,您不能以编程方式执行此操作。
如果您不控制外围固件,并且无法想出任何方法来避免锁定与设备的连接(也许通过改变您的操作时间?)那么您可能别无选择,只能简单地检测问题,适当时提醒用户,并在用例允许时指导用户循环使用蓝牙。我知道这不是一个理想的解决方案。
在我的应用程序中,我首先使用委托方法 retrieveConnectedPeripheralsWithServices
搜索以前连接的设备,如果没有找到,然后扫描附近的设备。这是代码
- (void)searchForPeripherals {
NSLog(@"*** scanning for Bluetooth peripherals... ***");
//Check to see if any devices were connected already
if(self.ourPeripheral != nil) {
[self connectToDevice];
} else {
//Search for previously paired devices...
bool foundPairedDevices = [self checkForAndConnectToPreviouslyPairedDevices];
//None found, scan for new devices nearby...
if(!foundPairedDevices) {
NSLog(@"No paired boxes found, checking device powered state...");
//If the app user has "bluetooth" option turned on
if(self.centralManager.state == CBCentralManagerStatePoweredOn) {
NSLog(@"--- central manager powered on.");
NSLog(@"...begin scanning...");
[self.centralManager scanForPeripheralsWithServices:nil
options:@{CBCentralManagerScanOptionAllowDuplicatesKey: @(YES)}];
//No, user has "bluetooth" turned off
} else {
NSLog(@"--- central manager is NOT powered on.");
}
}
}
}
- (bool)checkForAndConnectToPreviouslyPairedDevices {
NSLog(@"our peripheral device: %@", self.ourPeripheral);
NSArray *dcBoxesFound = [self.centralManager retrieveConnectedPeripheralsWithServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]];
NSLog(@"Previously paired DC boxes?: %@", dcBoxesFound);
//Are there any previously paired devices?
if(dcBoxesFound != nil && [dcBoxesFound count] > 0) {
CBPeripheral *newestBoxFound = [dcBoxesFound firstObject];
newestBoxFound.delegate = self;
self.ourPeripheral = newestBoxFound;
self.deviceUUID = [CBUUID UUIDWithString:[newestBoxFound.identifier UUIDString]];
[self connectToDevice];
return YES;
} else {
return NO;
}
}
- (void)connectToDevice {
if(self.ourPeripheral != nil) {
[self.centralManager connectPeripheral:self.ourPeripheral options:nil];
}
}
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSLog(@"scanned and found this peripheral: %@", peripheral);
NSLog(@"advertisment data: %@", advertisementData);
if(!self.isConnectedToDevice && [[advertisementData objectForKey:@"kCBAdvDataLocalName"] localizedCaseInsensitiveContainsString:@"dc-"]) {
NSLog(@"\n");
NSLog(@"-------------------------------------------");
NSLog(@"DC peripheral found! Attempting to connect to the following...: %@", peripheral);
peripheral.delegate = self;
self.ourPeripheral = peripheral;
self.deviceUUID = [CBUUID UUIDWithString:[peripheral.identifier UUIDString]];
[self connectToDevice];
NSLog(@"-------------------------------------------");
NSLog(@"\n");
}
}
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"--- didConnectPeripheral");
peripheral.delegate = self;
[peripheral discoverServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]];
}
问题
当我启动应用程序时,它通常连接正常。一切都很好。然后在非常不可预测和奇怪的时候,BT 连接以某种方式获得 "stuck"。我所说的卡住的意思是它永远不会真正连接起来。我的盒子上的蓝灯亮着,就好像系统已连接到它一样,但在我的应用程序中却发生了这种情况。
1) retrieveConnectedPeripheralsWithServices
方法找到以前连接的设备的空数组,告诉调用它的方法没有找到以前连接的设备。
2) scanForPeripheralsWithServices:nil
方法被触发并开始使用 didDiscoverPeripheral
方法扫描。
3) didDiscoverPeripheral
从来没有在附近发现过 "dc-" 某物或其他
kCBAdvDataLocalName
的任何盒子
如果我要进入 iOS 设置 -> 蓝牙 -> 忘记这个设备(必须按两次,这让我觉得它是 "stuck" 不知何故)然后实际关闭BT 选项一起...等待 2 秒,然后再次打开它...然后重新启动应用程序,一切正常。
1) 应用启动
2) 查看之前没有连接过的设备
3) 扫描外围设备并找到我的前缀框名称"dc-whatever"
4) 要求我与 BT 设备配对,然后我恢复了全部功能
如果我随后关闭应用程序并重新启动它,retrieveConnectedPeripheralsWithServices
会毫无问题地找到我之前连接的盒子并无缝连接...
所以...这是怎么回事?为什么它有时似乎随机 "stuck"?
编辑:
我确实意识到配对和连接不是一回事,所以有些方法的命名非常糟糕。
这是蓝牙 LE 编程的一个常见问题,由于 API 都是异步的,因此变得更加困难。
典型的解决方案是让代码在某些事情未完成时重试。而且因为您不一定会收到错误消息(您可能不会收到下一个回调),您最终要做的是设置计时器以在合理的时间段后开始重试——比如 5-10 秒。
如您所见,在某些情况下(例如在后台,如果设备已连接,或以其他方式停止广告),您只会收到一个回调 didDiscoverPeripheral
。因此,您确实需要保存 CBPeripheral
对象的副本,以便在发生超时时可以重新使用它。这是我将使用的基本逻辑:
- 当您在
didDiscoverPeripheral
中发现一个新的外围设备时,将对象实例保存在一个名为peripheralForConnectionAttempt
的变量中,然后启动一个 5 秒计时器。 - 如果连接在
didConnectPeripheral
中成功完成,请将peripheralForConnectionAttempt
的值设置为 nil。 - 当计时器关闭时,检查
peripheralForConnectionAttempt
是否不为零,如果是,则重试此对象,就好像didDiscoverPeripheral
已被调用一样。您可能希望有一个计数器将重试次数限制为 10 次或类似次数。
旁注:
当你说蓝灯卡住时,这可能意味着BLE Peripheral设备认为它已经建立了BLE连接。执行此操作的外围设备通常会停止广播,直到连接断开。如果 iOS 设备认为它没有连接而外围设备连接了,这会让你处于一个糟糕的境地,你必须以某种方式断开连接。不幸的是,CoreLocation 没有提供 API 来执行此操作。
在一个项目中,我在蓝牙外设固件中通过在发生通信超时时断开连接来实现这一点。但是如果你不控制固件,你显然不能这样做。
如您所见,重新打开蓝牙电源会中断任何活动连接。但是在 iOS 上,您不能以编程方式执行此操作。
如果您不控制外围固件,并且无法想出任何方法来避免锁定与设备的连接(也许通过改变您的操作时间?)那么您可能别无选择,只能简单地检测问题,适当时提醒用户,并在用例允许时指导用户循环使用蓝牙。我知道这不是一个理想的解决方案。