How to scan BLE devices in Background? - ios

I want to scan BLE devices on the background as like a foreground. But my iOS app doesn't work as I expect. Below is my AppDelegate class code.
private var centralManager : CBCentralManager!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)return true
}
func applicationWillEnterForeground(_ application: UIApplication) {
print("entering foreground...")
}
func applicationDidEnterBackground(_ application: UIApplication) {
print("entered background...")
print(centralManager.state)
centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
print("Bluetooth is On")
centralManager.scanForPeripherals(withServices: nil, options: nil)
} else {
print(central.state)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("\nName : \(peripheral.name ?? "(No name)")")
print("RSSI : \(RSSI)")
let name = peripheral.name ?? ""
if name.contains("ETGuardian") {
let DetailData = advertisementData["kCBAdvDataManufacturerData"]
let DiscoveredData = String(describing: DetailData)
print(DiscoveredData)
for ad in advertisementData {
print("AD Data: \(ad)")
}
}
}
}
Please help me to scan app in background state as like a foreground.

From Docs: link
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.
For background scanning to work you need to specify serviceUUIDs.

Related

Corebluetooth Scanning BGAppRefreshTask not working

I am working on a project for scanning BLE devices all around in the foreground and there is no issue. It is working well!
But background I cannot make scanning. I did everything on info.plist and on the section "Signing&Capabilities", switch on the Background Model for "Background Fetch".
I read almost everything on the Web about Corebluetooth, BGAppRefreshTask, Background Modes but find nothing.
Only thing I find get advertisement data of the BLE devices around me. UUID and other things are not important for me. I only need to identify BLE devices and know that it is same in other iOS device when it also look for it.
If you have any other solution to guide me to go another way, it will appreciated.
Here is the code of BLECentral. The .swift file which I want to call in Background.
import Foundation
import CoreBluetooth
class BLECentral: NSObject, CBCentralManagerDelegate {
var manager: CBCentralManager!
var discoveredPeripherals = [DiscoveredPeripheral]()
var onDiscovered: (()->Void)?
override init() {
super.init()
manager = CBCentralManager(delegate: self, queue: nil)
}
func scanForPeripherals() {
let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: false]
manager.scanForPeripherals(withServices: nil, options: options)
print("ben tarıyom")
}
// MARK: - CBCentralManagerDelegate
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
scanForPeripherals()
} else {
print("central is unavailable: \(central.state.rawValue)")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if let existingPeripheral = discoveredPeripherals.first(where: {$0.peripheral == peripheral}) {
existingPeripheral.advertisementData = advertisementData
existingPeripheral.rssi = RSSI
print(existingPeripheral.advertisementData)
} else {
discoveredPeripherals.append(DiscoveredPeripheral(peripheral: peripheral, rssi: RSSI, advertisementData: advertisementData))
}
onDiscovered?()
}
}
Here is the AppDelegate code:
import UIKit
import CoreBluetooth
import BackgroundTasks
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
var central: BLECentral!
var manager: CBCentralManager!
var window: UIWindow?
var flowController: AppFlowController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
flowController = AppFlowController(window: window!)
flowController?.start()
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.blebacktrial.refresh", using: nil) { (task) in
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
return true
}
func handleAppRefresh(task: BGAppRefreshTask) {
central.scanForPeripherals()
scheduleAppRefresh()
}
// MARK: - Scheduling Tasks
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.blebacktrial.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 60) // Fetch no earlier than 1 minutes from now
do {
try BGTaskScheduler.shared.submit(request)
print("task scheduled")
} catch {
print("Could not schedule app refresh: \(error)")
}
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
}
}
You don't need to use a background refresh task. Scanning for BLE peripherals is a supported background mode in its own right.
However, you can't scan for service nil in the background. You must scan for the specific service that you are interested in.
From the documentation
Your app can scan for Bluetooth devices in the background by specifying the bluetooth-central background mode. To do this, your app must explicitly scan for one or more services by specifying them in the serviceUUIDs parameter. The CBCentralManager scan option has no effect while scanning in the background.

How To Scan Nearby Bluetooth devices in iOS using CoreBluetooth?

