BLE device scanning in background using CoreBluetooth swift - ios

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.

Related

Scan Bluetooth device failed when I lock screen(use ibeacon)

I made a ibeacon project, now I found two problems:
first,when mobile phone lock screen, I'm going to scan for Bluetooth devices (I'm sure in the beacon region), sometimes Scan failed and returned empty array
second, when I lock screen, sometimes didEnterRegion and didExitRegion have stopped, when I am the light of the screen, they went on again
Now I want to scan the device every time when I lock the screen,what should i do?
MonitoringForRegions code:
let region = BRTBeaconRegion.init(proximityUUID: proxiID, identifier: proxiID.UUIDString)
region.notifyOnEntry = true
region.notifyOnExit = true
region.notifyEntryStateOnDisplay = true
BRTBeaconSDK.startMonitoringForRegions(region)
Appdelegate delegate code:
func beaconManager(manager:BRTBeaconManager,didEnterRegion region:BRTBeaconRegion){
if region.notifyOnEntry {
//PublicMethod().sendLocalNotification(BEACON_TIP_IN)
print("\(NSDate())-------enter--------")
}
}
func beaconManager(manager:BRTBeaconManager,didExitRegion region:BRTBeaconRegion){
if region.notifyOnExit {
//PublicMethod().sendLocalNotification(BEACON_TIP_OUT)
print("\(NSDate())-------exit--------")
}
}
func beaconManager(manager:BRTBeaconManager,didDetermineState state:CLRegionState,forRegion region:BRTBeaconRegion){
print("didDetermineState")
}
scan code:
BRTBeaconSDK .startRangingBeaconsInRegions(regionArray) { (beacons, region, error ) in
for beacon in beacons as! [BRTBeacon]{
print("beacons count:\(beacons.count) name :\(beacon.name) macaddress:\(beacon.macAddress) major:\(beacon.major) minor:\(beacon.minor) ")
}
}
updated at 14:25
I found the first problem more accurately described, when the lock screen, the device is close to the phone, can be scanned, but not far away (this distance can be scanned at the front desk)
So I guess if the scanning distance is not accurate when lock screen?
Did you turn on the Background Modes for Bluetooth in the Xcode project's capabilities page?
https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html
You need to first understand the mechanism of CoreBluetooth. Especially you need to understand the different between Ranging and Monitoring, and the limitations of them in background mode. Some operations can only be achieved in the foreground mode.
Detecting beacons via iBeacon Monitoring & Ranging vs CoreBluetooth scanForPeripheralsWithServices
didEnterRegion and didExitRegion is not realtime, there is buffer time in between exit and reenter the region. Also, if you are already in the region before you call the monitor function, the didEnterRegion won't be trigged.
Update
1、Not open ,Before trying to open, or have the same problem
You need to turn it on, and do some settings in order to implement background scanning.
2、Thank you very much, but I found the scan failed, not every time, occasionally.That's what I don't understand.
3、yes,My experiments are the first to leave the area, then enter the area, because I have two beacon equipment together, UUID is different, close one, then I went to scan, and then found sometimes does not scan, sometimes it is possible
I cant find the code of the Bluetooth library that you are using. It seems like it is written by Mainland China developer? Can you post the link of that library?
My exp, the CoreBluetooth ranging functions are quite reliable. So I guess the problem is you didnt turn on the background mode. Turn on Acts as a Bluetooth LE accessory and Uses Bluetooth LE accessories in the Capabilities tab.
Also, I suggest you to read / bookmark Radius Networks's blog. Their developer blog is worth reading.
http://developer.radiusnetworks.com/2014/11/13/extending-background-ranging-on-ios.html

Is it possible to detect the user's moving activity on the background?

