BLE background reconnect - ios

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.

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

Related

IOS BLE connection gets disconnected continuously

IOS application is communicating with BLE peripheral using Core-Bluetooth framework. The app has registered for glucose characteristic and is receiving data from the peripheral after every one minute.
It is being observed that, when the application is in idle state there is no disconnection from the BLE peripheral whereas if I navigate between the ViewControllers present in the application then there happens to be continuous disconnection with the BLE peripheral.
The connection parameters set are within the given range as mentioned in Apple's Core-Bluetooth programming guide. Any ideas why the connection keeps on getting disconnecting?
I had also used the BTLE Transfer Source Code present in apple developer site. And had replaced the Transfer service UUID with Glucose service UUID. I had tested this on iPhone 6 with iOS version 8.3. I am still facing the same issue of disconnection.
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
if (self.discoveredPeripheral != peripheral) {
self.discoveredPeripheral = peripheral;
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
[self.centralManager stopScan];
[self.data setLength:0];
peripheral.delegate = self;
[peripheral discoverServices:#[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]]];
}
check you don't explicitly use weak properties to manage your peripherals (by default types are strong), check this issue.
However without any piece of code all I can suggest is to grab a BLE packet sniffer and see who, when and therefore why it sends the disconnection command. In this process beware about the channel hopping, you have 1/3 of chances to capture the channel with the sniffer so just retry until you see the data flow on the screen. If not now, it's always welcome to have a device like that if you will regularly work with Bluetooth Low Energy.
// method called whenever you have successfully connected to the BLE peripheral
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
self.myPeripheral = peripheral;
self.myPeripheral.delegate = self;
NSString *connected = [NSString stringWithFormat:#"Connected: %#", peripheral.state == CBPeripheralStateConnected ? #"YES" : #"NO"];
NSLog(#"%#", connected);
}
Put this method,
Note: It is a good practice to assign your discovered peripheral to a retained CBPeripheral into centralManager: didConnectPeripheral
//this one line code stops disconnecting device automatically.
[[Manager sharedManager].connected_PeripheralDevice discoverServices:nil];

multiple BLE devices which have same services and characteristics in ios

I have multiple BLE devices which have same services and characteristics. I can scan and connect multiple devices. After connections, when I try to distinguish each one by sending command it doesn't work. It works perfectly with a single device. Is it something like socket connection ? Like A server spawns child threads and each client can maintain a connection through threads.
Please provide some tips on how to scan each device when other device is reading the data from the device.
-(void) scanDevice {
centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[centralManager scanForPeripheralsWithServices:nil options:0];
[AppDelegate app].cbCentral = centralManager;
}
-(void) stopScan {
[[AppDelegate app].cbCentral stopScan];
}
-(void)connectToDevice:(CBPeripheral *)peripheral{
[[AppDelegate app] cbCentral].delegate = self;
[[[AppDelegate app] cbCentral] connectPeripheral:peripheral options:nil];
}
-(void)calldiscoverServicesForPeripheral:(CBPeripheral *)peripheral{
[peripheral setDelegate:self];
[peripheral discoverServices:nil];
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(#"Connected PERIPHERAL %d",peripheral.state);
[delegate getConnectedPeripheral:peripheral];
NSLog(#"Connected peripheral %#",peripheral);
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
NSLog(#"Discovered servicea: %#", peripheral.services);
for (CBService *service in peripheral.services) {
NSLog(#"Discovered service: %#", [service.UUID data]);
[peripheral discoverCharacteristics:nil forService:service];
}
}
I will explain in detail,
I have table view, that contains BLE devices. For the first time it is empty, so i will search for the devices by calling a class "Scan Devices".
This "Scan devices" class contains all the Corebluetooth methods like CBCentralManager allocation, CBperipheral delegate methods.
After search, I will display the device in table view and connect to the BLE device. I am getting some data from "Scan Device" class.
Now, I want to search more devices to connect and get the data. For this, I will call [[CBCentralManager alloc] initWithDelegate:self] in "ScanDevices" class. At this time, for previous device(connected and reading) is showing the warning "is not a valid peripheral" and new device is connecting and reading data from the device.
But I want read the data from both devices at a time
Please help me...
Thanks
You shouldn't keep creating a new CBCentral - doing so will cause your previous CBCentral to be deallocated and therefore invalidate the existing peripherals.
You should activate your scanning once, say in viewWillAppear and deactivate it in viewWillDisappear.
Once you have initiated scanning and set your delegate in your ScanDevices class, it will call [delegate getConnectedPeripheral:] each time a new peripheral is found and connected.

iOS does not detect new peripheral name

We are using CC2540 BLE Chip as peripheral and an iOS 7.0.4 iPhone is playing central role.
We change the peripheral name, but this change is only shown when we disconnect from iOS device and reconnect.
It works fine in Android using this code to change peripheral name in response data:
GAPRole_SetParameter( GAPROLE_SCAN_RSP_DATA, sizeof ( deviceName ), deviceName );
We are using this code to change the peripheral name of GAP layer in iOS:
GGS_SetParameter( GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName );
Is there any way to change peripheral name so that no reconnection is needed?
For me the problem is in the iOS App code.
The basic order of calls to connect in iOS to BLE is:
centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil
options: nil];
this last one invokes:
- (void) centralManagerDidUpdateState:(CBCentralManager *)central
then if central.state is CBCentralManagerStatePoweredOn you can:
[centralManager scanForPeripheralsWithServices:nil
options:nil];
this last one invokes:
- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
Apple here recommends: "You must retain a local copy of the peripheral if any command is to be performed on it."
That means you should create a property for your CBPeripheral like:
self.myPeripheral = peripheral;
It is here where you have a peripheral.name or self.myPeripheral.name
If you don't call again in your code: [centralManager scanForPeripheralsWithServices:nil options:nil]; what you have is the information retrieved the first time. You should make the call again if you want to refresh the name. Maybe with a timer.
Hope that helps.
EDIT
Check: Core Bluetooth Programming Guide: Performing Common Central Role Tasks
you can get the local name from advertisementData. There is the solution: Incorrect BLE Peripheral Name with iOS
And on Apple Developer Forums, here is the detail of CBPeripheral name property.

Run iOS 6 device as a BLE peripheral

As we know, iOS 6 support running devices (iPhone 4s and above, and new iPad) as a BLE peripheral. There is a demo in WWDC 2012 Session 705 called "advanced core bluetooth". I asked for the source code from Apple. They sent me a modified version of source code (BTLE_Transfer_Draft). Then I:
Run the app in iPhone 5 (iOS 6) in "Peripheral Mode" and start "Advertising"
Run the app in new iPad (iOS 5.1.1) in "Central Mode"
The problem is that the peripheral is never been discovered at all. So I use other testing applications including some downloaded from App Store. All failed to discover peripherals. I think the problem should be in BTLE_Transfer_Draft. Because I'm not sure whether I'm allowed to present the whole source code. So I just show the "peripheral mode" part here:
- (void)viewDidLoad {
[super viewDidLoad];
// Start up the CBPeripheralManager
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
}
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
// Opt out from any other state
if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
return;
}
// We're in CBPeripheralManagerStatePoweredOn state...
NSLog(#"self.peripheralManager powered on.");
// ... so build our service.
// Start with the CBMutableCharacteristic
self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]
properties:CBCharacteristicPropertyNotify
value:nil
permissions:CBAttributePermissionsReadable];
// Then the service
CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]
primary:YES];
// Add the characteristic to the service
transferService.characteristics = #[self.transferCharacteristic];
// And add it to the peripheral manager
[self.peripheralManager addService:transferService];
}
/** Start advertising
*/
- (IBAction)switchChanged:(id)sender
{
if (self.advertisingSwitch.on) {
// All we advertise is our service's UUID
[self.peripheralManager startAdvertising:#{ CBAdvertisementDataServiceUUIDsKey : #[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }];
}
else {
[self.peripheralManager stopAdvertising];
}
}
The BLE is in powered on status and the startAdvertising is called. But the BLE central can never discover it.
Post updated:
According to mttrb's suggestion I added "CBAdvertisementDataLocalNameKey" when I startAdvertising. But my service is still can't be discovered by most of the apps including some apps from app store. The only one app can discover my service is an app from app store called "BLE scanner".
My question is: does this mean my application is working as a peripheral? But why my own code can't discover the service? How am I supposed to debug it ?
My code in Central Mode is like this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Start up the CBCentralManager
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
if (central.state != CBCentralManagerStatePoweredOn) {
return;
}
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
......
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (error) {
NSLog(#"Error discovering services: %#", [error localizedDescription]);
return;
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
// Deal with errors (if any)
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
return;
}
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(#"Peripheral Disconnected");
self.discoveredPeripheral = nil;
}
The didDiscoverPeripheral and didDiscoverServices are never called. What could be wrong? Any idea? Thanks
There is also a high quality free app called LightBlue that you can use to test your code with. It should be able to pick up all devices advertising in peripheral mode and it can even turn itself into an advertising peripheral if you want to make sure your device is working properly.
I would try moving the startAdvertising: method call up and into the end of your peripheralManagerDidUpdateState: delegate method and see if that helps.
I would also add a CBAdvertisementDataLocalNameKey key-value pair to your startAdvertising: method call. I found things were unreliable when the advertisement didn't have a name.
Finally, I would invest in the BLExplr app available in the App Store to help with scanning for your peripheral. It removes the assumption that your central is working correctly.
This Git hub project also throws some light on the CBPeripheralManager API. Called PeripheralModeTest.
This line is particularly useful for setting the advertising data
NSDictionary *advertisingData = #{CBAdvertisementDataLocalNameKey : #"Device Name", CBAdvertisementDataServiceUUIDsKey : #[[CBUUID UUIDWithString:CBUUIDGenericAccessProfileString]]};
Though I can't see any official documentation in the Apple iOS Developer Library yet. More specifically anything about setting the repeat period for the advertising.
The BTLE Transfer example has this piece of (odd) code, which may cause some troubles:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
// Reject any where the value is above reasonable range
if (RSSI.integerValue > -15) {
return;
}
// Reject if the signal strength is too low to be close enough (Close is around -22dB)
if (RSSI.integerValue < -35) {
return;
}
Just remove those two if-statements as it makes no sense!
I have made a simplified version available here that can be used to test high volumes of messages being sent from peripheral to central.
Please note that the CBPeripheralManager class was first introduced in iOS 6.0.
I do not know witch version of BTLE Central Peripheral Transfer you did actually test but current version has iOS 6 as requirement.
So I would suggest to test linkage against iOS 5.1 to see what compatibility issues it shows.

Cannot find peripheral when scanning for specific service CBUUID

When I use:
CBUUID * uuid = [CBUUID UUIDWithString:#"1800"]; // GAP
DEBUG_LOG(#"CBUUID: %#",uuid); // CBUUID: Generic Access Profile
_centralMan = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[_centralMan scanForPeripheralsWithServices:[NSArray arrayWithObject:uuid]
options:nil];
I cannot find my peripheral, but when I use:
_centralMan = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[_centralMan scanForPeripheralsWithServices:nil
options:nil];
…it shows up immediately.
When I connect, I am able to discover/read/write to all the services I am expecting. My understanding is that GAP makes this possible. I have also tried CBUUID's for other services that I know are running (specifically "1804", TX power service), to no avail; the scan never discovers anything.
Even though the services are running (I can communicate w/ them upon connecting), when I scan passing the service UUID array as nil and this delegate method is called...
-(void) centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
DEBUG_LOG(#"Found peripheral w/ UUID: %# RSSI: %# \n AdvData: %#",peripheral.UUID,RSSI,advertisementData);
DEBUG_LOG(#"Services found: %d",peripheral.services.count);
for (CBService * service in peripheral.services) {
DEBUG_LOG(#"Found service: %# w/ UUID %#",service, service.UUID);
}
}
…it always reports "Services found: 0". Should I expect the services array to be populated w/ services found during the scan? (I assume this is at least partially possible, based on scanForPeripheralsWithServices:[NSArray arrayWithObject:uuid]
options:nil.)
Could this be a problem w/ my peripheral's service advertisements? Am I using the API incorrectly? Any guidance is much appreciated! :-)
PS: I'm pretty green w/ Bluetooth.
PPS: I control the source to the peripheral (it's a CC2540DK). So I'd be as likely to believe the problem is there as it is here.
Turns out our peripheral code was not advertising the UUIDs a la this Bluetooth spec.
As soon as we dropped them into the advertising packet, iOS picked them up no sweat.

Resources