I used this code for CoreBluetooth devices in swift iOS. It scans devices with repetition and displays only one device or unknown device name. I cannot find my phone name or earbud's name during the scan. Can anyone specify what I am doing wrong.
override func viewDidLoad()
{
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("Central state update")
if central.state != .poweredOn {
print("Central is not powered on")
} else {
print("Central scanning for", particleLEDServiceUUID);
centralManager?.scanForPeripherals(withServices: nil)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("Discovered \(peripheral.name ?? "")")
}

iOS Swift - didDiscover peripheral not called in background [duplicate]

This question already has answers here:
iOS: didDiscoverPeripheral not called in Background mode
(4 answers)
Closed 2 years ago.
I want to scan for Bluetooth peripheral in the background and make an API Call with found peripherals. I am doing this.
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
let theViewController = loginVC()
APIManager.sharedInstance.pushtracerData(vc: theViewController, user_id: USERID.sharedInstance.getUserID(), SSID: peripheral.name ?? "", MAC: peripheral.identifier.uuidString, RSSI: "\(RSSI)", TST: "", tracer_or_mobile: "2") {
}
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey:NSNumber(value: false)]
centralManager?.scanForPeripherals(withServices: nil, options: options)
}
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
self.centralManager = CBCentralManager.init(delegate: self, queue: DispatchQueue.main)
completionHandler(.newData)
}
But in the background, this method is not called. Please help me out
centralManager?.scanForPeripherals(withServices: nil, options: options)
From the docs:
Your app can scan for Bluetooth devices in the background by specifying the bluetooth-central background mode. To do this, your app must explicitly scan for one or more services by specifying them in the serviceUUIDs parameter.
It's recommended in all cases that you scan for specific services and not pass nil here, but in the background it's required.

How to get notify when advertisementData changed when app is in background?

I want to get notified when something happened at the BLE device.
If BLE device passes some data/Command to the app, then in-app the advertisementData not changed.
But the same thing we can do with android it's working perfectly.
I want to implement functionality like when advertisementData changed I want to get notify.
Please help me to implement this.
Below is my AppDelegate.swift class.
private var centralManager : CBCentralManager!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)
return true
}
func applicationWillEnterForeground(_ application: UIApplication) {
print("entering foreground...")
}
func applicationDidEnterBackground(_ application: UIApplication) {
print("entered background...")
centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
print("Bluetooth is On")
let kTrackStandardDeviceInfoServiceUUID = CBUUID(string: "180A")
let dictionaryOfOptions = [CBCentralManagerScanOptionAllowDuplicatesKey : true]
let arrayOfServices: [CBUUID] = [kTrackStandardDeviceInfoServiceUUID]
centralManager.scanForPeripherals(withServices: arrayOfServices, options: dictionaryOfOptions)
} else {
print(central.state)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("\nName : \(peripheral.name ?? "(No name)")")
print("RSSI : \(RSSI)")
let name = peripheral.name ?? ""
if name.contains("ETGuardian") {
let DetailData = advertisementData["kCBAdvDataManufacturerData"]
let DiscoveredData = String(describing: DetailData)
print(DiscoveredData)
for ad in advertisementData {
print("AD Data: \(ad)")
}
}
}
For having the application can work in the background, you need to implement some background services in your apps.
Usually, background service is location fetch.
Please note that background service will make your app draining the battery faster
To implement the background service, Click your project, Signing & Capabilities, Background Modes, enable the BLE features.

centralManager didConnectPeripheral cannot be called Swift BLE [duplicate]

I want to use the CBCentralManager in order to find the bluetooth low energy peripherals around me. The centralManager is getting initiated, it starts correctly and it is scanning. But the didDiscover function is never called. I read all the other posts to this and similar problem and am 99% sure that my problem has not the same cause. Still, I cannot find what I did wrong.
import Foundation
import CoreBluetooth
#objc(SmallerBeaconServiceImpl)
class SmallerBeaconServiceImpl : NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
private let centralManager : CBCentralManager
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == CBManagerState.poweredOn && isRunning {
startScanningForPeripherals()
}
print("updated state: \(central.state)")
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(peripheral.name ?? "no name")
}
func startScanningForPeripherals() {
if centralManager.state != CBManagerState.poweredOn {
return
}
centralManager.scanForPeripherals(withServices: nil, options: nil)
print("is scanning: \(centralManager.isScanning)")
}
private(set) var isRunning = false
func startListening() {
if isRunning {
return
}
isRunning = true
print("started.")
startScanningForPeripherals()
}
func stopListening() {
if !isRunning {
return
}
isRunning = false
centralManager.stopScan()
}
override init() {
centralManager = CBCentralManager()
super.init()
print("initiated")
}
}
optional func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) is a CBCentralManagerDelegate method.
Your class SmallerBeaconServiceImpl is compliant with CBCentralManagerDelegate, but you didn't set your centralManager delegate (CBCentralManagerDelegate) property. That's why centralManager(_central:didDiscover:advertisementData:rssi) is not called.
This can be fixed with:
centralManager = CBCentralManager(delegate: self, queue: nil)

Resources