0
votes

I'm struggling with the problem which is, I can not reestablish the connection with my BLE Device when user force quits the app. My app works like this:

  1. First launch the app user should connect with the BLE Device - that works
  2. When app is in background the connections is still working
  3. If I turn off the BLE Device and turn on again the connection is reestablished - works
  4. When user goes out and turn back home the connection is reestablished and user gets notification that he is close to the Device - works

All of this is solved by turning Bluetooth 4.0 LE into iBeacon. When user is close enough an iBeacon starts sending signals and iOS app is triggered and user gets push notification an connection is reestablished.

The problem appears when user force quits the app. iBeacon still works because when user is connected to BLE the BLE device works as.. Bluetooth device, but when the connection is lost BLE works as iBeacon and advertise the iOS app. When user force quits the app the user gets local push notification that app is close to iBeacon so it means the app is relaunch for the couple of seconds (10s I think) but I can not with this reestablish connection.

This is the code for tigger the notification and as I hoped for BLE reconnection after fore quit.

   func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
    // Check if the user's INSIDE the region, not outside...

    guard state == CLRegionState.inside else { print("Error"); return }

    if state == CLRegionState.inside{

        let content = UNMutableNotificationContent()
        content.title = "Device found!"
        content.body = "You're close to your BLE Device, reestablish connection!"

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
        let request = UNNotificationRequest(identifier: "1389402823904", content: content, trigger: trigger)

        UNUserNotificationCenter.current().add(request) { (err:Error?) in
        }

        centralManager.scanForPeripherals(withServices: nil, options: nil)
        let uuid = UUID(uuidString: "74278BDA-B644-4520-8F0C-720EAF059935")!
        guard let mainPeripheral = mainPeripheral else {return}
        centralManager.connect(mainPeripheral, options: nil)

        centralManager.retrievePeripherals(withIdentifiers: [uuid])
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
            self.centralManager.stopScan()
        }

    }

}

}

For the simplicity the uuid is hardcoded. As I understand the didDeterminateState is triggered when iBeacon sends signal and I am checking the CLRegionState if the region is inside which means close to the iBeacon the method for local notifications gets triggers, then I need to rescan for BLE devices so I am calling scanForPeripherals but not the connect method. ScanForPeripherals should call the didDiscover method:

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
    print(peripheral)

    if peripheral.name == "DOPEY"{

        peripheral.delegate = self
        peripheral.discoverServices([HM_10_ServiceCharacteristic])
        mainPeripheral = peripheral
        centralManager.connect(peripheral, options: nil)

        centralManager.stopScan()

    }

}

When it founds DOPEY peripheral name the device should connect. For tests I am using HM-10 Bluetooth 4.0 LE. When user force quits the app it sends local push notification but does not reestablish the connection.

I am looking forward for any kind of help. Appreciate!

2

2 Answers

3
votes

There is no need to re-scan for the device; You should save its identier property the first time you discover it. You can then use retrievePeripherals(withIdentifiers:) to try and get the reference to the CBPeripheral. If you succeed, simply call connect.

Your code shows an attempt to do something like this, but you have used (what I presume is) your service UUID, and you aren't doing anything with the result from retrievePeripherals(withIdentifiers:) (You also won't reach this code because of the guard statement).

You should use something like:

func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
    // Check if the user's INSIDE the region, not outside...

    guard state == CLRegionState.inside else { print("Error"); return }


    let content = UNMutableNotificationContent()
    content.title = "Device found!"
    content.body = "You're close to your BLE Device, reestablish connection!"

    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
    let request = UNNotificationRequest(identifier: "1389402823904", content: content, trigger: trigger)

    UNUserNotificationCenter.current().add(request) { (err:Error?) in
    }

    if self.mainPeripheral == nil {
        let defaults = UserDefaults.standard
        if let uuidString = defaults.string(forKey:"peripheralID"),
           let uuid = UUID(uuidString: uuidString),
           let peripheral = centralManager.retrievePeripherals(withIdentifiers: [uuid]).first {
            self.mainPeripheral = peripheral
        }
    }


    if let mainPeripheral = mainPeripheral 
        centralManager.connect(mainPeripheral, options: nil)
    } else {
        //TODO: Scan for peripheral.  Note that you can't use `services:nil` in the background
    }

}


func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
    print(peripheral)

    if peripheral.name == "DOPEY" {
        let defaults=UserDefaults.standard
        defaults.set(peripheral.identifier.uuidString,forKey:"peripheralID")
        peripheral.delegate = self    
        mainPeripheral = peripheral
        centralManager.connect(peripheral, options: nil)
        centralManager.stopScan()
    }    
}

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    peripheral.discoverServices([HM_10_ServiceCharacteristic])
}
1
votes

You don't need to make your BLE act as a beacon if the user don't kill the app. I mean once the peripheral has been disconnect to reconnect it again while is available you just need to call connect again in the disconnection callback, if your app has the capability to run in the background as a central, it will work perfectly. This behavior seems to be more reliable than the iBeacon region monitoring.
Problem starts if the user kill your app. You must be aware that this behavior is the one that Apple has designed, I mean if a user kill your app probably it doesn't want to be bothered with connections to some devices.
In these case the only solution seems to be make the device works as a iBeacon. In these case if the peripheral is detected your app is launched in background for a short amount of time. The fact is that scanning in background only works if you set a specific service CBUUID of your device.

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.

Why aren't you using only the retrievePeripheralWithIdentifiers? Once you make a connection to a device this UUID is set.
Scan is asynchronous, so why are you trying to connect to the peripheral right after calling the scan method?
You should pick one way or the other