I just started with the core bluetooth framework for iOS and I'm developing an app that needs to constantly scan for BLE devices so that I can retrieve their RSSI number every minute or so.
Currently I have:
manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[manager scanForPeripheralsWithServices:nil options:options];
this starts my app scanning for BLE devices and calls this delegate method when a device is discovered:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
NSLog(#"Did discover peripheral. peripheral: %# rssi: %#, UUID: %# advertisementData: %# ", peripheral, RSSI, peripheral.UUID, advertisementData);
//Do something when a peripheral is discovered.
rssiLabel.text = [RSSI stringValue];
[manager retrievePeripherals:[NSArray arrayWithObject:(id)peripheral.UUID]];}
this method gets me the peripheral's RSSI number which i can display. The last line then calls this delegate method:
- (void) centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals {
NSLog(#"Currently known peripherals :");
int i = 0;
for(CBPeripheral *peripheral in peripherals) {
NSLog(#"[%d] - peripheral : %# with UUID : %#",i,peripheral,peripheral.UUID);
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[manager scanForPeripheralsWithServices:nil options:options];
}
This code seems to be working and doing a scan roughly every 1 minute, but I don't exactly know why it working...
The documentation on core bluetooth is pretty sparse so if anyone has any idea on how to do this, or has a better way to do what I'm trying to accomplish I'd appreciate the help!
Have you tried changing the scan option to YES?
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[manager scanForPeripheralsWithServices:nil options:options];
If you do this you will get your "didDiscoverPeripheral" callback with every ad packet that is seen by your iPhone, which would normally be about every 100ms (although I see this callback timing varying a lot for the same device). This includes the RSSI of each device it sees.
This should be a lot faster than your ~1 minute update rate.
Is far as I can see, this should do what you want.
When you started scanning for peripherals with the original call, your delegate should begin to get calls whenever a BLE device is discovered. This will continue until you stop the scan with a call to
[manager stopScan];
I don't think you actually need the second call to scanForPeripheralsWithServices in your centralManager:didRetrievePeripherals method, since, as far as I know, the scanning doesn't stop until you tell it to. I'm still getting started on this, too, though, and there may be a timeout I have not found, yet.
I'm pretty sure the reason you get a call about once a minute is because the BLE device is only advertising that often. If it advertises more often, like a device in discovery mode, I think you will get the calls more often. I would be interesting if you could confirm that. If the device has a discovery mode, you might try triggering it to see if the notices speed up.
You shouldn't do continous scanning as it is very costly for power. Once you discovered devices you have an array of CBPeripheral objects returned to you. On CBPeripheral you can read RSSI and get a callback when RSSI changes. See the following documentation:
http://developer.apple.com/library/mac/#documentation/CoreBluetooth/Reference/CBPeripheralDelegate_Protocol/translated_content/CBPeripheralDelegate.html
Swift Implementation of #Anders solution:
manager.scanForPeripheralsWithServices(nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey : NSNumber(value: true)])
Related
IOS application is communicating with BLE peripheral using Core-Bluetooth framework. The app has registered for glucose characteristic and is receiving data from the peripheral after every one minute.
It is being observed that, when the application is in idle state there is no disconnection from the BLE peripheral whereas if I navigate between the ViewControllers present in the application then there happens to be continuous disconnection with the BLE peripheral.
The connection parameters set are within the given range as mentioned in Apple's Core-Bluetooth programming guide. Any ideas why the connection keeps on getting disconnecting?
I had also used the BTLE Transfer Source Code present in apple developer site. And had replaced the Transfer service UUID with Glucose service UUID. I had tested this on iPhone 6 with iOS version 8.3. I am still facing the same issue of disconnection.
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
if (self.discoveredPeripheral != peripheral) {
self.discoveredPeripheral = peripheral;
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
[self.centralManager stopScan];
[self.data setLength:0];
peripheral.delegate = self;
[peripheral discoverServices:#[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]]];
}
check you don't explicitly use weak properties to manage your peripherals (by default types are strong), check this issue.
However without any piece of code all I can suggest is to grab a BLE packet sniffer and see who, when and therefore why it sends the disconnection command. In this process beware about the channel hopping, you have 1/3 of chances to capture the channel with the sniffer so just retry until you see the data flow on the screen. If not now, it's always welcome to have a device like that if you will regularly work with Bluetooth Low Energy.
// method called whenever you have successfully connected to the BLE peripheral
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
self.myPeripheral = peripheral;
self.myPeripheral.delegate = self;
NSString *connected = [NSString stringWithFormat:#"Connected: %#", peripheral.state == CBPeripheralStateConnected ? #"YES" : #"NO"];
NSLog(#"%#", connected);
}
Put this method,
Note: It is a good practice to assign your discovered peripheral to a retained CBPeripheral into centralManager: didConnectPeripheral
//this one line code stops disconnecting device automatically.
[[Manager sharedManager].connected_PeripheralDevice discoverServices:nil];
I want to reconnect to BLE device after device is moved out/terminated by user or system/reboted in background mode.
I know that it's possible : - see this question with description
Question - How can i setup centralManager for automatically reconnect to peripheral in background mode if app was terminated? Can someone describe step-by-step how it can be done?
Few word about current implementation:
I create centralManager with options like:
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:#{
CBCentralManagerOptionRestoreIdentifierKey: #"myCentralManagerIdentifier",
CBCentralManagerRestoredStatePeripheralsKey : #YES,
CBCentralManagerRestoredStateScanServicesKey : #YES,
CBCentralManagerRestoredStateScanOptionsKey : #YES
}];
After that i start to scan for BLE device
[self.centralManager scanForPeripheralsWithServices:[self discoverableCharacteristics] options:nil];
in - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI i connect to peripheral:
NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
[self.centralManager stopScan];
peripheral.delegate = self;
[self.centralManager connectPeripheral:peripheral options: #{
CBConnectPeripheralOptionNotifyOnNotificationKey : #YES
}];
After that i can discover services and characteristics - all looks like ok. When i discover characteristic and read/write data i cancelPeripheralConnection
in didDisconnect i reconnect to device
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error
{
[central connectPeripheral:peripheral options:nil];
}
i also implement centralManager:willRestoreState: like:
NSArray *peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey];
for (CBPeripheral *peripheral in peripherals) {
[central connectPeripheral:peripheral options:nil];
peripheral.delegate = nil;
}
In plist. added required key App communicates using CoreBluetooth.
Currently if i connected to device and terminate it - it relaunch automatically and connect to device - all it's ok, but if it's terminated again - nothing is happening.
Also if i moved out from peripheral and that come back - nothing happened.
Update
regarding point 5 - my fall - should use this key with connectPeripheral
in WillRestoreState:
NSArray *peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey];
if (!peripherals.count) {
peripherals = [central retrievePeripheralsWithIdentifiers:[self discoverableCharacteristics]];
}
if (peripherals.count) {
for (CBPeripheral *peripheral in peripherals) {
[central connectPeripheral:peripheral options:#{
CBCentralManagerRestoredStatePeripheralsKey : #YES,
CBCentralManagerRestoredStateScanServicesKey : #YES,
CBCentralManagerRestoredStateScanOptionsKey : #YES
}];
}
} else {
[self startScanning];
}
Current result - app will relaunched if it not swiped out from tray. I use my mac as a peripheral, so some times when i not launch app that make role of peripheral central can connect to mac itself not to required service.
Another question - are it's good option to reconnect to peripheral while lost connection for keeping connection like:
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error
{
[central connectPeripheral:peripheral options:#{
CBCentralManagerRestoredStatePeripheralsKey : #YES,
CBCentralManagerRestoredStateScanServicesKey : #YES,
CBCentralManagerRestoredStateScanOptionsKey : #YES
}];
}
Also try to change notify characteristic on peripheral and read it on device. If all done in foreground - all works perfect, but in case connection was done in background some times didUpdateValueForCharacteristic not called at all, but didUpdateNotificationStateForCharacteristic is called with no error - this mean (i think) that something was done wrong by my side. Maybe u can advice where problem can be
And one more question - is there is some restriction in writing data to characteristics? because in apple sample it's setuped to 20 bytes.
First off I wanna start by saying that I have been working with CoreBluetooth for about two years now and from what I have noticed CoreBluetooth State Preservation and Restoration does not work reliably at all. You can get it working sort of "ok", but you will never get it to reconnect reliably unless Apple fixes it some day.
Having said that, I want to note a few things on your setup.
1) In centralManager:willRestoreState: you can only retrieve peripherals that has done any communication while the app was terminated. This means that you should also implement centralManagerDidUpdateState: and if the state is CBCentralManagerStatePoweredOn then you can use the retrievePeripheralsWithIdentifiers: method to retrieve the other peripheral and reset their delegate as well. This of course means that you have to stor the peripheral identifiers somewhere in your app somewhere. Also remember to reset pending connections here as well.
2) You set the delegate to nil in centralManager:willRestoreState:! So even if it does connect then you will not know about it i:P
3) Your app will only get relaunched if the app was terminated by the system. It will not get relaunched if you manually swipe-kill it from the application list. Neither will it get relaunched if the device is rebooted, unfortunately.
4) The CBConnectPeripheralOptionNotifyOnConnectionKey is not necessary when using the bluetooth-central background mode and is just annoying for a user so I would not use it.
5) CBCentralManagerRestoredStatePeripheralsKey, CBCentralManagerRestoredStateScanServicesKey, CBCentralManagerRestoredStateScanOptionsKey are not valid initialisation options so I don’t get why you are using those..
5) If the bluetooth switches state while the app is terminated then all pending connections will be lost and you will not be relaunched to know about it. This on its own effectively means that State Restoration is rather useless.
Anyhow, I am sad to say it, but if you are developing an app that must rely on the peripheral being reconnected in the background then I wold not recommend doing it. You will be frustrated. I could probably write an essay about all the bugs in Core Bluetooth that Apple does not want to fix. Even more frightening is that you can pretty easily ruin the bluetooth connectivity globally on the device from one single app so that no app can use bluetooth until the device is rebooted. This is pretty bad since it goes against Apples own Sandboxing principle.
If you need any more help, just let me know!
/A
I have one Bluetooth LE device, I need to scan it only, that I have done with Core Bluetooth Framework in iPhone SDk.
Below is sample code,
manager is object of CBCenterManager which writes in the init method:
manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
scanning process:
- (void)startScan
{
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
manager.delegate = self;
[manager scanForPeripheralsWithServices:nil options:options];
}
Now I got that device in delegate methods,
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(#"Did discover peripheral. peripheral: %# rssi: %#, UUID: %# advertisementData: %# ", peripheral, RSSI, peripheral.UUID, advertisementData);
}
**Here I have write only few lines of code,
This is not enough data for me to show in interface. Because peripheral.UUID is unique for each device, if I change device to discover BLE, it will be changed.
So I want unique address of Bluetooth LE device which I got same in every iOS devices.
Like peripheral.UUID is 1FE639DB-3C54-B5A8-74A4-3D9FBFCAD074
I had discover same thing in android got address like C8:4D:93:78:98:AE this.
and its unique for all android devices,
So I am searching for the same thing in iPhone SDK.
Is it possible to get same unique address of Bluetooth LE in iPhone SDK?
Thanks for your time to read questions.
I have a chip, working on LE Bluetooth and transmitting it's UUID.
I need to discover it from IOS app and get it's UUID.
I know how to establish a connection between two IOS devices, but not with another chips.
Thanks!
You should check out Apple's CoreBluetooth Temperature Example.
First you will use the CBCentralManager to find the available Bluetooth Peripherals with the UUID you are looking for. It's a lengthy process that requires delegates and I can't easily give you code snippets to do this. It will look something like this.
.h file will have these. Remember to add the CoreBluetooth Framework.
#import <CoreBluetooth/CoreBluetooth.h>
CBCentralManager * manager;
CBPeripheral * connected_peripheral;
(Change your UUID accordingly):
NSArray * services=[NSArray arrayWithObjects:
[CBUUID UUIDWithString:#"0bd51666-e7cb-469b-8e4d-2742f1ba77cc"],
nil
];
[manager scanForPeripheralsWithServices:services options: [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBCentralManagerScanOptionAllowDuplicatesKey]];
[manager connectPeripheral:peripheral options:nil];
From there you know you have the right peripheral, but you still need to select it and stop the CBManager from continuing to scan for new devices.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
[manager stopScan];
NSArray *keys = [NSArray arrayWithObjects:
[CBUUID UUIDWithString:#"0bd51666-e7cb-469b-8e4d-2742f1ba77cc"],
nil];
NSArray *objects = [NSArray arrayWithObjects:
#"My UUID to find",
nil];
serviceNames = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
[connected_peripheral setDelegate:self];
[connected_peripheral discoverServices:[serviceNames allKeys]];
}
Now that you have told your peripheral to advertise what services it has, you'll have a delegate for parsing those services.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
CBService *bluetoothService;
for (bluetoothService in connected_peripheral.services) {
if([bluetoothService.UUID isEqual:[CBUUID UUIDWithString:#"0bd51666-e7cb-469b-8e4d-2742f1ba77cc"]])
{
NSLog(#"This is my bluetooth Service to Connect to");
}
}
I wish this process was easier to explain. The best way to figure it out is to download Apple's Temperature Example and run it on your iPhone or iPad (it won't work in the simulator). Even though you probably aren't broadcasting temperature it will find your Bluetooth LE device and will parse through the services it is broadcasting. Placing breakpoints in the LeDiscovery.m file of that project should show you the steps needed to discover your Bluetooth LE chip from an iOS app.
Hope this helps!
When I use:
CBUUID * uuid = [CBUUID UUIDWithString:#"1800"]; // GAP
DEBUG_LOG(#"CBUUID: %#",uuid); // CBUUID: Generic Access Profile
_centralMan = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[_centralMan scanForPeripheralsWithServices:[NSArray arrayWithObject:uuid]
options:nil];
I cannot find my peripheral, but when I use:
_centralMan = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[_centralMan scanForPeripheralsWithServices:nil
options:nil];
…it shows up immediately.
When I connect, I am able to discover/read/write to all the services I am expecting. My understanding is that GAP makes this possible. I have also tried CBUUID's for other services that I know are running (specifically "1804", TX power service), to no avail; the scan never discovers anything.
Even though the services are running (I can communicate w/ them upon connecting), when I scan passing the service UUID array as nil and this delegate method is called...
-(void) centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
DEBUG_LOG(#"Found peripheral w/ UUID: %# RSSI: %# \n AdvData: %#",peripheral.UUID,RSSI,advertisementData);
DEBUG_LOG(#"Services found: %d",peripheral.services.count);
for (CBService * service in peripheral.services) {
DEBUG_LOG(#"Found service: %# w/ UUID %#",service, service.UUID);
}
}
…it always reports "Services found: 0". Should I expect the services array to be populated w/ services found during the scan? (I assume this is at least partially possible, based on scanForPeripheralsWithServices:[NSArray arrayWithObject:uuid]
options:nil.)
Could this be a problem w/ my peripheral's service advertisements? Am I using the API incorrectly? Any guidance is much appreciated! :-)
PS: I'm pretty green w/ Bluetooth.
PPS: I control the source to the peripheral (it's a CC2540DK). So I'd be as likely to believe the problem is there as it is here.
Turns out our peripheral code was not advertising the UUIDs a la this Bluetooth spec.
As soon as we dropped them into the advertising packet, iOS picked them up no sweat.