Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iPhone does not discover Services on a Bluetooth LE tag on reconnection

I am working on a Bluetooth LE application for iOS. I am using the Core Bluetooth framework within iOS to handle all communications.

Question & Description:

When I use a single tag, despite numerous connections and disconnections, the single tag connects seamlessly and the phone discovers it services.

Also, when multiple Bluetooth LE tags connect for the first time, they connect seamlessly and the phone discovers their services.

When the tags get disconnected and then reconnect to the phone, the tags connect fine. But one of the two tags (either one) does not seem to advertise its services. i.e when the app is open and the tag reconnects, the DiscoverServices method does not call the didDiscoverServices delegate.

Why is this happening only when connection with multiple devices takes place.

I have set the peripheral.delegate correctly. I have tried everything, including doing repeated re-connect, repeated DiscoverServices calls to the tag. Nothing seems to work.

How can I re-connect to multiple tags to the phone and still discover all services.

Please help

Thanks,
Manju

like image 317
Manju Kiran Avatar asked Aug 17 '12 11:08

Manju Kiran


3 Answers

I was facing a similar issue with CoreBluetooth to connect to Bluetooth LE devices, in my case connecting to iOS devices (peripherals) from my Mac (central).

If I get you correctly, the pattern is quite consistent, the first time I run my Mac app for debuging, it always detected and connected to any bluetooth LE devices (peripherals), most importantly, it also discover their services/characteristics fine. The problem starts on the second run (for example, change some code, hit cmd-R to relaunch the debug). The central still detects peripherals and connects to them, but, it fails to discover any services/characteristics. In other words, the delegate peripheral:didDiscoverServices: and peripheral:didDiscoverCharacteristicsForService:error: never get called.

The solution after a lot of trial and errors, is surprisingly simple. It seems that CoreBluetooth caches services and characteristics for peripherals that are still connected, although locally it looks like it had been disconnected to the app, the peripheral still maintains a bluetooth connection to the system. For these type of connections, there is no need to (re)discover the services and characteristics, just access them directly from the peripheral object, check for nil to know if you should discover them. Also, as mentioned, since the peripheral is in a state that is in between connections, it is best to call cancelPeripheralConnection: right before attempting to connect. The gist of it as following, assuming we already discovered the peripheral to connects to:

-(void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    [central cancelPeripheralConnection:peripheral]; //IMPORTANT, to clear off any pending connections    
    [central connectPeripheral:peripheral options:nil];
}

-(void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    peripheral.delegate = self;

    if(peripheral.services)
        [self peripheral:peripheral didDiscoverServices:nil]; //already discovered services, DO NOT re-discover. Just pass along the peripheral.
    else
        [peripheral discoverServices:nil]; //yet to discover, normal path. Discover your services needed
}

-(void) peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    for(CBService* svc in peripheral.services)
    {
        if(svc.characteristics)
            [self peripheral:peripheral didDiscoverCharacteristicsForService:svc error:nil]; //already discovered characteristic before, DO NOT do it again
        else
            [peripheral discoverCharacteristics:nil
                                     forService:svc]; //need to discover characteristics
    }
}

-(void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    for(CBCharacteristic* c in service.characteristics)
    {
        //Do some work with the characteristic...
    }
}

This works well for me for a CBCentralManager in Mac app. Never tested it in iOS, but I assume it should be quite similar.

like image 60
P.L. Avatar answered Nov 10 '22 05:11

P.L.


I had the same problem but realized that I wasn't setting the delegate to CBPeripheral after didConnectPeripheral is called.

- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@"Peripheral Connected: %@", peripheral.name);

    peripheral.delegate = self;

    if (peripheral.services) {
        [self peripheral:peripheral didDiscoverServices:nil];
    } else {
        [peripheral discoverServices:@[[CBUUID UUIDWithString:CUSTOM_UUID]]];
    }  
}
like image 29
jhalla14 Avatar answered Nov 10 '22 06:11

jhalla14


Turns out that there was command I was issuing to the device in the"didDiscovercharacteristicsForService" delegate method which was causing the connection instability.

If you are facing similar issues, I suggest you to let the delegate method complete without any intervention (of any kind) and pass the CBPeripheral to another function designed by you to pass any values / issue a command to the devices.

Thanks anyway Wilhemsen.

So the steps are as follows..
1> Search for Tag,
2> If in range, CONNECT to Tag
3> If Connected, call DISCOVER services method (do not interrupt)
4> IN DidDiscoverServices, call DISCOVER Characteristics Method
..
In DidDiscoverCharacteristics Method, wait until all Characteristics are discovered.. Then , at the end, call a function in your code that will do the necessary setup..
...
Code Sample

-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{

    for (int i=0; i < peripheral.services.count; i++) {
        CBService *s = [peripheral.services objectAtIndex:i];
        printf("Fetching characteristics for service with UUID : %s\r\n",[self CBUUIDToString:s.UUID]);
        [peripheral discoverCharacteristics:nil forService:s];
    }

}


- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{        
    if (!error)
    {
        CBService *s = [peripheral.services objectAtIndex:(peripheral.services.count - 1)];
        if([self compareCBUUID:service.UUID UUID2:s.UUID])
        {
           // This is when the **Tag** is completely connected to the Phone and is in a mode where it can accept Instructions from Device well.

            printf("Finished discovering characteristics");
           // Now Do the Setup of the Tag
            [self setupPeripheralForUse:peripheral];

        }
    }

    else
    {
        printf("Characteristic discorvery unsuccessfull !\r\n");
    }
}

-(void) setupPeripheralForUse:(CBPeripheral *)peripheral
{

    // DO all your setup in this function. Separate Perpiheral Setup from the process that synchronizes the tag and the phone. 

}
like image 1
Manju Kiran Avatar answered Nov 10 '22 05:11

Manju Kiran