In the case where a peripheral has been discovered by a central, but right before user presses a button to connect, the peripheral goes out of range, so the connection cannot be completed. What method is called by CoreBluetooth?
I thought that centralManager didFailToConnect would be called, but in my code it is not being called, perhaps because the connection never started at all?
In this scenario which method should I use.
Thanks in advance for any help.
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("[ERROR] Could not connect to peripheral \(peripheral.identifier.uuidString) error: \(error!.localizedDescription)")
}
As said in the previous answer, none of the delegate methods will be called if any device does not initiated the connecting process.didFailToConnect method only invoked if the device initiated a connection procedure but due to any reason that device could not able to connect.
If you want to check if any device is there for the connection, then you need to create one timer for the specific time period and if during that period if no device is scanned then you can show a message showing "no device available" and again check for devices.
Nothing will be called in this scenario since the peripheral was not connected, nor was there an error connecting to the peripheral.
Core Bluetooth will have a pending connection for that peripheral and once it comes back into range the connection will complete and didConnectPeripheral will be called.
I faced a similar case,
What I needed was to handle a scenario where the user already had the device discovered but has waited some time before trying to connect and the device might not be available to connect anymore.
I didn't need any complex solution to maintain the In-Range status for the device just a solution that would recheck if the previously discovered device is still in-range without actually needing to maintain any status.
I solved it using below method:
I created a timeout logic of around 5 seconds (which we will invalidate if the connection was successful before the timeout) along with the connection request action.
If the connection was not made within 5 seconds, the timeout block will execute the disconnection request on the previously discovered peripheral which will cause the didFail delegate to be called by the central manager.
Timer.scheduledTimer(withTimeInterval: 5, repeats: false, block: { timer in
DEVICE_SCANNER_MANAGER.disconnectPeripheral()
DispatchQueue.main.async {
Utility.showAlert(vc: self, title: "", msg: "device went out of range", btnPrimary: "OK", handler: nil)
}
})
DEVICE_SCANNER_MANAGER => Custom manager class to handle all peripheral related operations.
Related
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.
User history: The user connects the app with the peripheral device, after a successful connection and after subscribing all the characteristic properly, the user puts the app in the background. The user leaves the rage area of the peripheral device and a connection happen.
Test case: When the user comes again and enters to the BLE range of the peripheral device, the OS should wake app the app and reconnect the app properly.
Solution: I'm using Support for State Preservation and Restoration, as the documentation: https://apple.co/2UM6xmi
The reconnection happens when only: when the app is killed by the system and the willRestore method is called, and the same happens when I restart the device.
Problem: When the user leaves the BLE range-area the disconnection happens but after 20 minutes of disconnection and going back to the range-area of the BLE peripheral the reconnection is not happening.
Is like the app is suspended, the BLE documentation says that in this case ( https://apple.co/2IpNffT ), the app is not going to restore the BLE central state, but now the question is if I kill the app programmatically after a disconnection, the method willRestore will be called only once and if there is not any BLE peripheral the connection won't happen anyway.
There is any way to achieve this? accordingly, to the documentation (in the first link) this case is possible to address, the documentation talks about a hypothetical app for an automatic door using BLE. But I have been tried this approach for a couple of days without any success, do you have any suggestion?
This is the code:
In the controller that scans the peripherials:
I initialize the central manager:
let options = [CBCentralManagerOptionRestoreIdentifierKey: "someIdentifier"]
self.centralManager = CBCentralManager(delegate: self, queue:nil, options: options)
in willRestoreState method:
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as! NSArray
print(peripherals.debugDescription)
if(peripherals.count > 0){
let peripheral = peripherals.object(at: 0) as! CBPeripheral
self.discoveredPeripheral = peripheral
self.discoveredPeripheral!.delegate = self
self.connectingPeripherals.append(self.discoveredPeripheral!)
self.centralManager!.connect(self.discoveredPeripheral!, options: nil)
}else{
scan()
}
}
didConnect method:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
savePeripheral(peripheral: peripheral)
self.discoveredPeripheral = peripheral
self.discoveredPeripheral!.delegate = self
currentState = STATE_CONNECTED
self.discoveredPeripheral!.discoverServices([CBUUID(string: TRANSFER_SERVICE_UUID)])
eventsToSubscribe = [CBUUID(string: SOME_CHARACTERISTIC_UUID)]
}
The scenarios that are working are these:
1- The app is killed with the statement: kill(getpid(), SIGKILL); it is reconnecting again.
2- The device is restarted after 1 or 2 minutes, it is reconnected.
3- Disconnection happens because we are out of range of the Bluetooth and the device enters in the range again within the first 5~10 minutes, then the device is reconnected.
The scenario that doesn't work:
1- If a disconnection happens because we are out of range of the Bluetooth and the device enters in the range again 10 minutes later the device won't be reconnected again.
Does someone have any clue what is happening here?
Thanks so much, I appreciate the support.
I am working on an app which we are connecting a chip by BLE and performing an operation like Connect/Read/Edit/Write.
Reading task working properly and try to write data after connection successful with the chip but getting the issue described below:
Connect BLE
Write first package of NSData using the .writeValue method on selected peripheral and characteristics.
Sent package call didWriteValueFor delegate method's as response.
Again I sent/write a second package of NSData using .writeValue
on different/static/predefined characteristics with it's matched peripheral. But the second time the response is not coming or the didWriteValueFor delegate is not calling.
I have managed/converted byte data in data NSData same as first to write/sent a package to BLE.
I send package data the first time and it's responding properly, but the second time it's not responding and no delegate is calling didWriteValueFor or didUpdateValueFor.
Thanks in advance.
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
I'm writing the app which uses CoreBluetooth framework. Everything is fine except one thing. According to Apple documentation for CBCentralManager, calling cancelPeripheralConnection: method causes invoking of centralManager:didDisconnectPeripheral:error: delegate method, but in my case centralManager:didFailToConnectPeripheral:error: is invoked. Any clues why is that?
Thanks for every help.
Edit
I'm not connected to peripheral, but I have pending connection. To be specific, I call connectPeripheral: method and when app is waiting for connection, user taps "Cancel" button. Then, I call cancelPeripheralConneciton: and later I have situation described above. The error I get has domain CBErrorDomain and description Unknown error.