Reputation: 284
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:
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!
Upvotes: 1
Views: 1521
Reputation: 26385
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
Upvotes: 1
Reputation: 114773
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])
}
Upvotes: 5