iOS: didDiscoverPeripheral not called in Background mode - ios

I am Working on BLE project, everything works fine when the app in the foreground.It can discover and connect to the peripheral, all the call back method work perfectly.
But the problem is that, when the app in the background mode (I press home button). Only the centralManagerDidUpdateState delegate method get called.
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBCentralManagerStatePoweredOn:
[self.cbCentralManager scanForPeripheralsWithServices:nil options:#{ CBCentralManagerScanOptionAllowDuplicatesKey : #YES }];
break;
default:
break;
}
}
I use scanForPeripheralsWithServices:nil option, But when the app in the background, the didDiscoverPeripheral call back never called. I have edit my plist file with "bluetooth-central" option to support ble central role in background.
Any idea why didDiscoverPeripheral method not call when app in the background?

Paulw11 said are right, If your app find the peripherals in the foreground. It will not call the didDiscoverPeripheral for the same peripherals when it enters the background.
For more information about the iOS BLE Behavior in the background mode. You can check this answer
What exactly can CoreBluetooth applications do whilst in the background?

I was working on Estimote Nearable type beacons. After iOS10 SDK update, I encountered exception from CBCentralManager stating :
<CBCentralManager: 0x17009e050> has provided a restore identifier but the delegate doesn't implement the centralManager:willRestoreState: method
To fix this, Turn-On "Background Mode", in Xcode -> Capabilities -> Background Mode

Scan for nil( scanForPeripheralsWithServices:nil) services will not work in background. You must search for some specific service in background.
You have to set the UUID in scanForPeripheralsWithServices: method which Peripherals/BLE device is advertising.

From Official Apple reference
You can provide an array of CBUUID objects—representing service
UUIDs—in the serviceUUIDs parameter. When you do, the central manager
returns only peripherals that advertise the services you specify
(recommended). If the serviceUUIDs parameter is nil, all discovered
peripherals are returned regardless of their supported services (not
recommended). If the central manager is already scanning with
different parameters, the provided parameters replace them. When the
central manager object discovers a peripheral, it calls the
centralManager:didDiscoverPeripheral:advertisementData:RSSI: method of
its delegate object.
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 CBCentralManagerOptionShowPowerAlertKey
scan option is ignored while scanning in the background.
Here
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.
So scanForPeripheralsWithServices:nil with nil it will not work in background , you need to specify list of UUIDS

Related

BLE device scanning in background using CoreBluetooth swift

I have an app demanding to scan BLE devices around in background mode when app is not active in foreground.
I have implemented such functionality using CoreBluetooth framework. This is code, I am using to scan device. First I have all device in DB, fetch and creating array.
for item in self.allItems ?? [] {
let uuid = UUID(uuidString: item.identifier)!
let id = CBUUID(nsuuid: uuid)
self.allServiceIds.append(id)
}
And when start scanning, passing same array in method.
self.centralManager?.scanForPeripherals(withServices: self.allServiceIds, options: [CBCentralManagerScanOptionAllowDuplicatesKey:true])
Also I have tried to pass service ids in array as I read lots of articles suggesting in background mode it is required.
Also inside Capabilities, I have checked required options. But still it is not scanning in when app is in background.
Any ideas are welcome.
A few tips:
Get your app working in the foreground first. Only once this is working should you try to get it working in the background.
Make sure that bluetooth is turned on in phone settings.
Make sure you have obtained Bluetooth permission from the user. An iPhone will send a dialog to prompt you for this permission the first time an app runs (it is triggered by CBCentralManager usage.) If you deny this permission, you won't see the dialog again and you must go to Settings -> Your App -> Permissions -> Bluetooth to enable it manually.
Take care that you have set the CBCentralManagerDelegate properly and that you are getting callbacks. In particular, log the callback to centralManagerDidUpdateState(_ central: CBCentralManager) and make sure that you see central.state transition to .poweredOn. Only once you reach the poweredOn state should you start scanning. See: https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate/1518888-centralmanagerdidupdatestate
Start off testing in the foreground without filtering self.centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey:true]) (note the withServices: nil). Only once you get callbacks detecting peripherals should you start filtering for specific service UUIDs. This will help you eliminate all other problems before filtering on the service.
If you can get all of the above working, there are tricks you can do to get duplicate detections even in the background.
If you cannot get the above working, please described the results of the above, and show more of your code including where you instantiate the CBCentralManager, where you set the delegate, showing the delegate callback methods, and describing which delegate callbacks get called and when.

