Before developing an android application using an external device, a detailed knowledge of the device is essential. In this case, it is important to know what an iBeacon actually does, how reliable it is, and how to properly incorporate its functionality within an android application.
Identifying iBeacons
iBeacons are Bluetooth Low Energy (BLE) devices that transmit advertising packets. Each advertising packet contains 3 main parameters:
- Universally Unique Identifier (UUID)
- Major
- Minor
UUID contains 32 hexadecimal digits and is used to distinguish a group of iBeacons from other beacons or BLE devices that are within range. Major is used to identify a subgroup of beacons and Minor identifies a specific beacon. To find out these parameters in android, download an android BLE scanner application and look for an id similar to e2c56db5-dffb-48d2-b060-d0f5a71096e0. This is the UUID of the iBeacon which will be used in this example.
iBeacon Calibration
In order to scan for iBeacons in an android app, the Android Beacon Library will be used. This library provides the means to output Received Signal Strength Indicator (RSSI) levels as well as the distance between the iBeacon and the android device. The lower the transmitter power of the iBeacon is, the error in distance calculation will be greater. Therefore, it is recommended that the transmitter power is set to strongest if possible.
Why is calibration necessary
The iBeacon was physically placed at different distances from the device, then for every distance, average RSSI values were measured and then plotted for analysis. Ideally, the result would be a linear function. However, Bluetooth is susceptible to interference, so it is not surprising that the result is actually not an ideal one. The function extrema are also noticeably far from the trendline. If the transmitting power of the iBeacon were greater, the result would have been closer to the ideal linear function with better distance accuracy.
Distance axis does not show correct distances. For example, the sixth measurement shows that the beacon was 102cm away from the device when in fact it was 240cm away.
Calibrating the Beacon
The results from measuring only the RSSI values and plotting them with actual distances of the iBeacon from the device are shown below.
Much closer to the ideal linear function.
Considering average RSSI values greater than -69.2 becomes impractical because the function above would produce two distance values. Since there is no means of determining which of the two distance values to take as the final result, RSSI values greater than -69.2 will be omitted. Therefore, the final function used when calculating the distance between the android device and the beacon is shown below.
The function used to calculate distance from given RSSI value.
The distance is calculated by finding two points whose RSSI is less than and greater than the RSSI value received by the iBeacon. Next, find the equation between two points and input the received RSSI value. The result can still be affected by interference, so it is recommended to take several RSSI values before making distance calculations.
Service Implementation
Firstly, create a function to return distance. This function creates an equation between two points.
private String calculateDistance(double averageRSSI, double x1, double y1, double x2, double y2) { double slope = (y2 - y1) / (x2 - x1); double b = y2 - slope * x2; Double distance = slope * averageRSSI + b; return distance.toString(); }
Next, define beaconManager in onStartCommand(…) and implement BeaconConsumer methods.
public class BeaconService extends Service implements BeaconConsumer{ /* … … … */ //Points from the final chart //Notice: axes here are inverted private final int numPoints = 6; private final double[] beaconRSSI = {-35, -48.4, -55.8, -61.12, -62.56, -69}; private final double[] beaconDistance = {0, 65, 99, 149, 209, 238}; /* … … … */ @Override public void onBeaconServiceConnect() { beaconManager.addRangeNotifier(new RangeNotifier() { @Override public void didRangeBeaconsInRegion(Collection collection, Region region) { if (collection.size() > 0) { currCalculation++; sumRSSI += collection.iterator().next().getRssi(); if(maxCalculations == currCalculation){ Log.d("Tag", "Distance: " + e(sumRSSI/maxCalculations)); currCalculation = 0; sumRSSI = 0; } } } }); try { beaconManager.startRangingBeaconsInRegion(beaconRegion); } catch (RemoteException e) { e.printStackTrace(); } } private String getDistance(double averageRSSI) { if(averageRSSI > beaconRSSI[0]) return "0"; for(int i = 0; i < numPoints - 1; i++) { if(averageRSSI <= beaconRSSI[i] && averageRSSI >= beaconRSSI[i + 1]) return calculateDistance(averageRSSI, beaconRSSI[i], beaconDistance[i], beaconRSSI[i + 1], beaconDistance[i + 1]); } //if averageRSSI is less than -69 return "Too far"; } }
This service constantly scans for iBeacons with given UUID in range. Doing this is not viable for actual applications since it significantly impacts the battery on an android device. In order to overcome this problem, it is recommended that you increase the scan period or start the service only when absolutely required and then stop it when done.