Objective-C蓝牙表示已连接,但CBCentralManager未发现它 [英] Objective-c bluetooth says it's connected but CBCentralManager does not discover it

查看:223
本文介绍了Objective-C蓝牙表示已连接,但CBCentralManager未发现它的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的应用中,我首先使用委托方法 retrieveConnectedPeripheralsWithServices 搜索以前连接的设备,然后如果找不到任何内容,则扫描附近的设备。这是代码

 -(void)searchForPeripherals {
NSLog(@ ***扫描蓝牙外围设备。 .. ***);

//检查是否已连接任何设备
if(self.ourPeripheral!= nil){
[self connectToDevice];

} else {
//搜索先前配对的设备...
bool foundPairedDevices = [self checkForAndConnectToPreviouslyPairedDevices];

//未找到,扫描附近的新设备...
if(!foundPairedDevices){
NSLog(@未找到配对的盒子,正在检查设备的电源状态。 。);

//如果应用程序用户具有蓝牙选项,则打开
if(self.centralManager.state == CBCentralManagerStatePoweredOn){
NSLog(@ ---中央管理器通电。);
NSLog(@ ...开始扫描...);

[self.centralManager scanForPeripheralsWithServices:nil
选项:@ {CBCentralManagerScanOptionAllowDuplicatesKey:@(YES)}];

//否,用户已关闭蓝牙
}否则{
NSLog(@-中央管理器未打开电源。);
}
}
}
}


-(bool)checkForAndConnectToPreviouslyPairedDevices {
NSLog(@我们的外围设备: %@,self.ourPeripheral);

NSArray * dcBoxesFound = [self.centralManager RetrieveConnectedPeripheralsWithServices:@ [[CBUUID UUIDWithString:SERVICE_UUID]]]];
NSLog(@先前配对的DC盒?:%@,dcBoxesFound);

//是否有以前配对过的设备?
if(dcBoxesFound!= nil&& [dcBoxesFound count]> 0){
CBPeripheral * newestBoxFound = [dcBoxesFound firstObject];
newestBoxFound.delegate =自我;

self.ourPeripheral = newestBoxFound;
self.deviceUUID = [CBUUID UUIDWithString:[newestBoxFound.identifier UUIDString]];

[self connectToDevice];

返回是;

}否则{
返回NO;
}
}


-(void)connectToDevice {
if(self.ourPeripheral!= nil){
[self.centralManager connectPeripheral:self.ourPeripheral选项:无];
}
}


-(void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
adsData: (NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {

NSLog(@已扫描并找到此外围设备:%@,外围设备);
NSLog(@广告数据:%@,advertiseData);

if(!self.isConnectedToDevice&& [[advertisedataData objectForKey:@ kCBAdvDataLocalName] localizedCaseInsensitiveContainsString:@ dc-]){
NSLog(@ \n );
NSLog(@ ------------------------------------------ -);
NSLog(@找到直流外围设备!试图连接到以下...:%@,外围设备);

外周.delegate =自我;
self.ourPeripheral =外设;

self.deviceUUID = [CBUUID UUIDWithString:[peripheral.identifier UUIDString]];

[self connectToDevice];

NSLog(@ -------------------------------------- -----);
NSLog(@ \n);
}
}


-(void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog (@ --- didConnectPeripheral);

外周.delegate =自我;
[外围设备DiscoverServices:@ [[CBUUID UUIDWithString:SERVICE_UUID]]];
}

问题



启动应用程序时,它通常连接正常。一切都很好。然后,在非常不可预测的奇数时间,BT连接会以某种方式卡住。我所说的卡住是指它永远不会真正连接。我的盒子上的蓝灯持续亮起,好像系统已连接到它,但是在我的应用程序中会发生这种情况。



1) retrieveConnectedPeripheralsWithServices 方法会找到一个空的先前连接的设备数组,并告诉该方法没有找到先前连接的设备。



2)触发 scanForPeripheralsWithServices:nil 方法,并开始使用 didDiscoverPeripheral 方法进行扫描。



3) didDiscoverPeripheral 从来没有一次在附近的任何框内找到带有 dc的 kCBAdvDataLocalName 前缀的框。 -某物或其他



如果我要进入iOS设置->蓝牙->忘记此设备(必须打两次,这让我觉得它卡住了 (以某种方式),然后实际上一起关闭BT选项...等待2秒钟,然后再次将其打开...然后重新启动应用程序,一切正常。



1)应用启动



2)看到以前没有连接过的设备



3)扫描外围设备,并找到我前缀的框名 dc-whatever



4)请我与BT设备配对,然后我具有完整的功能



如果我随后关闭应用程序并重新启动,则 retrieveConnectedPeripheralsWithServices 会找到我的以前连接的盒子没有问题并且可以无缝连接...



那么...这是怎么回事?为什么它似乎有时会随机卡住?



EDIT:



我确实意识到配对和连接不是同一件事,因此某些方法的命名确实很差。

解决方案

这是蓝牙LE编程中的一个普遍问题,由于API都是异步的,因此变得更加困难。



典型的解决方案是使代码成为如果未完成,请重试。而且由于您不一定会收到错误消息(您可能不会得到下一个回调),所以最终要做的是设置计时器以在合理的时间段(例如5到10秒)后开始重试。 / p>

