6

I want to reconnect to BLE device after device is moved out/terminated by user or system/reboted in background mode.

I know that it's possible : - see this question with description

Question - How can i setup centralManager for automatically reconnect to peripheral in background mode if app was terminated? Can someone describe step-by-step how it can be done?

Few word about current implementation:

I create centralManager with options like:

self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil  options:@{
                                                                                               CBCentralManagerOptionRestoreIdentifierKey: @"myCentralManagerIdentifier",
                                                                                               CBCentralManagerRestoredStatePeripheralsKey : @YES,
                                                                                               CBCentralManagerRestoredStateScanServicesKey : @YES,
                                                                                               CBCentralManagerRestoredStateScanOptionsKey : @YES
                                                                                               }];

After that i start to scan for BLE device

[self.centralManager scanForPeripheralsWithServices:[self discoverableCharacteristics] options:nil];

in - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI i connect to peripheral:

    NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
    [self.centralManager stopScan];
    peripheral.delegate = self;
    [self.centralManager connectPeripheral:peripheral options: @{
                                                                CBConnectPeripheralOptionNotifyOnNotificationKey : @YES
                                                                }];

After that i can discover services and characteristics - all looks like ok. When i discover characteristic and read/write data i cancelPeripheralConnection

in didDisconnect i reconnect to device

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error
{
    [central connectPeripheral:peripheral options:nil];
}

i also implement centralManager:willRestoreState: like:

NSArray *peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey];
for  (CBPeripheral *peripheral in peripherals) {
    [central connectPeripheral:peripheral options:nil];
    peripheral.delegate = nil;
}

In plist. added required key App communicates using CoreBluetooth.

Currently if i connected to device and terminate it - it relaunch automatically and connect to device - all it's ok, but if it's terminated again - nothing is happening.

Also if i moved out from peripheral and that come back - nothing happened.


Update

regarding point 5 - my fall - should use this key with connectPeripheral

in WillRestoreState:

NSArray *peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey];
if (!peripherals.count) {
    peripherals = [central retrievePeripheralsWithIdentifiers:[self discoverableCharacteristics]];
}

if (peripherals.count) {
    for  (CBPeripheral *peripheral in peripherals) {
        [central connectPeripheral:peripheral options:@{
                                                        CBCentralManagerRestoredStatePeripheralsKey : @YES,
                                                        CBCentralManagerRestoredStateScanServicesKey : @YES,
                                                        CBCentralManagerRestoredStateScanOptionsKey : @YES
                                                        }];
         }
} else {
    [self startScanning];
}

Current result - app will relaunched if it not swiped out from tray. I use my mac as a peripheral, so some times when i not launch app that make role of peripheral central can connect to mac itself not to required service.

Another question - are it's good option to reconnect to peripheral while lost connection for keeping connection like:

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error
{
    [central connectPeripheral:peripheral options:@{
                                                CBCentralManagerRestoredStatePeripheralsKey : @YES,
                                                CBCentralManagerRestoredStateScanServicesKey : @YES,
                                                CBCentralManagerRestoredStateScanOptionsKey : @YES
                                                }];
}

Also try to change notify characteristic on peripheral and read it on device. If all done in foreground - all works perfect, but in case connection was done in background some times didUpdateValueForCharacteristic not called at all, but didUpdateNotificationStateForCharacteristic is called with no error - this mean (i think) that something was done wrong by my side. Maybe u can advice where problem can be

And one more question - is there is some restriction in writing data to characteristics? because in apple sample it's setuped to 20 bytes.

Community
  • 1
  • 1
hbk
  • 10,908
  • 11
  • 91
  • 124

1 Answers1

16

First off I wanna start by saying that I have been working with CoreBluetooth for about two years now and from what I have noticed CoreBluetooth State Preservation and Restoration does not work reliably at all. You can get it working sort of "ok", but you will never get it to reconnect reliably unless Apple fixes it some day.

Having said that, I want to note a few things on your setup.

1) In centralManager:willRestoreState: you can only retrieve peripherals that has done any communication while the app was terminated. This means that you should also implement centralManagerDidUpdateState: and if the state is CBCentralManagerStatePoweredOn then you can use the retrievePeripheralsWithIdentifiers: method to retrieve the other peripheral and reset their delegate as well. This of course means that you have to stor the peripheral identifiers somewhere in your app somewhere. Also remember to reset pending connections here as well.

2) You set the delegate to nil in centralManager:willRestoreState:! So even if it does connect then you will not know about it i:P

