In this post, I’ll be discussing how to get an Android device to detect iBeacons.
What is an iBeacon?
If you’re reading this, you probably know what an iBeacon is, but, just in case you don’t, let’s do a quick summary.
An iBeacon is simply a Bluetooth LE device which has formatted its advertising packet in a specific way. That’s it, really, but it comes with one huge advantage over the regular Bluetooth paradigm: it’s wicked fast.
For most BLE devices, the process of extracting data can in practice be lengthy, frustrating, cumbersome, and buggy. A device must be detected, connected to, asked for a list of services it provides, respond with a list of those services, and finally asked for the data in one of those services. Each step is a potential point of failure.
With iBeacons, none of those steps need be taken (though, there’s nothing stopping you from doing so). All you need is broadcast in the advertising packet; before you ever try to connect to it, you have the information you’re looking for. What is that information? So glad you asked:
- A UUID (which consists of 16 bytes)
- A so called ‘major’ number (2 bytes)
- A ‘minor’ number (2 bytes)
- A power level (1 byte)
(This neglects some prefixed data indicating that this is formatted according to Apple’s guidelines for iBeacons.) The UUID, major number, and minor number are all used to specify a particular iBeacon. The use case most often cited is that the UUID could be a brand level identifier (for instance, Walmart), the major number would be the store number, and the minor number would be a specific place within the store. The power level is how strong this device’s signal was measured (in a lab) to be at 1 meter away. This can be combined with what you (in the real world) detect as the power of the signal, and used to determine about how far away you are from the given iBeacon.
Detecting iBeacons with Android
Up until version 4.3, Android had no support for Bluetooth LE, so if you’re trying to support earlier versions, you’re plum out of luck. In this example, I won’t go through how to use the BLE tools Android provides; you can find that in the last link. I’ll assume you know how to set up and scan for BLE devices, and the question I’ll be addressing is how to parse the iBeacon advertising packet once you’ve received it.
In order to detect a BLE device, you must instantiate a BluetoothAdapter.LeScanCallback
, which has a method onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord)
. What we want is the scanRecord
.
Here’s what we’re going to do:
- Convert the byte array into a hex string
- Verify the prefix is for an iBeacon
- Separate the iBeacon data into its constituent components
Step one is easy enough, and googling ‘java byte array to hex’ comes up with plenty of good answers. Here’s a particularly fast one (though we probably don’t need to worry about speed too much):
public static String bytesToHex(byte[] bytes){ char[] hexChars = new char[bytes.length * 2]; for ( int j = 0; j < bytes.length; j++ ) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); }
We’ll use that to turn the scanRecord
into a hex string:
String hexScanRecord = bytesToHex(scanRecord);
Once we have that, we need to make sure it’s actually an iBeacon, and has the appropriate preamble. To do this, we simply compare it to the hex string "02011A1AFF4C000215"
. Once we know it is an iBeacon, we can remove that preamble:
if (hexScanRecord.startsWith("02011A1AFF4C000215")){ String iBeaconInfoString = hexScanRecord.substring("02011A1AFF4C000215".length(), 58);
Next, we extract the UUID:
String uuid = iBeaconInfoString.substring(0, iBeaconInfoString.length() - 8);
the major number:
String majorString = iBeaconInfoString.substring(uuid.length(), uuid.length() + 4);
and the minor number:
String minorString = iBeaconInfoString.substring(uuid.length() + 4, iBeaconInfoString.length() - 2);
If desired (and it probably is), you can convert the Strings into ints using Integer.parseInt
:
int major = Integer.parseInt(major, 16);
The power level is the last byte (the last two hex digits) in iBeaconInfoString
, and should you desire to determine how far away the iBeacon is, there are plenty of formulas out there to help you do so.
And that’s it! Turns out it’s pretty easy to detect iBeacons. In a future post, I’ll show you how to turn an Android device into an iBeacon, (although, that will require Android 4.4.4 and above).