2

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.

Georg
  • 3,664
  • 3
  • 34
  • 75

3 Answers3

3

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...

Georg
  • 3,664
  • 3
  • 34
  • 75
  • 1
    Read "Reconnecting to peripherals" in https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/BestPracticesForInteractingWithARemotePeripheralDevice/BestPracticesForInteractingWithARemotePeripheralDevice.html. They're using `retrieveConnectedPeripheralsWithServices:` (and you should be too). Let me know if this is unclear and I can turn it into an longer answer. – Rob Napier Mar 27 '17 at 15:12
  • (BTW, I think your reputation is too low to see it, but @JensMeder gave all the details, but then deleted the answer. I don't know why. If you can see deleted answers, then read that one. I've voted to undelete it, but I can't do it unilaterally.) – Rob Napier Mar 27 '17 at 15:15
  • I can't see anything from a JensMeder :( I think I didn't even got an Email about it... But I'll give the `retrieveConnectedPeripheralsWithServices:` a shot! Thank you for pointing it out! – Georg Mar 28 '17 at 06:23
  • Finally got time to have a look at `retrieveConnectedPeripheralsWithServices:`! Works! Awesome! @RobNapier would you like to make a separate answer so that I can accept it for your reputation reward? – Georg Mar 28 '17 at 14:00
  • Added a longer answer with some more details. – Rob Napier Mar 28 '17 at 15:04
2

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

Cœur
  • 37,241
  • 25
  • 195
  • 267
whoswho
  • 202
  • 1
  • 10
  • Thanks for your help!!!! But why are Strava and Endomondo still be able to connect while I am connected? But the other way round it's now working? – Georg Mar 24 '17 at 08:44
  • Also when Strava is connected, Endomondo can still connect and the other way around as well... – Georg Mar 24 '17 at 08:45
  • What heart-rate sensor do you use? – whoswho Mar 24 '17 at 09:07
  • I have a H7 from Polar, but same happens with other brands (as reported by our users) – Georg Mar 24 '17 at 09:07
  • I have never experienced this behaviour with BLE before and now I might think that my BLE experience is a little outdated as you managed to connect two centrals to one peripheral. – whoswho Mar 24 '17 at 09:50
  • I am just wondering how strava, endomondo and others are doing it :) they can do it... but I can't and I also haven't found sample code that shows how. The only code I've found does it the same way I do it... :( – Georg Mar 24 '17 at 09:54
  • Well... basically the code that I posted above it the relevant part. I don't get any further when an other app connected to my heart-rate monitor first. I don't get any other delegate callback from `CBCentralManager` nor an other state change... nothing... It just doesn't find anything any more... Is there a way to connect to a device directly once I know it's UUID? Could this be how Strava and Endomondo still manage to properly connect? – Georg Mar 24 '17 at 12:16
2

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:​).

Rob Napier
  • 286,113
  • 34
  • 456
  • 610