I have created an android application to scan for BLE using Bluetooth LEscanner. Now that I need my app to identify if a beacon belongs to iBeacon or Eddystone. So far, I'm successful in determining UUID,MajorId,MinorId of ibeacon by parsing the AD frame.
It's relatively easy to read the bytes of the advertisements if you know the byte offsets of all the fields. Two code snippets below show you how you can parse these out. The first shows how you can do this in your own onLeScan
callback using the Android Beacon Library, and the second shows how you can roll your own from scratch.
To explain how the layouts work, look at the code below. It uses the Android Beacon Libray's BeaconParser
class which handles all of the parsing for a configurable layout. (Even if you want to roll your own as shown in the second code snippet, it is worthwhile looking at the layout expressions so you know how they work. The expressions below show the details for AltBeacon which is very similar to iBeacon. AltBeacon is shown because there are no intellectual property restrictions with discussing its implementation. Both AltBeacon and Eddystone are open source standards.)
The first layout expression shows that AltBeacon (again very similar to iBeacon) has three identifiers ("i" expressions). The first one (known as UUID in iBeacon) is 16 bytes that goes from byte offset 4-19. The second one (known as major on iBeacon) is 2 bytes that goes from byte offset 20-21. The third one (known as minor on iBeacon) is 2 bytes that goes from byte offset 22-23.
The second layout expression shows that Eddystone-UID is a service advertisement that has a 16-bit service UUID of 0xfeaa which is followed by a matching byte code of 0x00. It has two identifiers, the first known as the "namespace identifier" from byte offsets 4-13. The second identifier is known as the "instance identifier" from byte offsets 14-19.
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
ArrayList<BeaconParser> beaconParsers = new ArrayList<BeaconParser>();
final String ALTBEACON_LAYOUT = "m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25";
final String EDDYSTONE_UID_LAYOUT = "s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19";
beaconParsers.add(new BeaconParser().setBeaconLayout(EDDYSTONE_UID_LAYOUT));
beaconParsers.add(new BeaconParser().setBeaconLayout(ALTBEACON_LAYOUT));
Beacon beacon = null;
for (BeaconParser parser : beaconParsers) {
beacon = parser.fromScanData(scanRecord,
rssi, device);
if (beacon != null) {
if (beacon.getServiceUuid() == 0xfeaa) {
// This is Eddystone, which uses a service Uuid of 0xfeaa
Identifier eddystoneNamespaceId = beacon.getId1();
Identifier eddystoneInstanceId = beacon.getId2();
}
else {
// This is another type of beacon like AltBeacon or iBeacon
Identifier uuid = beacon.getId1();
Identifier major = beacon.getId2();
Identifier minor = beacon.getId3();
}
}
}
The open source Android Beacon Library handles all the details for you about variable length PDUs that can slightly alter the byte offsets within the scan response. You can see the source code of how its BeaconParser works here.
If you want to roll your own completely from scratch, the easiest way is to simply loop through the bytes looking for the pattern you want to find, then parse out the bytes of interest based on the offsets. (The Android Beacon Library uses a more robust and sophisticated method of actually parsing individual PDUs.) But the looping technique still works.
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
for (int startByte = 0; startByte < scanRecord.length; startByte++) {
if (scanRecord.length-startByte > 19) { // need at least 19 bytes for Eddystone-UID
// Check that this has the right pattern needed for this to be Eddystone-UID
if (scanRecord[startByte+0] == (byte)0xaa && scanRecord[startByte+1] == (byte)0xfe &&
scanRecord[startByte+2] == (byte)0x00) {
// This is an Eddystone-UID beacon.
byte[] namespaceIdentifierBytes = Arrays.copyOfRange(scanRecord, startByte+4, startByte+13);
byte[] instanceIdentifierBytes = Arrays.copyOfRange(scanRecord, startByte+14, startByte+19);
// TODO: do something with the above identifiers here
}
}
if (scanRecord.length-startByte > 24) { // need at least 24 bytes for AltBeacon
// Check that this has the right pattern needed for this to be AltBeacon
// iBeacon has a slightly different layout. Do a Google search to find it.
if (scanRecord[startByte+2] == (byte)0xbe && scanRecord[startByte+3] == (byte)0xac) {
// This is an AltBeacon
byte[] uuidBytes = Arrays.copyOfRange(scanRecord, startByte+4, startByte+19);
byte[] majorBytes = Arrays.copyOfRange(scanRecord, startByte+20, startByte+21);
byte[] minorBytes = Arrays.copyOfRange(scanRecord, startByte+22, startByte+23);
// TODO: do something with the above identifiers here
}
}
}
}
Again, the code above shows how to parse open source AltBeacons (for intellectual property reasons). To parse iBeacons, you'll need to do a Google search for its BeaconLayout, and make small adjustments to the code above.
Beacons can advertise both iBeacon and Eddystone. In fact, a project I'm involved in is using such beacons.
It's easy to extract iBeacon and Eddystone from packets if you use nv-bluetooth library. Like this:
// onLeScan() method of BluetoothAdapter.LeScanCallback interface.
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
{
// Parse the payload of the advertisement packet.
List<ADStructure> structures =
ADPayloadParser.getInstance().parse(scanRecord);
// For each AD structure contained in the advertising packet.
for (ADStructure structure : structures)
{
// If the ADStructure instance can be cast to IBeacon.
if (structure instanceof IBeacon)
{
// An iBeacon was found.
IBeacon iBeacon = (IBeacon)structure;
......
}
// If the ADStructure instance can be cast to Eddystone.
else if (structure instanceof Eddystone)
{
if (structure instanceof EddystoneUID)
{
// Eddystone UID
EddystoneUID es = (EddystoneUID)structure;
......
}
else if (structure instanceof EddystoneURL)
{
// Eddystone URL
EddystoneURL es = (EddystoneURL)structure;
......
}
else if (structure instanceof EddystoneTLM)
{
// Eddystone TLM
EddystoneTLM es = (EddystoneTLM)structure;
......
}
}
......
}
......
}
Note that android.bluetooth.le.ScanRecord
is one of the worst APIs in Android, so it is better to parse packets manually than to use ScanRecord
.
Please read thru the documentation about ScanRecord
https://developer.android.com/reference/android/bluetooth/le/ScanRecord.html. The getManufacturerSpecificData()
method may be something you are looking for.
来源:https://stackoverflow.com/questions/32793395/programmatically-how-to-identify-if-a-beacon-belongs-to-eddystone-or-ibeacon