3

I'm trying to scan for a specific device by it's service uuid - in this case JBL speaker, in future cases, I would like to scan for devices inside a car. At first, I scanned for nearby peripherals with nil.

self.centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey:true])

Once I found my speaker (this is the data I get):

peripheral identifier =  0BD95D98-D979-67DA-F3AA-C6C03781E70B
discovered peripheral =  <CBPeripheral: 0x283ed4000, identifier = 0BD95D98-D979-67DA-F3AA-C6C03781E70B, name = JBL Flip 4, state = disconnected>
advertisement data =  ["kCBAdvDataRxSecondaryPHY": 0, "kCBAdvDataTimestamp": 643124133.584141, "kCBAdvDataIsConnectable": 1, "kCBAdvDataManufacturerData": <5700d11e 0100ff58>, "kCBAdvDataRxPrimaryPHY": 0, "kCBAdvDataLocalName": JBL Flip 4]
kCBAdvDataManufacturerData =  {length = 8, bytes = 0x5700d11e0100ff58}

I connected to the speaker, and when I got the didConnect callback I called for discover services:

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        
        self.connectedPeripheral = peripheral
        peripheral.delegate = self
        
        peripheral.discoverServices(nil)
}

When didDiscoverServices services returned I saw the following output:

services =  Optional([<CBService: 0x281adc700, isPrimary = YES, UUID = 65786365-6C70-6F69-6E74-2E636F6D0000>, <CBService: 0x281adcb40, isPrimary = YES, UUID = FE8F>])

At this point, I wanted to use the service uuids that I got, and to scan for the JBL speaker. So instead of called for scanForPeripherals with nil, I used an array with the services like this:

self.centralManager?.scanForPeripherals(withServices: [CBUUID(string: "FE8F"), CBUUID(string: "65786365-6C70-6F69-6E74-2E636F6D0000")], options: [CBCentralManagerScanOptionAllowDuplicatesKey:true])

Then I relaunched the app, but this time I didn't find any device. Why is this so?

Keselme
  • 3,779
  • 7
  • 36
  • 68
  • 1
    That's normal behavior. Because the service isn't in the `advertisementData`. It's not telling (by advertising it) that it has service `FE8F` and `"65786365-6C70-6F69-6E74-2E636F6D0000`. So `scanForPeripherals(forServices:)` won't return them. – Larme May 19 '21 at 14:04
  • 1. So in order for this to work, the service must be included in the advertisement data? 2. What are the service uuids that I got from `discoverServices` then? – Keselme May 19 '21 at 14:11
  • 1
    It's like seeing a person (discover), and asking its job (services). You won't know by looking at him/her that's she/he knows how to play the violin, do some surgery or knows how to bake the most delicious baguette. It's not telling you that. If that person was yielding a sign with info, that's be its advertisement data, and you'll know, but else, you can't. – Larme May 19 '21 at 14:13

1 Answers1

4

See the doc of scanForPeripherals(withServices:options:):

You can provide an array of CBUUID objects — representing service UUIDs — in the serviceUUIDs parameter. When you do, the central manager returns only peripherals that advertise the services you specify

The service you got 65786365-6C70-6F69-6E74-2E636F6D0000, you got it AFTER you made before a connection to it (didConnect), and after "scanning more infos on the device" (discoverServices).
But it was not advertising it, it was not telling everyone that it has a service with that UUID. It keeps it secret. So you can't scan for it.

Larme
  • 24,190
  • 6
  • 51
  • 81
  • 1
    So basically unless the peripheral has the service uuid in its advertisement data, I can't scan for that specific service in advance? – Keselme May 19 '21 at 14:13
  • 1
    Exactly. That's how advertisement data works, and how the scan by advertisement work. Else, it would mean too much work to scan for any peripheral, connect to them, discover each of their services, (and maybe their char ?) each time. That wouldn't be "low energy" as BLE is supposed to be. Also, what about devices where you need real pairing ("code"), with encrypted data, hidden features, etc. – Larme May 19 '21 at 14:16
  • 1
    A BLE device is just by its advertisement saying "I'm here, my name is" and maybe telling one or two things (its manufacturer, some of its services. The scan is just listening to that. – Larme May 19 '21 at 14:17
  • 1
    I also want my app to scan for peripherals while it's in the background, but according to documentation, it works only if I scan for specific services by their uuids. Am I right to assume, that if I want to scan in the background the manufacturer of the peripheral must give me the uuids of the services in advance - like documentation or something similar..? – Keselme May 19 '21 at 14:20
  • 1
    It seems so, but it's been a while I haven't played with BLE and background. It's not they "must give you the services in advance in the doc", it's "the device MUST advertise them". But since it's an Audio device (I guess it's a JBL), you might be be able to do a real pairing and use `retrievePeripherals` – Larme May 19 '21 at 14:26