iOS: stay connected to an external BLE device as much as possible

Stefan Walkner
arkulpa
Published in
4 min readMar 20, 2017

At arkulpa, we built an app for a customer where — after the initial setup — it was necessary to stay connected to an external BLE device as much as possible without the user having to do anything. The BLE device is a home accessory, the goal was that if the user leaves the house and returns maybe not before days or weeks later the connection should be re-setup automatically.

For a detailed introduction into iOS/Swift/BLE, please head over to this really great blog post (three parts). This article here can be seen as extension which focuses on keeping the bluetooth connection alive and includes some responses which Apple Developer Technical Support provided.

Learning #1

If you want to find the BLE device when your app is in the background, do not use all the nil s here:

centralManager.scanForPeripherals(withServices: nil, options: nil)peripheral.discoverServices(nil)peripheral.discoverCharacteristics(nil, for: service)

According to the Apple documentation:

Apps that have specified the bluetooth-central background mode are allowed to scan while in the background. That said, they must explicitly scan for one or more services by specifying them in the serviceUUIDs parameter. The CBCentralManager scan option is ignored while scanning in the background.

So what we are doing is scanning with the nil options during the setup process (= app is in the foreground) and then save all the UUIDs (e.g. in the UserDefaults). When we need to scan while the app is in the background, we can use the saved UUIDs.

Learning #2

We needed a way to have the app reconnect automatically in the background whenever the connection gets lost, most of the time because the distance between iPhone and BLE gets too big. As it turned out, this is very simple (although you have to know it): simply call the connect-method again in func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)because…

[…] connection requests do not time out, the iOS device will reconnect when the user returns home.

The problem with this: it only works if none of the following happens:

  1. device reboot
  2. bluetooth radio being turned off and back on
  3. Airplane mode turned on then off
  4. If, for some reason, the BLE stack crashes
  5. And, most importantly, user force quits your app

(quoted from a mail from Apple Developer Technical Support). Although the “reconnect”-solution always worked for me for 2.) and 3.), there are still the other use cases where the connection could not be reestablished. This leads us to…

Learning #3

We were looking for a solution to wake up the app in the background and re-establish the connection. What we found was combining an iBeacon with the BLE device where the iBeacon is responsible for waking up the device, because according to Apple (again, Developer Technical Support):

There are very few services you can use that will relaunch your app after the user force quits it. Region monitoring is one of these.

[…]

Yes, Region Monitoring will wake up the app, relaunch it if necessary, and will give you 10 seconds to do what you need to do.

You can combine this with the state restoration scheme you mentioned, and that will be enough time to setup your BLE stuff and let the system wake you up again when the BLE side is connected.

Alternatively you can extend this 10 seconds to up to 180 seconds, using https://developer.apple.com/reference/uikit/uiapplication/1623031-beginbackgroundtaskwithexpiratio but that adds more complexity, and I don’t believe you need that much time.

If your BLE device is advertising properly and fast enough, you should be able to scan, discover, and connect in the 10 seconds.

But that’s not enough: having the iPhone near an advertising iBeacon isn’t enough to wake it up — only enter and exit events wake up the app. So what we did — together with our hardware department — was implement the iBeacon in a way that it only advertises whenever the BLE is not connected. So when the user force quits the app, the BLE connections gets lost (and cannot be reconnected due to #5 from above) and the iBeacon starts sending again. Now our app gets the

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion)

method called and can re-establish the connection to the BLE device.

Learning #4

This one is not really specific to staying-connected, but something that cost us some time: never ever store the peripheral.identifier hardcoded in the app, not even for testing. We did exactly that: only to discover that after testing on a different device we suddenly had problems to find the peripheral (i.e. it was impossible to discover it). Reason: the UUID is generated and hides the MAC address, therefore different iOS devices scanning the same peripheral generate different UUIDs for the same peripheral.

Working with BLE is really cool and interesting, although you need to know what you’re doing and how. But once you get all your pieces together you are rewarded with really awesome functionality!

--

--