How to restore bluetooth connection on iOS

I have xamarin project. I would like to pair bluetooth button with an app and keep connection alive in suspended state. I successfully subscribed to characteristic event which represent the click in foreground state. The main use case is to handle the event in suspended state and send data to a server.
I read the documentation here, but I am having the difficulties implement restoring the CBCentralManager especially translating these methods into Xamairn.iOS.
Opt In to State Preservation and Restoration
myCentralManager =
[[CBCentralManager alloc] initWithDelegate:self queue:nil
options:#{ CBCentralManagerOptionRestoreIdentifierKey:
#"myCentralManagerIdentifier" }];
Reinstantiate Your Central and Peripheral Managers
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSArray *centralManagerIdentifiers =
launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
...
Implement the Appropriate Restoration Delegate Method
- (void)centralManager:(CBCentralManager *)central
willRestoreState:(NSDictionary *)state {
NSArray *peripherals =
state[CBCentralManagerRestoredStatePeripheralsKey];
...
Questions:
Does app handle the events in suspended state? (technically in backgrounded)
Do I have to reconnect the device after app gets restored?
I think you can find the answer in the document.
1.Does app handle the events in suspended state? (technically in
backgrounded)
You need to enable a Core Bluetooth background execution mode in the info.plist to ensure your app keep running in background.
Also, an iOS app linked on or after iOS 10.0 must include in its Info.plist file the usage description keys for the types of data it needs to access or it will crash. InfoPlistKeyReference
You can read: Core Bluetooth Background Execution Modes
2.Do I have to reconnect the device after app gets restored?
If you gets restored here means you restart your app, I think you need to reconnect since it will lose the connection after the app is terminated.
If you gets restored here means enter foreground from background, I don't think you need to reconnect if you enabled background mode as your app is still running in the background.

Issue with CoreBluetooth: Cant discover the device

I'm working on BLE device named WIRELESS BLOOD PRESSURE WRIST MONITOR.
I've downloaded these application and every thing is working great.
But when I tried to connect to the device from my application, I didn't receive a response.
and my code is straight like the code from developer.apple.com and also this tutorial.
This is my code:
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[_centralManager scanForPeripheralsWithServices:nil options:nil];
I receive notification on the delegate for centralManagerDidUpdateState but I don't receive the didDiscoverPeripheral even if I'm searching with nil in services.
When I go to Setting -> Bluetooth: I can see the device and it is connected and the signal of bluetooth is on. So the iPhone can see the BLE device, So when I used in my code these method
retrieveConnectedPeripheralsWithServices to get the list of connected device it returns 0 object.
So I don't know what is the problem, keeping in mind that the BLE device is working great with there own app so it's Low Energy not classic , and the BLE device display bluetooth signal when opening the app.
So any ideas from the GEEKS :D
Thanks..
There are lots of pieces you need to take care of:
You need to wait for the centralManagerDidUpdateState to indicate CBCentralManagerStatePoweredOn. Anything you do before will either result in error or be ignored. So your call to scanForPeripheralsWithServices is probably ignored. This is true for other APIs, like the retrieveConnectedPeripheralsWithServices you mentioned.
It is also possible that the device turns off advertising after it is connected, so your scanning will not succeed until you disconnect from it.
Scanning in the background has many limitations. You can search the SO questions to find out the details. In the beginning I would advise you to not to try backgrounded operation as it can be really tricky.
Instead of searching immediately after initializing the central manager, try first to wait for update that the power is on.
Try these steps:
In viewDidLoad remove the call to scanForPeripheralsWithServices
Add method scanForPeripherals, that will first check the Central Manager is powered on and also check for the scanning state (see below).
code:
- (void)scanForPeripherals {
if (self.centralManager.state != CBCentralManagerStatePoweredOn) {
NSLog(#"CBCentralManager must be powered to scan peripherals. %d", self.centralManager.state);
return;
}
if (self.scanning) {
return;
}
self.scanning = YES;
[self.centralManager scanForPeripheralsWithServices:nil options:#{ CBCentralManagerScanOptionAllowDuplicatesKey: #YES }];
NSLog(#"Scanning started");
}
In ViewWillAppear call [self scanForPeripherals]
In centralManagerDidUpdateState, call scanForPeripherals only if central manager is powered on.
code:
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
if (central.state != CBCentralManagerStatePoweredOn) {
NSLog(#"CBCentralManager not powered on yet");
return;
}
// The state must be CBCentralManagerStatePoweredOn
[self scanForPeripherals];
}
Add BOOL property scanning. Using the scanning propery allows you to safely try and scan before the update callback called. You should handle the scanning state to prevent calling scan twice.

Apple Notification Center Service through OS X

I know you can use this service to have devices like smart watches intercept notifications from iOS devices. But can you receive these iOS notifications on a Mac through OS X?
I want to be able to have my OS X program detect a specific notification type that is received in iOS. I tried browsing for the ANCS device on my Mac, but it didn't show up. I know you can't do this between iOS devices, so I was wondering if maybe the same was true between iOS and OS X or not?
Thanks!
It's definitely possible!
Here's what you need:
An app on your iOS device which imports CoreBluetooth and uses CBPeripheralManager to advertise a dummy service with some custom UUID (not the ANCS UUID, it won't work). This dummy service is required for your Mac to "see" the ANCS service.*
An app on your Mac which imports IOBluetooth, initiates a CBCentralManager object, and starts a scan. You can do this as so:
[self.centralManager scanForPeripheralsWithServices:#[[CBUUID UUIDWithString:YOUR_CUSTOM_SERVICE_UUID]] options:#{CBCentralManagerScanOptionSolicitedServiceUUIDsKey:#[[CBUUID UUIDWithString:ANCS_SERVICE_UUID]]];
Make sure you set yourself up as a delegate to CBCentralManager to receive the delegate callbacks.
When your Mac discovers your device in didDiscoverPeripheral, connect to it:
[self.centralManager connectPeripheral:peripheral options:nil];
1 very important thing to note here is you need to retain your peripheral to a property if you wish to connect to it, otherwise it will be dealloc'ed. See this answer for a more detailed discussion.
In didConnectPeripheral, you need to set yourself up as a delegate to the CBPeripheral you're connected to then start discovering services:
[peripheral discoverServices:nil];
(All the callbacks from this point onward are for CBPeripheral)
In didDiscoverServices, you should get a list of available services. Loop through them as so and discover each service's characteristics:
for (CBService *service in peripheral.services) {
if ([service.UUID isEqual:[CBUUID UUIDWithString:YOUR_CUSTOM_SERVICE_UUID]]) {
NSLog(#"Found your Custom Service");
}
if ([service.UUID isEqual:[CBUUID UUIDWithString:ANCS_UUID]]) {
NSLog(#"Found ANCS Service");
}
[peripheral discoverCharacteristics:nil forService:service];
}
In didDiscoverCharacteristicsForService, you want to look for 3 characteristics:
ANCS Notification Source: UUID 9FBF120D-6301-42D9-8C58-25E699A21DBD (notifiable)
ANCS Control Point: UUID 69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9 (writeable with response)
ANCS Data Source: UUID 22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB (notifiable)
For those notifiable characteristics, subscribe to them for updates:
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:ANCS_CHARACTERISTIC_UUID]]) {
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
If you want to check if those characteristics are have started notifying, do a if(characteristic.isNotifying) in didUpdateNotificationStateForCharacteristic.
You will get the actual NSData updates in didUpdateValueForCharacteristic with characteristic.value. The important thing to note here is that you will get informed of notification events by the Notification Source characteristic, but these will not contain that much information. If you want your Mac to play a sound or flash some Hue lights or something like that for every iOS notification, this will suffice. However, for the actual notification details, it will need to come from the Data Source characteristic, but you need to request for them by making very specific calls to the Control Point characteristic. This is where it gets really complicated, and you'll be able to get more information in the official ANCS Specification document.
If you want a shortcut or a look at how others have done it, check out these Github repos:
jamiepinkham/BTLE_ANCS
KhaosT/ANCS-Mac
indragiek/INDANCSClient
Just be careful as you may find bugs in some of these implementations, mainly in the processing of data sent by the ANCS Data Source (I had to get creative with my own error handling).
*** Some may argue that you can use "Service Solicitation" to expose ANCS without having an app running on the iOS device and/or without advertising a dummy Service (see options parameter in Step 2), but I haven't had that much success with it so perhaps there's something I'm missing.

Peripheral name doesn't comply to NameKey

I have an app that simulates heart rate monitor peripheral (The peripheral app).
I also have an app that receives the data and present it (The central app).
The central app decided to connect to the discovered peripheral based on its name.
The problem is that both app work perfectly good, except that the name is always "iPhone".
The advertising is done this way:
- (IBAction)switchChanged:(id)sender
{
if (self.advertisingSwitch.on) {
NSDictionary *advData =
#{CBAdvertisementDataLocalNameKey:#"Custom Name",
CBAdvertisementDataServiceUUIDsKey:#[[CBUUID UUIDWithString:#"180D"]]};
[self.peripheralManager startAdvertising:advData];
NSLog(#"Advertising");
}
else {
[self.peripheralManager stopAdvertising];
[[self timerInterval] invalidate];
NSLog(#"Stopped advertising");
}
}
But on the central side, inside
- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)aPeripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
The name property never changed.
Is there anything that should be done?
What I have observed with CBPeripheral.name is that the device will, in fact, set the name to the name you select with CBAdvertisementDataLocalNameKey. This name is not persistent, though. If you disconnect the master and reconnect,the name will generally have been switched to "iPhone". If the peripheral disconnects due to an error, I have seen it reconnect with the correct peripheral name, but a new UUID.
There may be other situations where the name also switches to iPhone.
This appears to be a bug in iOS. I'm looking for confirmation before reporting it.
CBAdvertisementDataLocalNameKey only change kCBAdvDataLocalName in advertisementData.
When you nslog advertisementData, you will see some data like this:
{
kCBAdvDataIsConnectable = 1;
kCBAdvDataLocalName = Custom Name;
kCBAdvDataServiceUUIDs = (
"FB694B90-F49E-4597-8306-171BBA78F846"
);
}
Unfortunately, there is no other way to set the peripheral name. iPhone will always have the name: iPhone.
The advertisement is probably correctly seen on the central side. You may check by NSLogging the advertisementData. However, if you rely on the peripheral.name property, then that will either be empty (if you connect first) or contain the "iPhone" string.
I remember it is used to happen to me and I figured it was something to do with the way Core Bluetooth handles caching and service discovery. What Happened to me was that at first I received a default name like iPhone, iPad or nothing at all. But then after discovering services or trying to establish a connection the key magically changes to the value I had set on the other end.
Moreover, it seems it only happens the first time, afterwards, even between launches and subsequent runs of the app Core Bluetooth will try its best to return those values to you on the advertisement stage even on first discovery, but those might as well be outdated values.
my current implementation looks as follows:
NSString * baconName = [[UIDevice currentDevice] name];
NSDictionary *advertisementData = #{CBAdvertisementDataServiceUUIDsKey:#[[CBUUIDUUIDWithString:BACON_SERVICE_UUID]],
CBAdvertisementDataLocalNameKey:baconName};
And It just works for me, iPhones love Bacon, everybody does ;).
Hence, the best way to ensure you get any data you want, is to create another characteristic to transmit your flag and do constant discoveries of services and characteristics for Peripherals you are discovering, and accordingly minimise the discovery of existing or cached peripherals by caching or keeping a reference to them, CB is supposed to do this for ya, and they do their best effort but only you know the business logic of your app and what is important to you. I am overly paranoid and keep references to the discovered peripherals I am interested all the time. That is just me: it ensures I have the right information, and that I minimise scanning and constant re discovery of services and characteristics.
I Hope this helps.
In most such applications instead of identifying the peripheral by name, the client app should be identifying it by a service ID, and the server (peripheral), should be providing a either a standard service ID, as defined at bluetooth.org, or a proprietary service ID/name.
I have the same problem. I argee with Mike, this really seems like bug in IOS. If you discover your peripheral with TI multitool (for example) first, then your device will be discovered as you setuped in CBAdvertisementDataLocalNameKey.
to Dan1one:
you should use [[UIDevice currentDevice] model], not name, to get the string same to default.

Resources