I want to develop an app that detecting the user's moving way (walking, cycling, driving etc...) and send a specific UILocalNotification for each activity type.
My question is: is it possible to detect it on the background (when the app is completely closed) without draining the device's battery? What will be the best way to do it?
Thank you!
There is coprocessor m7(+) in iPhones upper 5s.
It gives you possibility to get device motion.
Just
import CoreMotion
in your file.
Create a CMMotionActivityManager object:
let motionActivityManager = CMMotionActivityManager()
Check if it`s available on your device:
motionActivityManager.isActivityAvailable()
Use this method:
motionActivityManager.startActivityUpdates(to: OperationQueue.main) { (activity) in
if (activity?.automotive)! {
print("User using car")
}
if (activity?.cycling)! {
print("User is cycling")
}
if (activity?.running)! {
print("User is running")
}
if (activity?.walking)! {
print("User is walking")
}
if (activity?.stationary)! {
print("User is standing")
}
if (activity?.unknown)! {
print("Unknown activity")
}
}
It would return you types of user activity.
Regarding the user activity which can be handled in background tasks are the below once which does not mention about (walking, cycling,driving etc...)
Implementing Long-Running Background Tasks
For tasks that require more execution time to implement, you must request specific permissions to run them in the background without their being suspended. In iOS, only specific app types are allowed to run in the background:
Apps that play audible content to the user while in the background,
such as a music player app
Apps that record audio content while in the background.
Apps that keep users informed of their location at all times, such as
a navigation app Apps that support Voice over Internet Protocol
(VoIP)
Apps that need to download and process new content regularly
Apps that receive regular updates from external accessories
Yes it´s possible to do that!
If your iOS app must keep monitoring location even while it’s in the
background, use the standard location service and specify the location
value of the UIBackgroundModes key to continue running in the
background and receiving location updates. (In this situation, you
should also make sure the location manager’s
pausesLocationUpdatesAutomatically property is set to YES to help
conserve power.) Examples of apps that might need this type of
location updating are fitness or turn-by-turn navigation apps.
Read more here.

iOS Bluetooth LE cant get notification programatically but can in other apps

I'm trying to get my iOS program to communicate with a custom Bluetooth LE device. I can connect to it, read all the services, and read all the CBCharacteristic objects for each service.
I am trying to get notified when one specific CBCharacteristic is updated. The CBCharacteristic.properties is set to 0x10 (Notify) but the CBCharacteristic.isNotifying is false.
After calling the following line of code:
myDevice.peripheral.setNotifyValue(true, forCharacteristic: myChar)
I am expecting to receive notifications via the CBPeripheralDelegate function:
func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {}
but it never gets called.
In addition, using the LightBlue Explorer app from PunchThrough.com I CAN get notifications, so I know it is possible. What is the LightBlue app doing that I am not?
Has anyone seen something similar to this?
For reference, the device uses WLT2564M Bluetooth module. Also I can read the value using
myDevice.peripheral.readValueForCharacteristic(myChar)
without any issues.
A few things to check:
the peripheral property needs to persist for the duration of its use
you must have connected to it (and weren't disconnected in the meantime)
check that the delegate is properly set
check logs
make sure you implement peripheral:didUpdateNotificationStateForCharacteristic:error: and log any errors there

iOS: didDiscoverPeripheral not called in Background mode

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

Background Scanning for BLE in Swift

I am attempting to have my app scan for BLE devices in the background and to search for a bit of advertisement data in Swift. I've been unable to find any tutorials or questions on here that cover this.
Basically, is there a way of doing this automatically in the background when the app isn't in the foreground and when the user has restarted their phone?:
Obtaining Bluetooth LE scan response data with iOS
I hope you can point me in the right direction. Thank you
Step 1: Enable bluetooth background mode for your projects capabilities
Step 2: Make sure the appropriate stuff was added to your info.plist file
Here is the plist code if it didn't add it:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>bluetooth-central</string>
</array>
Then, when you call "scanForPeripheralsWithServices" on your CBCentralmanager you have to specify an array of services to scan for. You can't pass it an empty array. It will still scan if you pass nil, just not in the background.
So specify an array of service UUID's like this:
let arrayOfServices: [CBUUID] = [CBUUID(string: "8888")]
self.myBluetoothManager?.scanForPeripheralsWithServices(arrayOfServices, options: nil)
Now if you care about options you can pass a dictionary of options in place of the nil I passed above. Mostly, this is used to specify if you want to continuously see a devices RSSI before you connect or if you just want the advertisement packet once. Put a:
println(advertisementData["kCBAdvDataLocalName"] as! String)
println(advertisementData["kCBAdvDataManufacturerData"] as! NSData)
in the "didDiscoverPeripheral" delegate method to observe the different behavior.
Here is the Dictionary you will pass if you want duplicate keys:
let dictionaryOfOptions = [CBCentralManagerScanOptionAllowDuplicatesKey : true]
self.myBluetoothManager?.scanForPeripheralsWithServices(arrayOfServices, options: dictionaryOfOptions)
Now Apple says that when your app goes in the background mode it defaults this scan option to false but as of iOS 8.3 I have see it keep scanning with duplicate keys.
One final note on background execution. If the iOS decides to suspend your app the scanning stops. I have seen this happen as soon as 30 seconds if there are a bunch of apps running.

Resources