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.
Related
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 have a BLE device that I connect with my application. I have managed to auto-connect to it the first time the application is used and I have also managed to make the application work if the device gets turned off and then on again. BUT the last one works only if the device gets turned off for like less than 20-30 minutes.
If I turn on the device in less than 30 minutes the application will reconnect to it automatically and read its data. But after 30 minutes although when I turn on the device it appears to reconnect to the iPhone, the application does seem to reconnect to it and read data.
Any suggestions, hints or tips? Thank you all in advance.
EDIT: adding some code as requested
/*
Invoked whenever an existing connection with the peripheral is torn down.
Reset local variables
*/
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)aPeripheral error:(NSError *)error
{
self.connected = NO;
if( self.peripheral )
{
[self.peripheral setDelegate:nil];
self.peripheral = nil;
}
[self.centralManager scanForPeripheralsWithServices:#[myService] options:#{ CBCentralManagerScanOptionAllowDuplicatesKey : #YES }];
}
I am Working on BLE project, everything works fine when the app in the foreground.It can discover and connect to the peripheral, all the call back method work perfectly.
But the problem is that, when the app in the background mode (I press home button). Only the centralManagerDidUpdateState delegate method get called.
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBCentralManagerStatePoweredOn:
[self.cbCentralManager scanForPeripheralsWithServices:nil options:#{ CBCentralManagerScanOptionAllowDuplicatesKey : #YES }];
break;
default:
break;
}
}
I use scanForPeripheralsWithServices:nil option, But when the app in the background, the didDiscoverPeripheral call back never called. I have edit my plist file with "bluetooth-central" option to support ble central role in background.
Any idea why didDiscoverPeripheral method not call when app in the background?
Paulw11 said are right, If your app find the peripherals in the foreground. It will not call the didDiscoverPeripheral for the same peripherals when it enters the background.
For more information about the iOS BLE Behavior in the background mode. You can check this answer
What exactly can CoreBluetooth applications do whilst in the background?
I was working on Estimote Nearable type beacons. After iOS10 SDK update, I encountered exception from CBCentralManager stating :
<CBCentralManager: 0x17009e050> has provided a restore identifier but the delegate doesn't implement the centralManager:willRestoreState: method
To fix this, Turn-On "Background Mode", in Xcode -> Capabilities -> Background Mode
Scan for nil( scanForPeripheralsWithServices:nil) services will not work in background. You must search for some specific service in background.
You have to set the UUID in scanForPeripheralsWithServices: method which Peripherals/BLE device is advertising.
From Official Apple reference
You can provide an array of CBUUID objects—representing service
UUIDs—in the serviceUUIDs parameter. When you do, the central manager
returns only peripherals that advertise the services you specify
(recommended). If the serviceUUIDs parameter is nil, all discovered
peripherals are returned regardless of their supported services (not
recommended). If the central manager is already scanning with
different parameters, the provided parameters replace them. When the
central manager object discovers a peripheral, it calls the
centralManager:didDiscoverPeripheral:advertisementData:RSSI: method of
its delegate object.
Apps that have specified the bluetooth-central background mode are
allowed to scan while in the background. That said, they must
explicitly scan for one or more services by specifying them in the
serviceUUIDs parameter. The CBCentralManagerOptionShowPowerAlertKey
scan option is ignored while scanning in the background.
Here
Apps that have specified the bluetooth-central background mode are allowed to scan while in the background. That said, they must explicitly scan for one or more services by specifying them in the serviceUUIDs parameter.
So scanForPeripheralsWithServices:nil with nil it will not work in background , you need to specify list of UUIDS
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
CoreBluetooth state preservation issue: willRestoreState not called in iOS 7.1
Hey all. I’ve been working on a Bluetooth LE project for the past few weeks, and hit a roadblock. I have been unable to get state restoration working properly in iOS 7 / 7.1. I’ve followed (I think) all of the steps Apple lays out, and got some clues on other stack overflow posts.
I added the proper bluetooth permissions to the plist
when I create my central manager, I give it a restore Identifier key.
I always instantiate the CM with the same key
I added the willRestoreState function to the CM delegate
My Test Case:
Connect to peripheral
Confirm connection
Simulate Memory Eviction (kill(getpid(), SIGKILL);)
Transmit Data
Results iOS 7:
The app would respond in the AppDelegate didFinishLaunchingWithOptions function, but the contents of the NSArray inside of launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey] was always an empty array.
Results on iOS 7.1:
Progress! I can see my CentralManager key in the UIApplicationLaunchOptionsBluetoothCentralsKey array 100% of the time, but willRestoreState is never called.
Code:
//All of this is in AppDelegate for testing
#import CoreBluetooth;
#interface AppDelegate () <CBCentralManagerDelegate, CBPeripheralDelegate>
#property (readwrite, nonatomic, strong) CBCentralManager *centralManager;
#end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:#{CBCentralManagerOptionRestoreIdentifierKey:#“myCentralManager”}];
//Used to debug CM restore only
NSArray *centralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
NSString *str = [NSString stringWithFormat: #"%# %lu", #"Manager Restores: ", (unsigned long)centralManagerIdentifiers.count];
[self sendNotification:str];
for(int i = 0;i<centralManagerIdentifiers.count;i++)
{
[self sendNotification:(NSString *)[centralManagerIdentifiers objectAtIndex:i]];
}
return YES;
}
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary *)state {
activePeripheral = [state[CBCentralManagerRestoredStatePeripheralsKey] firstItem];
activePeripheral.delegate = self;
NSString *str = [NSString stringWithFormat: #"%# %lu", #"Device: ", activePeripheral.UUID];
[self sendNotification:str];
}
//sendNotification is a func that creates a local notification for debugging when device connected to comp
When I run the tests, didFinishLaunchWithOptions is called 100% when my BLE device communicates to the phone when the app is not in memory, but willRestoreState is never called.
Any and all help would be great! thanks!
Okay, so I've had to delete two of my answers already to this question. But I think I've finally figured it out.
This comment is the key to your problem. Essentially, this centralManager:willRestoreState: only gets called if it's force closed by the OS while an outstanding operation is in progress with a peripheral (this does not include scanning for peripherals On further investigation, if you're scanning for a service UUID and the app is killed in the same way, or you've already finished connecting, it will in fact call your delegate).
To replicate:
I have a peripheral using CoreBluetooth set up on my MacBook. I advertise on the peripheral, and have the central on my iPhone discover it. Then, leaving the OSX peripheral app running, kill your BT connection on your Mac and then initiate a connect from the central on your iOS device. This obviously will continuously run as the peripheral is non-reachable (apparently, the connection attempt can last forever as Bluetooth LE has no timeout on connections). I then added a button to my gui and hooked it up to a function in my view controller:
- (IBAction)crash:(id)sender
{
kill(getpid(), SIGKILL);
}
This will kill the app as if it was killed by the OS. Once you are attempting to connect tap the button to crash the app (sometimes it takes two taps).
Activating Bluetooth on your Mac will then result in iOS relaunching your app and calling the correct handlers (including centralManager:willRestoreState:).
If you want to debug the handlers (by setting a breakpoint), in Xcode, before turning BT on on your Mac, set a breakpoint and then select 'Debug > Attach to Process... > By Process Identifier or Name...'.
In the dialog that appears, type the name of your app (should be identical to your target) and click "Attach". Xcode will then say waiting for launch in the status window. Wait a couple seconds and then turn on BT on OSX. Make sure your peripheral is still advertising and then iOS will pick it up and relaunch your app to handle the connection.
There are likely other ways to test this (using notify on a characteristic maybe?) but this flow is 100% reproducible so will likely help you test you code most easily.
Had the same issue. From what I can work out, you need to use a custom dispatch queue when instantiating your CBCentralManager and your willRestoreState method will be triggered. I think this is due to async events not being handled by the default queue (when using "nil") when your app is started by the background recovery thread.
...
dispatch_queue_t centralQueue = dispatch_queue_create("com.myco.cm", DISPATCH_QUEUE_SERIAL);
cm = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:#{CBCentralManagerOptionRestoreIdentifierKey:#"cmRestoreID",CBCentralManagerOptionShowPowerAlertKey:#YES}];
...
You need to move the CentralManager to its own queue.
You also need to instantiate your peripheral with a restore identifier.