Programmatically, How to identify if a beacon belongs to Eddystone or iBeacon?

寵の児 提交于 2019-12-05 08:24:30

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!