4

I am currently writing an app that connects via BLE to an external device. All operations are fine when the app is in foreground.....including connecting, obtaining data, and reconnecting (in cases of the device going out of range) via a reconnect protocol I wrote. The app also functions properly when it is backgrounded but the BLE connection remains alive.

However, the only instance in which the app does not function is if the app is backgrounded and then the BLE device goes out of range. Once the connection is broken, the app seems to be suspended by iOS after a few seconds and none of the code I wrote will continue to function...even if I bring the device back into range. The only way to restore functionality is to bring the app back into the foreground again. (Note: I have the info.plist file and all other settings configured appropriately for centralManager background functionality)

I've read some documentation and it seems that this comes down to not having state preservation/restore code properly implemented. I went ahead and implemented the "willRestoreState" and "didUpdateState" commands, but the app still doesn't reconnect to a device once it has been suspended when in background mode.

I've shown some relevant code below, including the willRestoreState, didUpdateState, and didDisconnect methods. Any ideas or suggestions? Thanks!

//define service+characteristic UUIDS
let serviceUUID                          = CBUUID(string: "xxxxxxxxx")
let streamingCharacteristicUUID          = CBUUID(string: "xxxxxxxxx")

//Local dictionary of UUIDs for connected devices (the ble code updates this var with each connected device)
var devicesUniqueId:[UUID:String] = [UUID:String]()

//Local dictionary of connected peripherals, with respect to each of their UUIDS (the ble code updates this var with each connected device)
var sensorPeripheral = [UUID:CBPeripheral]()



///restoreState function
 func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {

    if let peripheralsObject = dict[CBCentralManagerRestoredStatePeripheralsKey] {

        let peripherals = peripheralsObject as! Array<CBPeripheral>       
        print ("starting restorestate code")
        if peripherals.count > 0 {
            for i in 0 ..< peripherals.count {
                print ("starting restorecheck")

//Check if the peripheral exists within our list of connected peripherals, and assign delegate if it does
                if self.devicesUniqueId.keys.contains(peripherals[i].identifier) {
                    peripherals[i].delegate = self
                }
            }
        }
    }
}



 func centralManagerDidUpdateState(_ central: CBCentralManager)
{ 
    if central.state != .poweredOn
    {
        return
    }
     self.startScanning()

    //////Preservation + Restoration code////////


//Iterate through array of connected UUIDS
    let keysArray = Array(self.patchDevicesUniqueId.keys)
        for i in 0..<keysArray.count {

//Check if peripheral exists for given UUID
           if let peripheral = self.sensorPeripheral[keysArray[i]] {              
            print("peripheral exists")       

//Check if services exist within the peripheral
            if let services = peripheral.services {           
                 print("services exist")   

//Check if predefined serviceUUID exists within services 
                if let serviceIndex = services.index(where: {$0.uuid == serviceUUID}) {                 
                     print("serviceUUID exists within services")          
                    let transferService = services[serviceIndex]
                    let characteristicUUID = streamingCharacteristicUUID        

//Check if predefined characteristicUUID exists within serviceUUID
                    if let characteristics = transferService.characteristics {                    
                         print("characteristics exist within serviceUUID")                           

                        if let characteristicIndex = characteristics.index(where: {$0.uuid == characteristicUUID}) {                               
                             print("characteristcUUID exists within serviceUUID")                      
                            let characteristic = characteristics[characteristicIndex]  

//If characteristicUUID exists, begin getting notifications from it
                            if !characteristic.isNotifying {                                   
                                 print("subscribe if not notifying already")
                                peripheral.setNotifyValue(true, for: characteristic)
                            }
                            else {                                    
                                 print("invoke discover characteristics")    
                            peripheral.discoverCharacteristics([characteristicUUID], for: transferService)
                            }                               
                        }                                                        
                    }                        
                }
                else {
                    print("invoke discover characteristics")
                    peripheral.discoverServices([serviceUUID])
                }                 
            }            
        }
    }
}



//didDisconnect method to handle a connect command issue
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)
{


//commented out unnecessary code

    self.removePeripheralData(peripheral: peripheral)

    if(sensorCount>0){
        sensorCount -= 1
    }

}


//removePeripheralData function used in didDisconnect
 func removePeripheralData ( peripheral: CBPeripheral) {

   //Commented out unnecessary code

            //Issue reconnect command
            print ("issuing reconnect command")
              centralManager.connect(peripheral, options: nil)

     //Commented out unnecessary code

    handleDidRemoveDevice()
}
riceman89
  • 179
  • 1
  • 13
  • 1
    Can you show you `didDisconnect` method? Generally all you need to do is call `peripheral.connect` in this method and CoreBluetooth will reconnect your device once it comes back in to range, either in the background or the foreground. You do also need to handle central state restoration but you are already doing that – Paulw11 Apr 24 '17 at 21:22
  • Thanks for the response Paul. I just added my didDisconnect method into the code....I've been issuing a connect command upon each disconnect (and the terminal verifies that it has been issued).....but still no dice as to restoring BLE functionality while suspended if a device comes back into range. – riceman89 Apr 24 '17 at 21:57
  • What object holds your CBCentralManager? The code you have shown should result in a call to `didConnect` once the peripheral comes in to range I have done this many times. – Paulw11 Apr 24 '17 at 22:59
  • CBCentralManager is listed under my BleService class, and BleService is initialized as a singleton in the form of: "static let shared = BleService()" Are you aware of any problems that this may cause? Thanks again! – riceman89 Apr 25 '17 at 02:23
  • It might be worth noting that, according to the debugger terminal, the code in the willRestoreState and the latter half of the didUpdateState methods doesn't even execute after the app has been suspended. Perhaps there is something preventing the execution of this code that I am missing? – riceman89 Apr 25 '17 at 21:56
  • You wouldn't expect `willRestoreState` to be executed after a suspension. That method is only executed when your app is re-launched in the background for a Bluetooth event. Bluetooth events where the app is suspended simply result in the delegate method being called in the background. – Paulw11 Apr 25 '17 at 22:00
  • You can take a look at my [sample code](https://github.com/paulw11/BTMonitor) that implements state restoration – Paulw11 Apr 25 '17 at 22:05
  • Thanks for the sample code! Quick question: I notice in your CentralManager initialization, you utilize a notification queue that is then referenced by the AppDelegate. I'm assuming this is not necessary for the suspend->reconnect functionality, correct? – riceman89 Apr 25 '17 at 22:39
  • The queue in the `BTManager` class is private. AppDelegate uses the main queue where required but does not (and cannot) refer to the `BTManager` `queue` – Paulw11 Apr 25 '17 at 22:42
  • Sorry for the confusion, I didn't phrase my question properly. Does the CentralManager initialization need to occur with a queue, or can we instantiate it with "Nil" and have the restoration functionality still work properly? – riceman89 Apr 25 '17 at 22:54
  • It will work on the main queue. The example in the Core Bluetooth Programming guide shows `nil` for the `queue`. – Paulw11 Apr 25 '17 at 22:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/142672/discussion-between-paulw11-and-riceman89). – Paulw11 Apr 25 '17 at 23:33

0 Answers0