I'm using CoreBluetooth framework in my iOS application to scan and connect to other bluetooth devices. I'm able to scan successfully for devices using below code:
var centralManager: CBCentralManager?
var peripherals = [CBPeripheral]()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
In the CBCentralManagerDelegate, I implemented below code:
extension ViewController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if (central.state == .poweredOn){
self.centralManager?.scanForPeripherals(withServices: nil, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if !peripherals.contains(peripheral) {
let localName = advertisementData[CBAdvertisementDataLocalNameKey]
print("\(String(describing: localName))" )
peripherals.append(peripheral)
tableView.reloadData()
}
}
}
From above code localName is always nil. Am I missing any code?
To add more info,
When I am scanning for bluetooth devices in my Mac, I'm able to see the names of the devices. But when I am scanning for bluetooth devices on my iPhone, none of them are listed.
I cross checked and made sure that I enabled blue tooth on 4 more devices with me (iPad, 2 Android Phones and 1 Android TV)
Swift with core bluetooth library:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber){
let peripheralLocalName_advertisement = ((advertisementData as NSDictionary).value(forKey: "kCBAdvDataLocalName")) as? String
if (((advertisementData as NSDictionary).value(forKey: "kCBAdvDataLocalName")) != nil)
print(peripheralLocalName_advertisement)//peripheral name from advertismentData
print(peripheral.name)//peripheral name from peripheralData
peripherals.append(peripheral)
arrayPeripheral.append(advertisementData)
}
}
I changed in your code only source of name - instead of:
let localName = advertisementData[CBAdvertisementDataLocalNameKey]
I use:
let localName = peripheral.name
and now I see name of BLE devices around me.
I changed in your code only source of name - instead of:
let localName = advertisementData[CBAdvertisementDataLocalNameKey]
I use:
let localName = peripheral.name
and now I see name of BLE devices around me.
The didDiscoverPeripheral callback unfortunately can return nil for CBPeripheral names and also the advertisementData's local name for the very first callback your app gets after you start a scan.
If you know that the peripheral that you're working with will contain a local name in the Bluetooth advertisement packet, you can choose to ignore the first callback that contains a nil name.
// In didDiscoverPeripheral
guard let peripheralName = peripheral.name else { return }
print("Did discover peripheral: \(peripheralName), RSSI: \(RSSI)")
Related
var peripherals = [CBPeripheral]()
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if let _ = peripheral.name {
if !peripherals.contains(peripheral) {
peripherals.append(peripheral)
table.reloadData()
}
}
}
Using the delegate method above, called after centralManager.scanForPeripherals(withServices: nil), I am showing available peripherals(only those with name).
Since some of the available devices are also mine, I am sure that bluetooth is turned off on them.
So why does this method returns them as available? Are they cached somewhere or?
In Swift, and Xcode 9, I am trying to scan for and connect to all BLE devices. This code should print out "no name" for all BLE devices found. Scanning is working, but my BLE devices are not being found. Beside me, there is an Android phone with BLE, which I am unable to find.
Simulation device: iPhone 6s, bluetooth turned -ON
import UIKit
import CoreBluetooth
//let svcLight = CBUUID.itit(string: "24958294582945")
class BLEViewController: UIViewController , CBCentralManagerDelegate, CBPeripheralDelegate{
func centralManagerDidUpdateState(_ central: CBCentralManager) {
//scan for peripherals if "on" state change
if central.state == CBManagerState.poweredOn{
//not concerned with any services for now, and we are not passing in any options
central.scanForPeripherals(withServices: nil, options: nil)
print("scanning...")
//check for other states, add if else statements
}
}
//handle the callback for when something is found
//diddiscover was the autocomplete word for a peripheral
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if peripheral.name?.contains("POR 1007BT") == true {
//even if the peripheral doesn't have a name, we will get some info for it
print (peripheral.name ?? "no name")
centralManager.stopScan()
print(advertisementData)
//connect to the peripheral now
central.connect(peripheral, options: nil)
//store a local copy of the peripheral in the property
myPeripheral = peripheral
}
}
//so our central can begin scanning again
//if peripheral disconnects for whatever reason, it will immidiately start scanning
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
central.scanForPeripherals(withServices: nil, options: nil)
}
//callback for connecting to a central, didconnect was the autocomplete word
//discovr services
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("connected \(peripheral.name)")
peripheral.discoverServices(nil)
peripheral.delegate = self
}
//callback for diddiscover services: auto complete: diddiscover
//for each of the services in my peripheral, print out the UUID
//may not need this funciton, need to check
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
//optionally binding it
if let services = peripheral.services {
for svc in services {
print(svc.uuid.uuidString)
}
}
}
//! is used to unwrap it so anywhere in the code, it doesn't need to be unwrapped as an optional
var centralManager : CBCentralManager!
//keep a reference/store our peripheral
var myPeripheral : CBPeripheral?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//create instance of CBManager
//pass in self as the delegate to handle any callbacks
centralManager = CBCentralManager.init(delegate: self, queue: nil)
}
I'm working on an app which connects to a BLE Peripheral, and I sometimes run into a very strange issue, where the Peripheral is never found by the CentralManager. However, when pulling down Notification Center on the device, or when swiping up Control Center, the Peripheral immediately shows up and connects.
I've tried to find the cause of this problem, but so far I haven't been able to find anything. Apart from willResignActive and didBecomeActive, no other lifecycle functions seem to be called (AFAIK), but in neither of those functions I do anything other than printing that they've been called.
I already made sure to use self.centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]) in case I somehow screw up and not detect an initial scan.
Does anyone know what might be the cause of this, and how Control Center or Notification Center's influence on the app might solve this problem?
Thanks!
EDIT: Some extra code that might help find the issue. However, I know that when the problem happens, both discoveredPeripheral and connectedPeripheral are nil.
didConnect
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
if centralManager.isScanning {
centralManager.stopScan()
}
self.connectedPeripheral = peripheral
self.discoveredPeripheral?.delegate = self
debugPrint("\(Date()): Connected \(peripheral)")
peripheral.discoverServices(nil)
DispatchQueue.main.async {
self.rssiTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.readPeripheralRSSI), userInfo: nil, repeats: true)
}
}
didDiscover
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
if peripheral.name == global.qnr && discoveredPeripheral == nil {
centralManager.stopScan()
debugPrint(
"Discovered \(peripheral). " +
"Initiating authentication sequence with \n" +
"\tQNR: \(global.qnr)\n" +
"\tSessionKey: \(global.sessionKey)\n" +
"\tToken: \(global.bluetoothToken)")
if self.discoveredPeripheral == nil || self.discoveredPeripheral != peripheral {
self.discoveredPeripheral = peripheral
self.centralManager.connect(peripheral, options: nil)
}
}
}
startScanning
func scanForPeripherals() {
if !centralManager.isScanning && global.sessionKey != "" {
let services = [CONSTANTS.CBUUID]
self.centralManager.scanForPeripherals(withServices: services, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
}
}
You have to notify the delegate when a device(s) discovered. This is how I handle. I remove the previously added devices when I discover and then add or re-add. Once the discovered devices are added to the array, then reload the table if you are using the tableView to display.
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if (peripheral.name != nil) {
// remove previously added devices
items.removeAll()
items.append(peripheral)
}
// Set RSSI to custom peripheral signal property
customPeripheral.signal = RSSI
// Notify delegate for change in data.
NotificationCenter.default.post(name: .reload, object: nil)
}
// Extension for reload tableView when peripheral array is updated
extension Notification.Name {
static let reload = Notification.Name("reload")
}
In your case. You notify the notification to connect to your device once the scan is complete and the device is found.
// Connect with peripheral
func connectPeripheral(peripheral: CBPeripheral) {
centralManager.connect(peripheral, options: nil)
centralManager.stopScan()
print("Connecting")
}
I am trying to connect with BLE in background but it did not connect in background.
It is working when my app is in foreground.
I am trying to scan with UUID of the peripheral.
Here is the attached code.
override func viewDidLoad() {
super.viewDidLoad()
manager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
var msg = ""
switch central.state {
case .poweredOff:
msg = "Bluetooth is Off"
case .poweredOn:
msg = "Bluetooth is On"
let arrayOfServices: [CBUUID] = [CBUUID(string: "CCAExxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
manager?.scanForPeripherals(withServices:arrayOfServices, options: nil)
case .unsupported:
msg = "Not Supported"
default:
msg = "Not Connected"
}
print("STATE: " + msg)
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("Name: \(peripheral.name)") //print the names of all peripherals connected.
//you are going to use the name here down here ⇩
if peripheral.name == "Name of device" { //if is it my peripheral, then connect
self.myBluetoothPeripheral = peripheral //save peripheral
self.myBluetoothPeripheral.delegate = self
manager.stopScan() //stop scanning for peripherals
manager.connect(myBluetoothPeripheral, options: nil) //connect to my peripheral
}
}
How can resolve it?
Turn on 'Background Modes' Capabilities.
And turn on below two option:
1. User Bluetooth LE accessories.
2. Act as Bluetooth LE accessory.
Do same as Screenshot:
Now BLE is working in background mode in iOS application.
What you have to do is when you instantiate CentralManager you need to instantiate it with a restoration identifier.
Ex:
CBCentralManager(delegate: self,options:
[CBCentralManagerOptionRestoreIdentifierKey: "bleCentralManager"])
This is necessary as in the apple documentation it says "Core Bluetooth preserves the state of only those objects that have a restoration identifier".
Then when your app is relaunched into the background you have to reinstantiate your appropriate central manager with the same restoration identifier inside you appDelegate's application:didFinishLaunchingWithOptions: method.You can get the restoration identifiers like this:
let centralManagerIdentifiers = launchOptions![UIApplicationLaunchOptionsKey.bluetoothCentrals]
Finally in your central managers centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) delegate method you can get a list of all the peripherals the central manager was connected to or was trying to connect to and do whatever want to do in this method.
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
}
I am trying to connect to my Arduino project that is using the BlueFruit BLE spi module. I am having an issue when trying to connect using my iOS app. After I have found the device I try to connect to it but the state gets stuck in 'connecting' state=1. This prevent me from searching the services and such because a 'connected' state isn't achieved
Here is a code snip...
//check state of the bluetooth on phone
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOff{
//TODO: ADD SAVE DATA TO REALM BEFORE DISSMISSING
errorView.isHidden = false
}
if central.state == .poweredOn{
errorView.isHidden = true
//scan for peripherals with the service i created
central.scanForPeripherals(withServices: nil, options: nil)
}
}
//devices found(should only be ours because we will create Unique serviceID)
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// get advertisement data and check to make sure the name is matching. set it as the peripheral then make connection
if let peripheralName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
print("NEXT PERIPHERAL NAME: \(peripheralName)")
print("NEXT PERIPHERAL UUID: \(peripheral.identifier.uuidString)")
if peripheralName == nameID{
manager.stopScan()
self.peripheralHalo = peripheral
peripheralHalo!.delegate = self
manager.connect(peripheral, options: nil)
while(peripheralHalo?.state.rawValue == 1)
{
if(manager.retrieveConnectedPeripherals(withServices: [serviceID]).count > 0 ){
print("\(manager.retrieveConnectedPeripherals(withServices: [serviceID]))")
}
}
}
print("Connected!!")
}
When I call manager.connect(peripheral, options: nil) , the peripheral tries to connect. I add the following while loop for testing and always shows the state as "connecting". I have tried the LightBlue iOS app and i can properly connect and receive notifications of characteristic value changes so the Arduino firmware should be all good.PLEASE HELP!!!
You don't want that while loop; This will just block the Core Bluetooth delegate thread. After issuing the connect you will get a call to the didConnect CBCentralManagerDelegate method. Once the peripheral is connected you need to call discoverServices on the peripheral, which will give a callback to the peripheral:didDiscoverServices: peripheral delegate method. You can then discover the characteristics in a similar way.
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// get advertisement data and check to make sure the name is matching. set it as the peripheral then make connection
if let peripheralName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
print("NEXT PERIPHERAL NAME: \(peripheralName)")
print("NEXT PERIPHERAL UUID: \(peripheral.identifier.uuidString)")
if peripheralName == nameID {
self.peripheralHalo = peripheral
central.stopScan()
central.connect(peripheral, options: nil)
}
}
}
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
print("Connected!!")
peripheralHalo!.delegate = self
peripheral.discoverServices([serviceID)
}
Also, if you are going to store something that identifies which peripheral you want to connect to, I suggest you use the identifier and not the name as the name can change.