Hi,
There is a random issue I am facing where if I'm trying to connect to a device, it goes to connecting state but even after waiting quite a while, the state doesn't become connected, neither does connection fails.
The issue is more observable when connecting to multiple devices, for eg if i'm connecting to 8 devices, sometimes 1 gets stuck in connecting state. In an even rarer case, all devices start getting stuck in connecting state. Killing the app and opening seems to resolve the issue but a few days later the issue resurfaces.
This is what I'm doing regarding CBBluetoothManager:
- Define two dispatch queues
fileprivate let bluetoothQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".CBCentralManager")
fileprivate let nonBluetoothQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".CBCentralManager.2")
- Define centralManager
private lazy var defaultCentralManager = CBCentralManager(delegate: self,
queue: bluetoothQueue,
options: [
CBCentralManagerOptionShowPowerAlertKey: true,
CBCentralManagerOptionRestoreIdentifierKey: "MyCBCentralManager"
])
- Start and stop scan
func start() {
isScanningRequested = true
bluetoothQueue.async {
self.centralManagerDidUpdateState(self.defaultCentralManager)
}
}
func stop() {
isScanningRequested = false
restartScanJob?.cancel()
bluetoothQueue.async {
self.centralManagerDidUpdateState(self.defaultCentralManager)
}
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
nonBluetoothQueue.async { [self] in
switch central.state {
case .poweredOn:
if(isScanningRequested){
Logger.log(priority: .INFO, message: "starting scan")
restartScanJob?.cancel()
restartScanJob = DispatchWorkItem(block: {
self.start()
})
bluetoothQueue.asyncAfter(deadline: .now() + .seconds(120), execute: restartScanJob!)
//Restarting after 2 mins as it is observed that scan slows down after sometime
central.scanForPeripherals(withServices: getServiceCBUUIDs(), options:[CBCentralManagerScanOptionAllowDuplicatesKey: true] )
}
else{
Logger.log(priority: .INFO, message: "stopping scan")
central.stopScan()
}
break
default:
break
}
}
}
- All delegate functions run in nonBluetoothQueue so as to return the function immediately. Following is one example
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
nonBluetoothQueue.async { [self] in
guard let extendedPeripheral : ExtendedPeripheral = getExtendedPeriperal(from: peripheral) else {
Logger.log(priority: .ERROR, message: "Connected to unknown \(peripheral.identifier.uuidString) disconnecting")
defaultCentralManager.cancelPeripheralConnection(peripheral)
return
}
Logger.log(priority: .DEBUG, message: "Connected to \(peripheral.identifier.uuidString)")
extendedPeripheral.connected = true
delegate.didConnect(extendedPeripheral)
peripheral.discoverServices(nil);
}
}
PS. ExtendedPeripheral is a wrapper class to store the CBPeripheral object
- I also have a safety check to not call connect repeatedly. This was done because I used to observe the connection getting delayed if calling connect when it's already connecting
private func connect(to extendedPeripheral : ExtendedPeripheral) {
if (extendedPeripheral.cbPeripheral.state == .connected || extendedPeripheral.cbPeripheral.state == .connecting) {
Logger.log(priority: .WARN, message: "Will not connect. Current state is \(extendedPeripheral.cbPeripheral.state)")
return
}
Logger.log(priority: .INFO, message: "Connecting to " + extendedPeripheral.identifier.uuidString)
defaultCentralManager.connect(extendedPeripheral.cbPeripheral)
}