3) Your app will only get relaunched if the app was terminated by the system. It will not get relaunched if you manually swipe-kill it from the application list. Neither will it get relaunched if the device is rebooted, unfortunately.

4) The CBConnectPeripheralOptionNotifyOnConnectionKey is not necessary when using the bluetooth-central background mode and is just annoying for a user so I would not use it.

5) CBCentralManagerRestoredStatePeripheralsKey, CBCentralManagerRestoredStateScanServicesKey, CBCentralManagerRestoredStateScanOptionsKey are not valid initialisation options so I don’t get why you are using those..

5) If the bluetooth switches state while the app is terminated then all pending connections will be lost and you will not be relaunched to know about it. This on its own effectively means that State Restoration is rather useless.

Anyhow, I am sad to say it, but if you are developing an app that must rely on the peripheral being reconnected in the background then I wold not recommend doing it. You will be frustrated. I could probably write an essay about all the bugs in Core Bluetooth that Apple does not want to fix. Even more frightening is that you can pretty easily ruin the bluetooth connectivity globally on the device from one single app so that no app can use bluetooth until the device is rebooted. This is pretty bad since it goes against Apples own Sandboxing principle.

If you need any more help, just let me know!

/A

Anton
  • 978
  • 8
  • 16
  • Many thanks for your response. I update my code - currently it's looks like work fine if i not swipe out app form "apps stack".Please see update in question – hbk Dec 16 '15 at 09:25
  • Regarding number 3 above, it's really unfortunately that the Core Bluetooth documentation doesn't clearly state "Hey, your app needs to be in the MRU/suspended app list", but it does say things like "the system wakes it up from a suspended state to allow it to handle Bluetooth-related events" which seems to be their way of telling you that. Anyway, @Anton thanks for your answer. I'm sure it will help many others struggling with this aspect of developing with iOS and BLE with Core Bluetooth! :) – Evan K. Stone Dec 22 '15 at 19:42
  • Thanks Anton. Have Apple still not updated their api? – Bright Lee Aug 26 '16 at 03:58
  • Apple has not made any updates in regards to any of the topics that we have discussed here. They have however made some API changes in the iOS 10 beta, but they quite minor changes. – Anton Aug 26 '16 at 08:17
  • @Anton Just as a little follow-up, have there been improvements in reliability in iOS 9 or 10 since December? I'm particularly interested in State Preservation and Restoration and was wondering what the general consensus was about its stability, performance and reliability were these days... Thanks! – Evan K. Stone Sep 01 '16 at 21:45
  • Just one more question... How is it that one could "ruin the bluetooth connectivity globally on the device from one single app so that no app can use bluetooth until the device is rebooted"? I'm really interested in finding out what circumstances would cause it so I can avoid them, hopefully beyond just not using backgrounding... or is that your basic recommendation at this time? – Evan K. Stone Sep 01 '16 at 22:16
  • On your 3rd point. If an app is running in background when a device is shutdown will the OS notify the app when the phone is restarted? If so, how do you register for that notification? – bickster Sep 23 '16 at 17:45
  • Are you sure 3rd tip. I saw some app which wake up when app is terminated by user. For example Tile App. – yusufonderd Dec 19 '17 at 07:53
  • Yes, it always used to be like that. However, since iOS 11 I think they have accidentally changed that behavior. Now, if you swipe kill the app when the app is already terminated by the system then it will still be woken up again by the system. If you swipe kill when the app is in background or suspended, then it will not be woken up by bluetooth. So my guess is that they accidentally introduced a bug that made thing better for developers. It is also possible that the Tile app uses other means to wake up apps, such as VOIP or similar, but I doubt it. – Anton Dec 20 '17 at 09:23
  • @Anton about point 5, my app does not call willRestoreState or centralDidUpdateState when I turn the bluetooth off in connected state. https://stackoverflow.com/questions/48030480/turning-bluetooth-radio-back-on-after-the-app-is-suspended-doesnt-call-centralm – Saleh Jan 08 '18 at 16:56
  • @Anton https://stackoverflow.com/questions/53321747/how-to-retrieve-connected-ble-device-while-app-is-in-background please have a look at this. – va05 Nov 16 '18 at 13:29
  • 1
    Thankyou @Anton This solidifies my decision to go with Wifi. If anyone is reading this in 2020, I've found this to still be the case on iOS 13. I've used this absolutely excellent tutorial for the iOS code (https://www.splinter.com.au/2019/06/06/bluetooth-sample-code/) The result is that it works perfectly when it works. But that sometimes it just fails to find the device, and you have to turn bluetooth off and on again for it to find it again. Users hate that. – legoblocks Sep 17 '20 at 04:08