Bluetooth peripheral connected to iOS doesn't work in the background

I’m having an issue with a bluetooth peripheral working with a backgrounded iOS app. I need the app to respond to all bluetooth notifications in real-time. I’ve followed Apple's CoreBluetooth background process guide and it works around 10% of the time my app is backgrounded, but the other 90% of the time the events are queued and sent to my app in batches every 10 minutes.


I just need to fire a quick local subnet network request over wifi after a bluetooth event, so it takes well under the 10s iOS gives apps to work in the background.


As for CoreBluetooth settings, I followed the guide to the letter, and it does work in the background occasionally. But it also batches up notifications and re-launches my app every ten minutes, which makes me think it is configured correctly.


I can confirm the app isn’t being killed. I can keep the debugger attached and it continues to run. In fact, the debugger shows that the notifications get queued and the app enters the foreground every ten minutes. It does work for a few minutes after I use the app. But it inevitably gets fully backgrounded.


I would expect the app to be woken up as soon as a bluetooth notification comes in. Checking the connected devices list in the iOS Settings > Bluetooth screen shows a connected peripheral. And it's clear iOS is capturing the notifications. But it rarely wakes the app to send the notifications.

Accepted Reply

In case this helps somebody else, I figured it out. Let’s play spot the bug...


class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {




    var window: UIWindow?
    var bluetoothManager = TTBluetoothManager()




    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        ...
        return true
    }
    ... 
}


See that `var bluetoothManager = TTBluetoothManager()` line? It should be a `var bluetoothManager: TTBluetoothManager!` and `bluetoothManager = TTBluetoothManager()` should be in `application(_:didFinishLaunchingWithOptions:)`



That did the trick and now my Bluetooth device runs in the background.

Replies

So the issue is not in the device itself. It stays connected and even iOS's Settings app shows the device is still connected. I realized though that I had `beginBackgroundTaskWithExpirationHandler` running on `applicationWillResignActive`, which was giving me pseudo-background activity. When I commented that out, the app no longer un-suspends on bluetooth notification from my device.


All of the bluetooth notifications are queued and all immediately fire when the app is re-launched manually. Debugging the app shows no activity when the app is in the background. So now it just looks like I haven't configured the app to correctly work with Bluetooth in the background.


So my hunch is that something is mis-configured in following the CoreBluetooth background doc. But there's only a few steps and I have implemented each of them. I would expect `launchOptions?[UIApplicationLaunchOptionsBluetoothCentralsKey]` to be sent to `application(_:didFinishLaunchingWithOptions:)`, but it is never sent. And `centralManager(_:willRestoreState:)` is never called.


  • I have "Uses Bluetooth LE accessories" enabled
  • I'm using CBCentralManagerOptionRestoreIdentifierKey:
manager = CBCentralManager(delegate: self, queue: nil, options:
                           [CBCentralManagerOptionRestoreIdentifierKey: "TTcentralManageRestoreIdentifier",
                            CBConnectPeripheralOptionNotifyOnDisconnectionKey: NSNumber(bool: true),
                            CBConnectPeripheralOptionNotifyOnConnectionKey: NSNumber(bool: true),
                            CBConnectPeripheralOptionNotifyOnNotificationKey: NSNumber(bool: true)])

I'm subscribing to the characteristic notification:

func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {  
    if service.UUID.isEqual(CBUUID(string: DEVICE_V2_SERVICE_BUTTON_UUID)) {
        for characteristic: CBCharacteristic in service.characteristics! {
            if characteristic.UUID.isEqual(CBUUID(string: DEVICE_V2_CHARACTERISTIC_BUTTON_STATUS_UUID)) {
                peripheral.setNotifyValue(true, forCharacteristic: characteristic)
                let device = foundDevices.deviceForPeripheral(peripheral)
                device?.buttonStatusChar = characteristic
            } else if characteristic.UUID.isEqual(CBUUID(string: DEVICE_V2_CHARACTERISTIC_NICKNAME_UUID)) {
                peripheral.readValueForCharacteristic(characteristic)
            }
        }
    }
    ...
}

`UIApplicationLaunchOptionsBluetoothCentralsKey` is never called.

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    ...
    let centralManagerIdentifiers = launchOptions?[UIApplicationLaunchOptionsBluetoothCentralsKey]
    if centralManagerIdentifiers != nil {
        print(" ---> centralManagerIdentifiers: \(centralManagerIdentifiers)")
    }
    ...
}

Finally, `centralManager(_:willRestoreState:)` is never called:

func centralManager(central: CBCentralManager, willRestoreState dict: [String : AnyObject]) {
    manager = central
    let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as! [CBPeripheral]
    print(" ---> Restoring state: \(peripherals)")
    ...
}

In case this helps somebody else, I figured it out. Let’s play spot the bug...


class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {




    var window: UIWindow?
    var bluetoothManager = TTBluetoothManager()




    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        ...
        return true
    }
    ... 
}


See that `var bluetoothManager = TTBluetoothManager()` line? It should be a `var bluetoothManager: TTBluetoothManager!` and `bluetoothManager = TTBluetoothManager()` should be in `application(_:didFinishLaunchingWithOptions:)`



That did the trick and now my Bluetooth device runs in the background.

thank you!