I would like to have a list of ble devices to which my iOS can connect, which refreshes when ble devices appear and disappear.
In order to do that, I created an NSMutableDictionnary* peripheralsAvailable, and everytime - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; is called by the CBCentralManager, I add the peripheral to the peripheralsAvailable dictionnary (and then update a UITableView). All is OK here.
However I can't find how I can update the dictionnary if a Peripheral "disappear". It seems that I can only add peripherals in my dictionnary when they are detected, but I can't remove one when I shut it down for example.
Could you tell me if I'm missing something?
The OS will cache discovery of devices. I.e. you will only get one "discovery" event per device.
To continuously get discovery events while the peripheral is advertising, you must use the following option:
CBCentralManagerScanOptionAllowDuplicatesKey
A Boolean value that specifies whether the scan should run without
duplicate filtering.
The value for this key is an NSNumber object. If YES, filtering is
disabled and a discovery event is generated each time the central
receives an advertising packet from the peripheral. Disabling this
filtering can have an adverse effect on battery life and should be
used only if necessary. If NO, multiple discoveries of the same
peripheral are coalesced into a single discovery event. If the key is
not specified, the default value is NO.
Setting the above option to YES, you could keep track of all of the peripherals that are advertising and when it stops advertising, you could remove it from the list.
For a device that you've connected to, there is the didDisconnectPeripheral delegate event.
Bluetooth devices don't advertise that they're about to go away, nor do they advertise that you're about to go out of range. You get an advertisement while they're advertising and you're within range, and you get nothing when you're out of range or they stop advertising. There's no event to trigger on when they're gone. You have to remember the devices that are advertising, and when they stop advertising (you haven't received an advertisement in awhile), you can remove it from the list.
Great answer by Marcus. One additional note to add is that the scan option CBCentralManagerScanOptionAllowDuplicatesKey as mentioned above does not work in the background.
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 CBCentralManager scan option is ignored while scanning in the background.
Related
I'm working on with BLE of iOS where in i could able to establish a peripheral advertisement and scanning at the same time.
I have 2 devices where in both would scan advertise at the same time, it works fine when the devices are in foreground.
Now the issue is: The device is in background meaning the advertisement is not working(not able to show the Local Name and UUID as it will mask as per Apple) because as per Apple Docs:
advertisement key are placed in a special “overflow” area; they can be discovered only by an iOS device that is explicitly scanning for them.
Now my question how can i scan for this overflow area?
Also note when a device is advertising in background i do not get a call back in below delegate too:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
Any possible way i can do this approach, which would really helpful.
The Solution to the above scenario is as per #Paulw11 said.
We just need to scan for specific Peripheral Id.
Thanks Paulw11
How does core bluetooth determine the new peripheral device as unique?
I have devices. Each device has services. Each service has characteristics.
When we test bluetooth-based app, it appears, that some (different) devices have same uids ( peripheral.identifiers ).
I think that it happens because of lack of advertising information on device ( devices don't advertising services and they coalescing into one peripheral.identifier )
I suppose that services can't differ (could/should be the same) among devices, the same story with characteristics.
So, which info should be enough for bluetooth to determine that new peripheral device is unique?
Our experiment
We have two devices, one with name 'oldboy' and another with name 'newone'.
Also, we have same services on each device ( services has same structure and same uuids. services uids and characteristics uids are the same across all devices )
Names are differ between devices.
Sad, devices don't advertising services and app scan for all services passing nil to appropriate method scanServices: options: instead of services uids array. It could be done but it doesn't help in background and seems very bad for energy efficiency.
Server send information about all devices that could be found.
We suppose, that uids are not generated by system and we can hardcode uid of devices and differ devices on this characteristic ( peripheral.identifier )
One iDevice have tested app with perihperal device called 'oldboy' very long.
And this iDevice should work with another device called 'newone'.
As it started to scan, it found an device with name = 'newone' and 'peripheral.identifier = peripheral(oldboy).identifier'.
Again, it can't be true, so, we disconnect all devices except 'newone' and restart experiment. Ok, same issue.
And final check was on interaction. We connect to device ('newone') and try to send command to it. Nothing happens. But when we try to send command to 'oldboy' device, we win!
I dive into this issue and found that connected 'newone' device identifier somehow equal to 'oldboy' device identifier.
It happens in didDiscoverPeripheral handler. So, before connection system recognize 'newone' peripheral device as a 'oldboy' peripheral device ( their peripheral.identifier are equal )
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
// take peripheral info ?
// check that peripheral identifier is valid for us
BOOL identifierIsOk = YES;
DDLogDebug(#"we found! peripheral: %# ", peripheral);
DDLogDebug(#"and data: %#",advertisementData);
if (identifierIsOk) {
// put peripheral into dictionary storage and try to connect and discover services and characteristics
[self centralManager:central safeConnectToPeripheral:peripheral];
}
}
BUG
Finally, we solved our troubles.
We found that MAC addresses are same on different devices!
Afterwords, I want to add this article as core bluetooth understanding tool
Is there a mechanism in iOS CoreBluetooth so that an event will be triggered when a discovered (but not connected) peripheral is "lost" i.e. a peripheral that was advertising is no longer advertising .
You could specify the CBCentralManagerScanOptionAllowDuplicatesKey:YES in the options to CBCentralManager's scanForPeripheralsWithServices: and then keep a table of observed devices, ageing the entries periodically, but this will impact battery life and you will not be able to operate once your application is no longer in the foreground. Connecting is a better option - is there some reason why you don't want to connect to the device?
You can look in AltBeacon, an open source project we released https://github.com/CharruaLabs/AltBeacon how we do this. Check the method reportDelegates of the class AltBeacon.m
A bit more in detail. What we do it to keep reporting in an preestablished interval and then age the signal (RSSI) of the device. After a while if it is too aged you can trigger the no longer advertising callback. What we do is just to change an enum status distance to unknown.
I have been trying to setup an app to make the device both scan for peripherals and advertise as a peripheral. The goal is for two devices to be woken up in the background when they become near each other via bluetooth discovery. From the Apple Documentation, it seems that you should be able to run BLE in the background (with bluetooth-central and bluetooth-peripheral background modes enabled), and my application works when one device is in the foreground.
First, I advertise data like so:
NSDictionary *advertisingData = #{CBAdvertisementDataLocalNameKey:#"my-peripheral",
CBAdvertisementDataServiceUUIDsKey:#[[CBUUID UUIDWithString:identifier]]};
// Start advertising over BLE
[peripheralManager startAdvertising:advertisingData];
I then set the device to scan for data:
NSArray *services = #[[CBUUID UUIDWithString:identifier]];
[centralManager scanForPeripheralsWithServices:services options:nil];
However, when both go into the background (device has to be locked), the bluetooth cannot discover and
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
never gets called on either device. How can I fix this?
Thanks
I'm afraid what you are trying to do will not work.
I have tried to achieve the same thing.
The problem is the difference in scanning in foreground and background.
When you are scanning for devices in the foreground you can scan for anything. In the background you must specify the actual service UUID you are scanning for. Ok, this isn't actually a problem as you know the UUID you are looking for.
Peripheral:
Broadcasting as a peripheral again works differently in foreground and background.
In foreground it works like any normal BT peripheral. In the background it has a very limited amount of space to work with, so your peripherals UUID is hidden away and not broadcast. Only when a central device (an iPhone in foreground) requests the information from it will it wake your app and show it's UUID.
So the 2 cancel each other out. As your background scan can only scan for devices with a specific UUID and your background peripheral cannot advertise its UUID, they cannot see each other.
1 of your devices (either peripheral or central) must be in the foreground to work.
This has been discussed several times on the Apple Bluetooth mail list.
You should elaborate on how you're testing this, because theoretically it looks like it should work. There's two primary issues you may be facing:
1.) Scanning is throttled down when iOS devices are in the background.
While scanning in the foreground will likely immediately discover a device advertising next to it, discovery in the background can take up to ~60 times longer. The iOS system makes no assumptions that the user would prefer one app to have better Bluetooth functionality than another (or that only one app wants to use it). And since it is shared functionality, they want users to have a uniform experience across apps. You should check out the technical specifications regarding Advertising and Scanning intervals to get a better idea of what Apple has to do under the covers.
2.) Your devices may have already discovered each other before entering the background.
We must remember that Apple disables the CBCentralManagerScanOptionAllowDuplicatesKey scanning flag when we enter the background. Since you're not even specifying this flag, it defaults to NO anyways. So if they've even seen each other once, you will not get another callback when they are in the background.
I personally needed such a feature and I developed an open source component: https://github.com/omergul123/Discovery
It might be very helpful if you want to exchange an ID even if the peer apps are running at background.
Can I measure the signal strength of Bluetooth devices within range of my iPhone?
Basically what I want to do is scan for a list of devices within range, and then see which one has the highest signal strength.
Is it possible in iOS and how would I do it if so?
Yes there is a way to measure the signal strength for Bluetooth Low Energy (4.0) it is the RSSI number. When you scan for peripherals you will set the flag CBCentralManagerScanOptionAllowDuplicatesKey to YES as shown below:
NSDictionary * dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#YES, CBCentralManagerScanOptionAllowDuplicatesKey, nil];
// Start scanning for peripherals
[cmanager scanForPeripheralsWithServices:nil options:dictionary];
If you want to see the RSSI number work without writing any code you should check out the app LightBlue in iTunes. When you connect to a peripheral it will show you the updated RSSI number every second when it is connected.
Have a look to the CoreBluetooth documentation:
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
{ ... }
RSSI is what you are looking for. Disclaimer: Core Bluetooth is made for Bluetooth 4 LE only.
If the exact range does not matter, but you are interested in scanning devices which are available in general, you may have a look to the github project BeeTee, which allows you to scan for all Bluetooth devices around you (not only Bluetooth LE). Again disclaimer: I am the author of BeeTee. ;-)
When you're coding your 'central' code using CBCentralManager, you'll eventually connect to the CBPeripheral you're looking for. Once you're connected to the peripheral, keep a reference to it, set your object as the peripheral's delegate and invoke 'readRSSI' on the peripheral. You'll get a delegate callback peripheral: didReadRSSI: error: If you write a method that invokes 'readRSSI', you can invoke it using performSelector: withObject: afterDelay:.
Another of the suggested answers to this question is to supply the 'allow duplicates' key when scanning. The docs for the dictionary key CBCentralManagerScanOptionAllowDuplicatesKey when passed to scanForPeripheralsWithServices:options: indicates that "Disabling this filtering can have an adverse effect on battery life and should be used only if necessary".
If you choose to write a delayed invocation, you can tune the frequency of the calls to help you manager the impact on your users' batteries.