I have a BLE device that I am writing an app to pair with. I can discover and connect to the device with no problems. But if I am connected and pull and reinsert the battery on the BLE device I get the didDisconnectPeripheral callback but I never get another didConnectPeripheral even though I'm still scanning. I also tried calling retrieveConnectedPeripheralsWithServices and retrievePeripheralsWithIdentifiers but neither of those return anything.
How can I reliably reconnect after cycling the power on my BLE device?
As soon as the peripheral disconnects you can issue another connect - iOS will automatically reconnect to the device once it is visible again and call your didConnectPeripheral: delegate method
-(void) centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(#"Disconnected from peripheral");
[central connectPeripheral:peripheral options:nil];
}
There is no need to rescan/re-discover the peripheral.
You may want a more comprehensive implementation that updates UI etc.
Here is some sample code that connects to a peripheral and displays the vendor information -
https://github.com/paulw11/BTBackground
Related
I have an application which is using retrieveConnectedPeripheralsWithServices to get the list of paired HID devices. I can get the list and connect to the desired one successfully using connectPeripheral code. When the connection succeeds, following delegate is being called:
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
and for disconnection, I get the corresponded delegate:
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
Now when the HID device gets connected again to the iOS (directly from Setting, not from my application), I want to be informed. Is there any way to find out when an HID device reconnected to the OS?
Finally, I found the answer.
Thanks to #Paulw11 for his guidance, based on Apple Documentation:
When the user leaves home, the iOS device may eventually become out of range of the lock, causing the connection to the lock to be lost. At this point, the app can simply call the connectPeripheral:options: method of the CBCentralManager class, and because connection requests do not time out, the iOS device will reconnect when the user returns home.
I currently experience issues with Bluetooth reconnects. I am writing an app which uses a BLE device (HID device) to unlock the motorbike. I want the App to reconnect to the device as soon as it comes into range.
The flow the app does:
Scan and bond with device
Communicates with device
When device is out range, call centralManager.connect().
Wait for didConnect or didFailToConnect to be called.
Most of the time, it works well, but there are some cases that the app never get a didConnect or didFailToConnect, and the only way to reconnect to the device is to kill the app and open it again.(so it's not a hardware issue, it works perfectly with android)
What I do:
Keep a strong reference to all peripherals.
Set the delegate of all peripherals to our singleton bluetooth manager
Only connect with one device per time
The first connect always works, but then it randomly can not re-connect
CentralManager is running with a custom queue:
dispatch_queue_t queue = dispatch_queue_create("com.ble.queue", 0);
centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:queue options:options];
In didDisconnect I reconnect to device
(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error
{
[centralManager connectPeripheral:peripheral options:nil];
}
Is there anything I can do to fix it?
I am currently working on an app that gets data from a BLE device – similar to a heart monitor. The app reads the data from the device and then when it gets a specific amount of data, it creates a .csv file and uploads it into a server. Everything works fine, except when the device gets out of the range. The app just stop receiving data and doesn’t recognize that the connection is lost. I don’t get any error message. The app just stops in the middle of the “getting data” loop and keeps waiting for a data that never comes. When the device is back in the range, nothing happens.
I would like to show an alert informing that the BLE device is out of the range. When the device is in the range again, the app should reconnect to it automatically and then continue reading data from the device. How can I implement that? I tried to get the CM state – using the function below – but it didn’t work.
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
printf("Status of CoreBluetooth central manager changed %d (%s)\r\n",central.state,[self centralManagerStateToString:central.state]);
}
I’ve even tried to add an if clause inside the loop to check the device state, but it didn’t work also.
I am using the Texas instruments chip CC2540.
As suggested by Paulw11 and SJoshi (Thank you guys), I had to implement the didDisconnectPeripheral method. So, here is how I did it:
.h file:
// will be invoked once disconnected
-(void)centralManager:(CBCentralManager *)central
didDisconnectPeripheral:(CBPeripheral *)peripheral
error:(NSError *)error;
.m file:
-(void) centralManager:(CBCentralManager *)central
didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
[central connectPeripheral:peripheral options:nil];
// you can add whatever you want here.
// will execute when the peripheral loose its connection.
}
I'm working on BLE device named WIRELESS BLOOD PRESSURE WRIST MONITOR.
I've downloaded these application and every thing is working great.
But when I tried to connect to the device from my application, I didn't receive a response.
and my code is straight like the code from developer.apple.com and also this tutorial.
This is my code:
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[_centralManager scanForPeripheralsWithServices:nil options:nil];
I receive notification on the delegate for centralManagerDidUpdateState but I don't receive the didDiscoverPeripheral even if I'm searching with nil in services.
When I go to Setting -> Bluetooth: I can see the device and it is connected and the signal of bluetooth is on. So the iPhone can see the BLE device, So when I used in my code these method
retrieveConnectedPeripheralsWithServices to get the list of connected device it returns 0 object.
So I don't know what is the problem, keeping in mind that the BLE device is working great with there own app so it's Low Energy not classic , and the BLE device display bluetooth signal when opening the app.
So any ideas from the GEEKS :D
Thanks..
There are lots of pieces you need to take care of:
You need to wait for the centralManagerDidUpdateState to indicate CBCentralManagerStatePoweredOn. Anything you do before will either result in error or be ignored. So your call to scanForPeripheralsWithServices is probably ignored. This is true for other APIs, like the retrieveConnectedPeripheralsWithServices you mentioned.
It is also possible that the device turns off advertising after it is connected, so your scanning will not succeed until you disconnect from it.
Scanning in the background has many limitations. You can search the SO questions to find out the details. In the beginning I would advise you to not to try backgrounded operation as it can be really tricky.
Instead of searching immediately after initializing the central manager, try first to wait for update that the power is on.
Try these steps:
In viewDidLoad remove the call to scanForPeripheralsWithServices
Add method scanForPeripherals, that will first check the Central Manager is powered on and also check for the scanning state (see below).
code:
- (void)scanForPeripherals {
if (self.centralManager.state != CBCentralManagerStatePoweredOn) {
NSLog(#"CBCentralManager must be powered to scan peripherals. %d", self.centralManager.state);
return;
}
if (self.scanning) {
return;
}
self.scanning = YES;
[self.centralManager scanForPeripheralsWithServices:nil options:#{ CBCentralManagerScanOptionAllowDuplicatesKey: #YES }];
NSLog(#"Scanning started");
}
In ViewWillAppear call [self scanForPeripherals]
In centralManagerDidUpdateState, call scanForPeripherals only if central manager is powered on.
code:
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
if (central.state != CBCentralManagerStatePoweredOn) {
NSLog(#"CBCentralManager not powered on yet");
return;
}
// The state must be CBCentralManagerStatePoweredOn
[self scanForPeripherals];
}
Add BOOL property scanning. Using the scanning propery allows you to safely try and scan before the update callback called. You should handle the scanning state to prevent calling scan twice.
When I get delegate didConnectPeripheral:(CBPeripheral *)peripheral
can I just store peripheral in an array, and then use it to re-connect later, instead of using retrievePeripherals and its subsequent didRetrievePeripherals?
Seems like it would be easier, if it's feasible and has no risk.
How much later can (CBPeripheral *)peripheral be re-used? Is it still valid after disconnection with that peripheral?
Workflow:
scanForPeripheralsWithServices() - to scan for a peripheral
didDiscoverPeripheral:(CBPeripheral *)peripheral - when it's detected
connectPeripheral:peripheral
didConnectPeripheral:(CBPeripheral *)peripheral stopScan and store the (CBPeripheral *)peripheral for later.
... read or write characteristics ...
cancelPeripheralConnection
didDisconnectPeripheral
LATER, TO RE-CONNECT...
connectPeripheral:peripheral - from array with peripheral
didConnectPeripheral:(CBPeripheral *)peripheral
...
YES, it will work (but it's terrible practice). The retrievePeripherals: method was specifically created so that you can reconnect to peripherals between subsequent launches of an application. You can use your method, but once the app is shutdown, you will never be able to connect to the peripheral again (without putting it into advertising mode and starting from scratch basically). You can store the uuid between launches, but you cannot store a CBPeripheral object. So there's the big downside right there.
So to sum up: it will work, but it doesn't really gain you anything. It is not faster than calling retrievePeripherals: and then connecting them. Your suggested method is only limiting your abilities for connections in CoreBluetooth. But an interesting question nonetheless.