I'm trying to scan for a specific BLE Device on my app by specifying a CBUUID in the scanForPeripheralsWithServices(), but if I mention it scan is never processed.
Here is my CBCentralManagerDelegate code:
func centralManagerDidUpdateState(central: CBCentralManager) {
switch (central.state) {
case CBCentralManagerState.PoweredOff:
print("BLE powered off")
self.clearDevices()
case CBCentralManagerState.Unauthorized:
// Indicate to user that the iOS device does not support BLE.
print("BLE not supported")
break
case CBCentralManagerState.Unknown:
// Wait for another event
print("BLE unknown event")
break
case CBCentralManagerState.PoweredOn:
print("BLE powered on")
self.startScanning()
break
case CBCentralManagerState.Resetting:
print("BLE reset")
self.clearDevices()
case CBCentralManagerState.Unsupported:
print("BLE unsupported event")
break
}
}
func startScanning() {
print("\(NSDate()) Start scanning...")
if let central = centralManager {
let dictionaryOfOptions = [CBCentralManagerScanOptionAllowDuplicatesKey : true]
let ble = [CBUUID(string: "B737D0FF-AF53-9B83-E5D2-922140A9FFFF")]
central.scanForPeripheralsWithServices(ble, options: nil)
}
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
print("Discovered peripheral \(RSSI) dBM name: \(peripheral.name)")
print("UUID: \(peripheral.identifier.UUIDString)")
print("Peripheral \(peripheral.identifier)")
.....send event to server....
sleep(delayPolling)
self.startScanning()
}
If I change the scanForPeripheralsWithServices() without any CBUUID String it works fine:
central.scanForPeripheralsWithServices(nil, options: dictionaryOfOptions)
My main concern if to be able to launch this Thread in background and from Apple documentation, we need to scan for a specific device by specifying the CBUUID to be able to run my task.
Can you tell me where I'm wrong?
Related
UPDATE 15/6 - 1 - ALTERNATIVE METHOD
As per Paulw's suggestion, I changed the strategy and instead of discovering for peripherals, I connected with peripheral and it worked fine when either peripheral or central are in foreground. The problem occurs when:
1) I setup notifications on delegate calls.
2) Both peripheral and central are in background.
3) As I bring them closer, I see no delegate calls.
4) As soon as I bring either peripheral or central in foreground, I get didDiscover call and everything works fine even if I now put app in both phones in background.
In short, the central does not discover peripheral if both are in background state.
This is the code for both parts:
func startAsPeripheral() {
peripheralManager = CBPeripheralManager(delegate: self, queue: DispatchQueue.global(qos: .background))
}
func startAsCentral() {
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.global(qos: .background))
}
}
extension BluetoothManager: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
LogManager.shared.log(object: "Central state update")
LogManager.shared.log(object: central.state)
if central.state != .poweredOn {
LogManager.shared.log(object: "Central is not powered on")
} else {
LogManager.shared.log(object: "Central scanning for: " + self.serviceUUID.uuidString);
self.centralManager.scanForPeripherals(withServices: [self.serviceUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey : false])
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
self.centralManager.stopScan()
self.peripheral = peripheral
self.peripheral.delegate = self
self.centralManager.connect(self.peripheral, options: nil)
let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String
LogManager.shared.log(object: "Peripheral (" + (name ?? "unknown") + "): " + RSSI.stringValue)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
if peripheral == self.peripheral {
LogManager.shared.log(object: "Connected.")
peripheral.discoverServices([serviceUUID])
}
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
if peripheral == self.peripheral {
LogManager.shared.log(object: "Disconnected")
self.peripheral = nil
LogManager.shared.log(object: "Central scanning for: " + self.serviceUUID.uuidString);
self.centralManager.scanForPeripherals(withServices: [self.serviceUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
}
}
}
extension BluetoothManager: CBPeripheralDelegate {
func peripheral( _ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
LogManager.shared.log(object: "Services: \(peripheral.services!)")
for service in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service)
}
}
func peripheral( _ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
LogManager.shared.log(object: "Characteristics: \(service.characteristics!)")
}
}
extension BluetoothManager: CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
switch peripheral.state {
case .unknown:
LogManager.shared.log(object: "Bluetooth Device is UNKNOWN")
case .unsupported:
LogManager.shared.log(object: "Bluetooth Device is UNSUPPORTED")
case .unauthorized:
LogManager.shared.log(object: "Bluetooth Device is UNAUTHORIZED")
case .resetting:
LogManager.shared.log(object: "Bluetooth Device is RESETTING")
case .poweredOff:
LogManager.shared.log(object: "Bluetooth Device is POWERED OFF")
case .poweredOn:
LogManager.shared.log(object: "Bluetooth Device is POWERED ON")
let service = serviceUUID
let myService = CBMutableService(type: service, primary: true)
let writeCharacteristics = CBMutableCharacteristic(type: characteristricUUID, properties: .write, value: nil, permissions: .writeable)
myService.characteristics = [writeCharacteristics]
peripheralManager.add(myService)
peripheralManager.startAdvertising([CBAdvertisementDataLocalNameKey : "Spacedemic", CBAdvertisementDataServiceUUIDsKey : [service]])
LogManager.shared.log(object: "Started Advertising")
default:
LogManager.shared.log(object: "Unknown State")
}
}
}
ORIGINAL QUESTION
I have an app used for contact tracing. I configure it to work as both central and peripheral, both should work in background AND foreground. This is for contact tracing, so users won't have the application in foreground state in most cases. Advertising works fine as what I have observed, both in background and foreground. The problem here is that when central is in background, it sometimes gets into didDiscover only one time and sometimes never. Thus if a peripheral is in range, I am unable to detect the RSSI from central or if the user moves away from the central. In contrast, when in foreground, I get frequent updates with RSSI in didDiscover method.
How can I solve this? Or is there a workaround to get the desired results?
I am using CBCentralManager to scan for nearby BLE devices. Below is sample code:-
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOff:
debugPrint("Central manager state is Powered Off")
case .poweredOn:
debugPrint("Central manager state is Powered On")
loggerUtils.log.debug("Central manager state is Powered On")
centralManager.scanForPeripherals(withServices: nil, options: nil)
case .resetting:
debugPrint("Central manager state is resetting")
case .unauthorized:
debugPrint("Central manager state is unauthorised")
case .unknown:
debugPrint("Central manager state is unknown")
case .unsupported:
debugPrint("Central manager state is unsupported")
default:
debugPrint("Central Manager state is unrecognised....enum raw value is \(central.state.rawValue)")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("\n****\nPeripheral is \(peripheral.name ?? "No Name")\n*****\nAdvertisement data is \(advertisementData)\n*****\nRSSI is \(RSSI) ")
}
It shows the nearby speakers and other BLE enabled devices. But it is not showing any Android Phone whose bluetooth is powered on from settings option. I can see the android device in device list inSettings->Bluetooth in same iPhone.
Is there a way to discover all Bluetooth devices in an iOS app? I am using CoreBluetooth and I get all iDevices around plus one unknown device. However if I go to General -> Bluetooth -> Devices it lists a couple of devices that are not listed by the example app I am making. I need to find a none Apple device in the list and connect to it. Is this possible? And how come the two lists (the system one and the one in the app) are different?
In case it is important it needs to be used for ODB2 system later on, but for now just listing the none Apple device in the discovered devices will do.
Here is the code I am using to list the devices:
class HRMViewController: UIViewController {
var centralManager: CBCentralManager!
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
}
extension HRMViewController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown:
print("central.state is .unknown")
case .resetting:
print("central.state is .resetting")
case .unsupported:
print("central.state is .unsupported")
case .unauthorized:
print("central.state is .unauthorized")
case .poweredOff:
print("central.state is .poweredOff")
case .poweredOn:
print("central.state is .poweredOn")
centralManager.scanForPeripherals(withServices: nil)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print(advertisementData)
print(peripheral)
}
}
I am developing an iOS application for Heart rate monitoring, and want to connect a garmin device to my application using Bluetooth.
But I am not able to list down any Garmin device Using Core bluetooth in Swift using (180D Service UUID).
func startUpCentralManager() {
print("Initializing central manager")
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func discoverDevices() {
print("discovering devices")
let services = [CBUUID(string: HRM_HEART_RATE_SERVICE_UUID),CBUUID(string:HRM_DEVICE_INFO_SERVICE_UUID),CBUUID(string:HRM_DEVICE_BATTERY_LEVEL_SERVICE_UUID),CBUUID(string:CALORIE_SERVICE)]
centralManager.scanForPeripherals(withServices: services, options: nil)
}
//MARK: CBCentralManagerDelegate
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("checking state")
switch (central.state) {
case .poweredOff:
print("CoreBluetooth BLE hardware is powered off")
case .poweredOn:
print("CoreBluetooth BLE hardware is powered on and ready")
blueToothReady = true;
case .resetting:
print("CoreBluetooth BLE hardware is resetting")
case .unauthorized:
print("CoreBluetooth BLE state is unauthorized")
case .unknown:
print("CoreBluetooth BLE state is unknown");
case .unsupported:
print("CoreBluetooth BLE hardware is unsupported on this platform");
}
if blueToothReady {
discoverDevices()
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
let nameOfDeviceFound = (advertisementData as NSDictionary).object(forKey: CBAdvertisementDataLocalNameKey) as? NSString
print("\(String(describing: nameOfDeviceFound))")
print("Discovered Peripheral name : ", peripheral.name ?? String())
if !Utilities.allPeripheralList.contains(peripheral)
{
Utilities.allPeripheralList.add(peripheral)
}
let deviceObj = DeviceModel()
deviceObj.deviceName = peripheral.name ?? String()
deviceObj.UDID = String(format: "%#",(peripheral.identifier).uuidString)
self.addingPeripheralToGlobalSearchArray(obj: deviceObj)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices(nil)
self.connected = peripheral.state == CBPeripheralState.connected ? "YES" : "NO"
print("Connection Status : %#", self.connected)
if self.connected == "YES"{
Utilities.sharedInstance.goToDashboardTabBarScreen(viewController: self)
}
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
// central.scanForPeripherals(withServices: nil, options: nil)
}
func addingPeripheralToGlobalSearchArray(obj:DeviceModel){
var isFound:Bool = false
for i in 0..<Utilities.allDeviceList.count{
let deviceObj = Utilities.allDeviceList[i] as! DeviceModel
if obj.UDID == deviceObj.UDID{
isFound = true
}
}
if !isFound{
Utilities.allDeviceList.add(obj)
}
}
Above is the code which I am using to list down BLE devices. I am able to find Mio-Fuse devices using same peace of code but not Garmin devices.
You need to use the Garmin Standard SDK. Contact Garmin and register as a developer. You cannot connect directly using BLE you have to connect via the Garmin SDK API.
First off, I know this question has been asked a lot on SO. And I went through all of them trying to resolve it but to no avail.
I have a simple app that scans for Bluetooth enabled devices. Here's my code.
import CoreBluetooth
import UIKit
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
var manager: CBCentralManager!
var peripheral: CBPeripheral!
override func viewDidLoad() {
super.viewDidLoad()
manager = CBCentralManager(delegate: self, queue: nil)
}
// MARK: - CBCentralManagerDelegate
func centralManagerDidUpdateState(central: CBCentralManager) {
print(#function)
switch central.state {
case .Unsupported:
print("Unsupported")
case .Unauthorized:
print("Unauthorized")
case .PoweredOn:
print("Powered On")
central.scanForPeripheralsWithServices(nil, options: nil)
case .Resetting:
print("Resetting")
case .PoweredOff:
print("Powered Off")
case .Unknown:
print("Unknown")
}
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
print(#function)
print("Discovered \(peripheral.name) at \(RSSI)")
if peripheral.name!.containsString(name) {
manager.stopScan()
self.peripheral = peripheral
self.peripheral.delegate = self
manager.connectPeripheral(peripheral, options: nil)
}
}
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
print(#function)
}
func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
central.scanForPeripheralsWithServices(nil, options: nil)
}
// MARK: - CBPeripheralDelegate
func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
print(#function)
guard let services = peripheral.services else {
return
}
for service in services {
print(service.UUID)
if service.UUID == serviceUUID {
peripheral.discoverCharacteristics(nil, forService: service)
}
}
}
func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
print(#function)
guard let characteristics = service.characteristics else {
return
}
for characteristic in characteristics {
print(characteristic.UUID)
if characteristic.UUID == scratchUUID {
peripheral.setNotifyValue(true, forCharacteristic: characteristic)
}
}
}
func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
print(#function)
var count: UInt32 = 0
if characteristic.UUID == scratchUUID {
characteristic.value!.getBytes(&count, length: sizeof(UInt32))
print(String(format: "%llu", count))
}
}
}
I ran it in my iPhone 5 (iOS 9.3.4). I had a laptop running Windows, an Android phone, an iPad Air 2 and my Mac all Bluetooth turned on. But none of those devices were discovered. centralManagerDidUpdateState method gets called and scanning starts but that's it. didDiscoverPeripheral delegate method never gets called.
I repeated the process, this time ran it in the iPad Air 2 but same result.
If I go to the Bluetooth menu on the device I do see other devices being discovered.
In some SO answers, I saw that running it in the main queue would work. Like so, manager = CBCentralManager(delegate: self, queue: dispatch_get_main_queue()). But that did not work for me either.
I'd really appreciate any help on this.
Your other devices are probably not advertising a BLE service (like Paulw11 mentioned in the comments). All of the devices you mentioned will not appear as discoverable unless they are in the Bluetooth Settings page.
There are apps for iOS, Android, and Windows that have them advertise over LE. The easiest to use is LightBlue for iOS. Run your app on the iPhone while the other iOS device is advertising and you should see the advertisements. It might be useful to get an LE advertisement scanner on Windows or Android to double check that LightBlue is advertising.
Note Windows does not yet support Peripheral Role for Bluetooth LE,
so even though you will be able to see the advertisement you will not
be able to connect to it.