正如您所注意到的,在某些情况下(例如在后台,如果设备连接或停止播发广告),您只会收到一个回调到 didDiscoverPeripheral 。因此,您确实需要保存 CBPeripheral 对象的副本,以便在发生超时时可以重新使用它。这是我会使用的基本逻辑:


  1. 当您在 didDiscoverPeripheral ,将对象实例保存在名为 peripheralForConnectionAttempt 的变量中,然后启动5秒钟计时器。

  2. 如果在 didConnectPeripheral 中成功完成连接,则设置 peripheralForConnectionAttempt 的值

  3. 当计时器关闭时,检查 peripheralForConnectionAttempt 是否不为零,如果是,则重试此对象如果已调用 didDiscoverPeripheral 。您可能希望有一个计数器将重试次数限制为10次或类似的次数。

侧面说明:



当您说蓝灯一直亮着时,这可能意味着BLE外围设备认为已建立BLE连接。执行此操作的外围设备通常会停止广告,直到连接断开。如果iOS设备认为未连接,而外围设备认为未连接,则会使您处于严重的状况,必须以某种方式断开连接。不幸的是,CoreLocation没有提供任何API来执行此操作。



在一个项目中,如果在那里发生通讯超时,我会通过断开连接来在蓝牙外围设备固件中完成此操作。但是,如果您不控制固件,则显然无法执行此操作。



重新打开蓝牙电源会断开任何活动的连接,如您所见。但是在iOS上,您无法以编程方式执行此操作。



如果您不控制外围固件,并且想不出任何方法来避免锁定与设备的连接(也许是通过更改操作时间来实现)?那么您可能没有其他选择,只能简单地检测出问题,在适当时提醒用户,并在用例许可的情况下指示用户循环使用蓝牙。我知道这不是一个理想的解决方案。


In my app, I search for previously connected devices with the delegate method retrieveConnectedPeripheralsWithServices first, then scan for nearby devices if nothing is found. Here is the code

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

The Problem

When I start up the app it usually connects just fine. Everything is all good. Then at very unpredictable and odd times the BT connection gets "stuck" somehow. What I mean by stuck is that it never truely connects. The blue light on my box goes on as though the system has connected to it but in my app this is what happens.

1) The retrieveConnectedPeripheralsWithServices method finds an empty array of previously connected devices, telling the method that called it that there are no previously connected devices found.

2) The scanForPeripheralsWithServices:nil method is fired and begins scanning with the didDiscoverPeripheral method.

3) The didDiscoverPeripheral never even once discovers any boxes nearby with the prefix in kCBAdvDataLocalName of "dc-" something or other

If I were to go into iOS settings -> bluetooth -> forget this device (have to hit it twice which makes me feel like it's "stuck" somehow) and then actually turn off the BT option all together... Wait 2 seconds and then turn it back on again... then re-launch the application, it all loads fine.

1) The app starts up

2) See's no previously connected devices

3) Scans for peripherals and finds my prefixed box name "dc-whatever"

4) Asks me to pair with BT device and then I have full functionality back

If I then shut down the app and re-launch it, the retrieveConnectedPeripheralsWithServices finds my previously connected box without trouble and connects seamlessly...

So... what is going on here? Why does it seem to get "stuck" randomly some times?

EDIT:

I do realize that pairing and connecting are not the same thing so some method are named really poorly.

解决方案

This is a common problem with Bluetooth LE programming, made harder by the fact that the APIs are all asynchronous.

The typical solution is to make the code to retry if something doesn't complete. And because you won't necessarily get error messages (you might just not get the next callback) what you end up having to do is set up timers to kick off a retry after a reasonable time period -- say 5-10 seconds.

As you've noticed, in some cases (like in the background, if the device connects, or otherwise stops advertising) you will only get one callback to didDiscoverPeripheral. So you really need to save off a copy of the CBPeripheral object, so that it can be re-used if a timeout happens. Here's the basic logic I would use:

  1. When you discover a new peripheral in didDiscoverPeripheral, save the object instance in a variable called peripheralForConnectionAttempt, then start a 5 second timer.
  2. If the connection completes successfully in didConnectPeripheral, set the value of peripheralForConnectionAttempt to nil.
  3. When the timer goes off, check if peripheralForConnectionAttempt is not nil, and if so, retry on this object just as if didDiscoverPeripheral had been called. You might want to have a counter to limit the retries to 10 times or something like that.

Side Note:

When you say the blue light gets stuck on, this may mean that the BLE Peripheral device thinks it has established a BLE connection. Peripherals that do this typically stop advertising until the connection is broken. If the iOS device thinks it is not connected and the peripheral does, this puts you in a bad situation where you must break the connection somehow. Unfortunately, CoreLocation gives you no APIs to do this.

On one project, I have accomplished this in the bluetooth peripheral firmware by breaking the connection if a communication timeout happens there. But if you don't control the firmware, you obviously cannot do this.

Cycling power to bluetooth will break any active connections as you have seen. But on iOS, you cannot do this programmatically.

If you do not control the peripheral firmware, and cannot figure out any way to avoid locking up the connection with the device (perhaps by altering the timing of your operations?) then you may have no recourse other than to simply detect the problem, alert the user if appropriate, and instruct the user to cycle bluetooth if the use case warrants. This is hardly an ideal solution, I know.

这篇关于Objective-C蓝牙表示已连接,但CBCentralManager未发现它的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