5
votes

I have a project that requires ranging of beacons, I've looked at lots of sample code which all uses CLLocationManager and CLBeaconRegion. My issue with this is that the didRangeBeacons callback only gets called once per second.

Although I've not found any code examples specifically for beacons, am I correct in thinking that CoreBluetooth would give me more control over how often my app scans for beacons?

My thinking is that if I can scan for beacons at a higher rate (say ever 200ms) I will be able to use more rssi values in my filtering function and therefore get a more accurate proximity without it taking too long. Am I correct in assuming this?

Can anyone point me to any sample code/tutorial on using CoreBluetooth with beacons (if it is worth the extra effort that is)?

1
Yes an No. If the beacon is a "pure beacon", iOS will translate its advertisement as a Beacon (while Android won't for instance), and it will be only seen through CoreLocation. If it's a mix, it can be seen with CoreBluetooth, but not with CoreLocation. "Advanced" Beacons, can switch, and send whatever they want, being "pure beacon", or both (in order for most of them, to be configurable through CoreBluetooth).Larme
@Larme that's interesting. So if it's a "pure beacon" I am stuck with CoreLocation? Is there a list somewhere of the different beacon types (pure, advanced, mix)?user2424495

1 Answers

5
votes

If you use CoreBluetooth to scan for beacons instead of CoreLocation, you get one callback for each advertisement packet detected vs. one callback every second for CoreLocation. There are advantages and disadvantages to both approaches, so it is important to understand exactly how each work to make the proper choice. The advantages and disadvantages are highly affected by the advertising rate of the beacon, with the standard iBeacon advertising rate being 10 Hz.

Using CoreLocation ranging

The following method will get a callback once per second, but only for BLE Advertisements that match the iBeacon layout and match a ProximityUUID within that layout that has been registered for ranging as part of a CLBeaconRegion object. When these conditions are met, the following callback is called every second regardless of how many beacon packets are detected in that time interval:

locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion).

Each CLBeacon object in the array of beacons has a rssi field. This field contains the mean rssi of all the beacon packets detected over the past one second interval.

If the beacon is advertising at 10 Hz, this rssi reading will be the average of the 8-10 packets received in the past second (only 80-90% of packets are typically received due to radio noise, collisions and interference).

If the beacon is advertising at 1Hz or less (typical of battery beacons that try to save power) then only one rssi reading will be included in the average. (There is no way to know how many detections went into the rssi value returned by the callback.)

So while you don't get access to the rssi value from each detection, and you can't control the averaging interval, you do get the benefit of the multiple detections in the sense that the rssi value is averaged and less noisy than if there had been only one reading. CoreLocation therefore gives you just as much accuracy as CoreBluetooth provided you are willing to accept its hard-coded averaging intervals.

The CLBeacon object also has fields for accuracy and proximity which are based on a derived value of rssi filtered over a longer interval (experimentation shows it is about 20 seconds). There is no control in the API over this averaging interval, and 20 seconds is a very long lag for some applications where you want rapid updates in distance estimates.

Using CoreBluetooth scanning

This is typically done by setting a flag allowing duplicate results:

centralManager.scanForPeripherals(withServices: [], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true] )

When the above scan is started, the following callback is made for every single bluetooth advertisement detected:

centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)

The rssi number in this callback is the raw value from the single packet detection. If your beacon is advertising at 10 Hz, you will get 8-10 callbacks per second on this method (again, not 100% of packets are received) and you can average or filter your rssi however you like. This gives you fine-grained control over using the rssi, and you can make distance estimates over arbitrary averaging intervals.

The above motivation led me to develop the open source iOS Beacon Tools, which allow detecting bacons with CoreBluetooth and calculating distance estimates based on rssi collected over arbitrarily specified averaging intervals.

However, there are several disadvantages to using CoreBluetooth:

  • iBeacon packets cannot be decoded, as the operating system filters out the data payload of any packet matching its layout. For this reason, you must use a similar layout like AltBeacon, or use an Eddystone frame.

  • Manufacturer advertisements like AltBeacon are not delivered in the background, only in the foreground.

  • Service advertisements like Eddystone are delivered in the background, but only at a very, very slow rate. So it is not useful for rssi data collection.