Georg
Georg

Reputation: 3930

CoreBluetooth not discovering hear-trate monitor if other app is connected first

I want my users to be able to track their heart-rate with my app. So I use CBCentralManager for that. Everything works fine if no other app is connected to the heart-rate sensor yet. The problem I have is if I start f.e. Strava or Endomondo first. Then I just can't find any devices any more. The other way round everything works fine, so I guess I am missing an options somewhere?

What I currently do:

I instantiate my CBCentralManager like so

centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)

which will cause the delegate method for connection to be triggered

 func centralManagerDidUpdateState(_ central: CBCentralManager) {

    let heartRateServiceUUID = CBUUID(string: "180D")
    let services = [heartRateServiceUUID]

    switch central.state {

    case .poweredOn:
        centralManager.scanForPeripherals(withServices: services, options: nil)

and from there on no peripheral are found.

But again, when I force quit other apps like Endomondo or Strava and then start my app, everything works fine.

Upvotes: 2

Views: 1178

Answers (3)

whoswho
whoswho

Reputation: 202

Update: It appears that BLE4.0 chipset did only support one connection so when a centralmanager connected to a BLE4.0 device it stopped advertising and established a connected layer.

With BLE4.1 chipset it added support for multi-role connection. It is possible that the heart-rate sensor use newer BLE technique that support more than one connection. https://e2e.ti.com/blogs_/b/connecting_wirelessly/archive/2016/11/30/bluetooth-low-energy-multi-role-demystified

It is still unclear why your app wont connect if the other apps are connected. Can you post all your code?

My old answer: "Core bluetooth and Bluetooth Low Energy can only handle one connection for each peripheral which means that if your app(CBCentralManager) establish a connection with a peripheral(UUID) it establish a message layer between the central and peripheral so other apps cant interfere the connection to that peripheral unless you disconnect the first app. This is how Bluetooth Low Energy works."

Apple Guides and Samples: https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/AboutCoreBluetooth/Introduction.html#//apple_ref/doc/uid/TP40013257-CH1-SW1

Apple Core Bluetooth API: https://developer.apple.com/reference/corebluetooth

Upvotes: 2

Rob Napier
Rob Napier

Reputation: 299355

See Best Practices For Interacting With A Remote Peripheral Device for full details on how to connect to devices you already know about.

The main issue you're encountering is that most BLE devices stop advertising once they have a connection. Since they aren't advertising, you can't see them in a scan. In this particular case, the BLE device is connected to the iPhone you're running on, but that doesn't change anything. It's still not advertising.

To deal with this, you want to ask the iPhone for connected devices that have the service you want, using retrieveConnectedPeripherals(withServices:). This is a very fast, synchronous call, and you generally should do it before calling scan​For​Peripherals(with​Services:​options:​).

There are several other steps that you generally should do. The precise order and logic depends a little on your situation, but the linked flowchart above walks you through one approach. Basically it will look something like this:

  • Call retrieve​Peripherals(with​Identifiers:​) to find a peripheral you already know the identifier for. Note that this just tells you the system knows about the peripheral; it doesn't mean it's currently nearby. Calling connect on it may never succeed.

  • Call retrieveConnectedPeripherals(withServices:) to find a peripheral that is already connected to this iPhone and advertises your service. You still need to call connect on it for your process, but it should succeed.

  • If all the rest fails, then call scan​For​Peripherals(with​Services:​options:​).

Upvotes: 2

Georg
Georg

Reputation: 3930

Ok, I just found the solution to my main problem myself. So anybody who might have the same issue:

  1. Once you are connected to a device remember it's UUID
  2. When you want to reconnect to it you don't need to scan, just use centralManager.retrievePeripherals(withIdentifiers: [the id's of the devices you know])
  3. This will give you back a list of CBPeripherals and you simple connect to them like you would have after searching for them.

The only thing that I still don't know: Even when i uninstall Endomondo, start my app first, they still can discover my heartrate monitor... And I suppose even if they remember the UUID it will be in UserDefaults i suppose, so they can't reconnect using centralManager.retrievePeripherals(...). So I still like to know what I'd need to change in order for that to work...

Upvotes: 3

Related Questions