10

I scan for my peripheral like this:

NSDictionary *scanOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] 
                                                            forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
        // Scan for peripherals with given UUID
        [cm scanForPeripheralsWithServices:[NSArray arrayWithObject:HeliController.serviceUUID] options:scanOptions]

No problem there, I find the peripheral and are able to connect to it. As you can see I give it CBCentralManagerScanOptionAllowDuplicatesKey with bool NO to not allow for more than one peripheral, but sometimes the didDiscoverPeripheralcallback fires twice.

- (void) centralManager:(CBCentralManager *)central 
  didDiscoverPeripheral:(CBPeripheral *)peripheral 
  advertisementData:(NSDictionary *)advertisementData 
               RSSI:(NSNumber *)RSSI 
{        
if(!discovered){
    discovered = YES;
    NSLog(@"Discovered");

    [cm stopScan];

    [scanButton setTitle:@"Connect" forState:UIControlStateNormal];
}
else if(discovered){
    discovered = YES
    NSLog(@"Already discovered");
}
}

Some times I get

Discovered
Already discovered

as output in my console, and most of the times only the Discoveredmessage shows.

In my peripheral delegate I first discover services, which then call [peripheral discoverCharacteristics and the callback always occurs:

- (void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{

NSLog(@"Did discover characteristic for service %@", [service.peripheral UUID]);

for(CBCharacteristic *c in [service characteristics]){
    // We never get here when peripheral is discovered twice
    if([[c UUID] isEqual:myCharacteristicUUID]){

        NSLog(@"Found characteristic");

        self.throttleCharacteristic = c;

    }
}

When didDiscoverPeripheral occur twice, service becomes nilin this method, even though peripheral is not (UUID, name is still correct).

Rebooting the phone or resetting the network settings fixes the problem temporarily.

I really need to get this fixed! Thank you

chwi
  • 2,752
  • 2
  • 41
  • 66

2 Answers2

12

Devices may return additional data while advertising. These may arrive in separate packets, arriving at different times. In this case, didDiscoverPeripheral is called first when the device is initially seen, and then again when additional information becomes available for it.

CBCentralManagerScanOptionAllowDuplicatesKey is different. It tells CoreBluetooth whether you want to receive duplicate results when the device advertises itself again. It doesn't prevent multiple calls to didDiscoverPeripheral for the same discovery sequence; it prevents it for repeated discovery sequences.

Source: http://lists.apple.com/archives/bluetooth-dev/2012/Apr/msg00047.html (the Apple rep on bluetooth-dev).

Glenn Maynard
  • 55,829
  • 10
  • 121
  • 131
  • @Wilhelmsen: What needs to be solved? Just track the UUIDs you've seen already. – Glenn Maynard Jul 31 '12 at 15:10
  • I tried @Mike's answer, doesn't that keep track of UUIDs as well? I am fairly new at this, so if you have any further hints on how to programatically achieve this I would be really grateful! Anyway, thank you very much for your information – chwi Aug 01 '12 at 07:40
  • 1
    didDiscoverPeripheral returns CBPeripheral objects. Each of these objects has a UUID attribute that you can use to keep track of the first time and the second time the callback happens on the same peripheral object. – yuklai Aug 02 '12 at 05:16
  • That "additional data" is called the *scan response*, and the peripheral only sends that additional data when the central asks for it. In this case, CoreBluetooth automatically asks for it for every device it discovers. – Marcus Adams Mar 13 '15 at 14:06
  • I was looking for this but I'm not sure about this. How to check if the second time we didDiscoverPeripheral is for additional data? In my case the advertisementData are the same. – SeikoTheWiz Jan 24 '17 at 10:19
6

I don't think this parameter does what you think it does. My understanding from looking at how it is used in Apple samples like the Health Thermometer is that turning this flag on allows discovery of multiple different peripherals with the same UUID. For example, if you want to write an app that looks at four different thermometers in the same room, and finds all of them, you would need the parameter so the scan didn't stop after finding the first one.

In their code, Apple avoids duplicates like this:

NSMutableArray *peripherals = [self mutableArrayValueForKey:@"thermometers"];
if( ![self.thermometers containsObject:peripheral] )
    [peripherals addObject:peripheral];

If the device already exists in the array, it is not added a second time.

It would be nice if the documentation was clearer on this point. I admit I'm guessing based on how the parameter is used in context.

Mike
  • 3,084
  • 1
  • 25
  • 44
  • Thank you! should this go into my didDiscoverPeripheral method? – chwi Jul 21 '12 at 07:15
  • Yes, that's where Apple put it, anyway. See http://developer.apple.com/library/mac/#samplecode/HealthThermometer/Listings/HealthThermometerClient_HealthThermometerClientAppDelegate_m.html#//apple_ref/doc/uid/DTS40011370-HealthThermometerClient_HealthThermometerClientAppDelegate_m-DontLinkElementID_4 for the complete source. – Mike Jul 21 '12 at 20:57
  • I actually think that solved it. I have not been able to replicate it any how :D I`ll post an answer on my question just to clearify. Thank you very much – chwi Jul 24 '12 at 12:23
  • I was wrong, it worked for a good time, then back to shit again – chwi Jul 24 '12 at 12:56
  • I think the parameter you mentioned should do what you said. Can you share out the code that you set the parameter? – yuklai Jul 26 '12 at 01:16
  • setting CBCentralManagerScanOptionAllowDuplicatesKey is shown the question – chwi Jul 26 '12 at 10:54
  • update: Instead of rebooting the phone I can also reset network settings,works exactly the same – chwi Jul 30 '12 at 06:25
  • 1
    This is incorrect. Different peripherals will never have the same UUID; if there are four thermometers, there are four UUIDs. Setting CBCentralManagerScanOptionAllowDuplicatesKey allows the *same device* to be returned multiple times, so you'll receive the didDiscoverPeripheral callback over and over so long as the device is advertising and you haven't connected to it. Setting it to NO (the default) means that you only want to see it the first time. – Glenn Maynard Jul 30 '12 at 23:39
  • 3
    There is a difference between the peripheral UUID and the service UUID. The scanForPeripheralsWithServices:options: uses a service UUID, which would (presumably) be duplicated across multiple devices providing the same information, such as thermometers. The UUID for the device itself, which is not used in this call, is a different issue. I assume, but do not know for sure, that they are unique. The original post did not make that point clearly. – Mike Jul 31 '12 at 16:21
  • didDiscoverPeripheral is called twice because your advertising packet is larger than 31 bytes (the maximum per packet) and the rest of the data needs to get sent, I imagine you're setting a service with CBAdvertisementDataServiceUUIDsKey and also a name of the device via CBAdvertisementDataLocalNameKey? Or possibly one of the other advertising values you can send. – afrederick Sep 24 '12 at 22:04
  • @afrederick, the advertising packet can't be larger than can be sent in one packet. `didDiscoverPeripheral` is called again to deliver the *scan response* data, which CoreBluetooth asks for once it receives the first advertising packet. – Marcus Adams Mar 13 '15 at 14:04
  • But this `CBCentralManagerScanOptionAllowDuplicatesKey` is ignored while background scanning. How will I connect to these 4 thermometers in the background because it will be coalesced into one and advertised as one packet by iOS? @Mike – Shashank Agarwal Sep 23 '18 at 17:04