Objective-C iOS 6 BLE (Bluetooth 4.0) Distance calculation - ios

Is it possible to calculate the distance between the device and the iPhone, or at least, have an idea of the strength so I can check if the distance is getting smaller or bigger as I move. I would like to build some sort of system which makes it easier to find "lost" things in a close perimeter.
Is such thing possible? And if so, what is the best way to do this.
I assume that there is no obstruction between the two devices.

Yes, absolutely!
Actually it's basic functionality provided by Core Bluetooth. To reference a link provided by a previous answer:
As soon as a Peripheral is discovered during the scanning, the Central
delegate receives the following callback:
(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber
*)RSSI
This call notifies the Central Manager delegate (the view controller)
that a peripheral with advertisement data and RSSI was discovered.
RSSI stands for Received Signal Strength Indicator. This is a cool
parameter, because knowing the strength of the transmitting signal and
the RSSI, you can estimate the current distance between the Central
and the Peripheral. So, you can use the distance as a filter for a
given service: only if the Central is close enough to the Peripheral,
then your app does something.
The RSSI value allows you to estimate the distance, create virtual fences and can also be used to zero-in on a BLE device by comparing the new RSSI measurement to the previous one.

Related

The background advertised peripheral to be scanned from other BLE devices either in foreground/background

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 to detect with CoreBluetooth when a peripheral disappears?

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.

UUIDs of iBeacons are different on different devices

I'm developing an application that will use iBeacons for indoor navigation, and I found that the rate the function locationManager:rangingBeaconsDidFailForRegion:withError: is being called is not high enough, so I'm going to add the RSSI data from CoreBluetooth's centralManager:didDiscoverPeripheral:advertisementData:RSSI:.
And I found a curious fact: when I listen to the iBeacon with CoreLocation and log the peripheral id:
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
{
NSLog(#"%#, RSSI: %#", peripheral, RSSI);
each beacon is reported with different UUID on different devices:
A25804BD-D77A-5004-4C2C-301D996C7367 - my iPhone 5
843F4237-6059-9A5E-AA34-0BD92304BE1F - colleague's iPhone 5
77685805-C253-52BD-B787-8B95308834FB - other colleague's iPad mini
The idea was to bind UUID of each beacon to its location, so this behavior is quite functionality-breaking.
Why the UUID of physically the same beacon (not powered off/on) is different on different devices? Is it an expected behavior? If it is, how could I opt out of it?
You cannot read the identifiers of standard iBeacons using CoreBluetooth. As Chris Stratton said in his comment, the UUID that CoreBluetooth gives you is a device UUID, which is randomly generated on a per-session basis by iOS. It has nothing to do with the iBeacon ProximityUUID.
More details on why you can't read iBeacon identifiers with CoreBluetooth are here: http://developer.radiusnetworks.com/2013/10/21/corebluetooth-doesnt-let-you-see-ibeacons.html
It is true that you only get on RSSI measurement per locationManager:didRangeBeacons:inRegion: callback. This is a real obstacle to doing a custom distance estimate. Behind the scenes, iOS can gather 10x as many measurements for iBeacons transmitting at 10Hz. You are correct that you can get more measurements using CoreBluetooth, but the problem is that there is no reliable way of lining up which bluetooth devices you see with CoreBluetooth correspond to the iBeacons you can see with CoreLocation.

iBeacon for auto peripheral pairing + authentication?

If I want to do "NFC based transactions" but in the iBeacon way, when 2 iOS devices are in the "immediate" range, how can I connect both device automatically without pairing/authentication, so that I can do the transaction communication?
The scenario:
User's iPhone detects iBeacon signal, prompt user to open the app
User opens the app, and wants to purchase a physical item
User grab his device to the retailer's iOS-based POS device
Both iOS devices detect each other are in the "Immediate" range and start the connection/pairing without manual authentication
Transaction communication starts here
From my understanding, scanForPeripheralsWithServices cannot return enough information for the above scenario to happen, does anyone know the correct way to implement such service?
Thank you
The scenario you describe can be carried out with very basic connections exactly the way you listed it, no BLE level pairing, authentication is required at all. More precisely:
The presence of iBeacon triggers the app to notify the user of an opportunity to buy something.
The client's iOS device scans for peripherals and the retailer's POS advertises itseld. The distance between the two is estimated using the RSSI value from the advertisement packages. The next step is activated if this value gets high enough (practically, higher than a given threshold, like -30 or so. You can select whatever fits your needs)
The client uses connectPeripheral: to connect to the POS. This alone will not trigger authentication or pairing.
You implement the the transaction by using secure encryption and authentication and whatever needed. This is completely proprietary to your solution and you need to create it or get some existing one to work. (I don't know about existing implementations, though.)
To see an example, the of the RSSI based distance estimation, the BTLE example is a good starting point. Here is the corresponding excerpt from BTLECentralViewController.m
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
// Reject any where the value is above reasonable range
if (RSSI.integerValue > -15) {
return;
}
To implement the transaction, you need to understand the basics of Core Bluetooth: the services, characteristics, BLE transactions (read, write, notifications...)
Pairing and authentication are never required for detecting and ranging for iBeacons when using iOS CoreLocation APIs. iBeacons are connectionless and transmit only. You can tell that two devices are in immediate range from one another by making both transmit as iBeacons, and then using the CoreLocation ranging APIs to detect the proximity:
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)iBeacons inRegion:(CLBeaconRegion *)region {
for (CLBeacon *iBeacon in iBeacons) {
if (iBeacon.proximity == CLProximityImmediate) {
// Add code to process transaction here
}
}
If you want to exchanges secure data wirelessly between the two devices after you have verified they are in immediate proximity, you can do so with Bluetooth as #allprog suggests in his answer. You can also do this through web services, which allow you to have an iBeacon-only solution without having to drop down to using CoreBluetooth APIs. See this answer for more details on how that might work.

Can I measure Bluetooth signal strength in iOS?

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.

Resources