Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read advertisement packet in Android

I'm working on a BLE sensor that is advertising manufacturer specific data. Is there any sample code that demonstrates how to receive an advertisement packet in Android and parse its payload?

like image 275
Gilson Avatar asked Jun 02 '14 21:06

Gilson


2 Answers

This is what I was looking for:

The BLE scan API BluetoothAdapter.startLeScan(ScanCallback) requires a call back function for the scan results. the method needs to look like the following:

    private BluetoothAdapter.LeScanCallback ScanCallback =
        new BluetoothAdapter.LeScanCallback()onLeScan(final BluetoothDevice device, 
                                                            int rssi, 
                                                      final byte[] scanRecord)
    {...}

And the scanRecord variable is a byte array which contains the Advertisement packet payload.

Per the BLE specification the structure of the payload is very simple as follows:

The packets can be up to 47 bytes in length and consist of:

  • 1 byte preamble
  • 4 byte access address
  • 2-39 bytes advertising channelPDU
  • 3 bytes CRC

For advertisement communication channels, the access address is always 0x8E89BED6.

The PDU in turn has its own header (2 bytes: size of the payload and its type – whether the device supports connections, etc.) and the actual payload (up to 37 bytes).

Finally, the first 6 bytes of the payload are the MAC address of the device, and the actual information can have up to 31 bytes.

the format of the actual information is as follows:

first byte is length of the data and second byte is type followed by the data.

This is a clever way to allow any application to skip entire data records if they don't care about the contents.

Here is the sample code to determine the contents of the Advertisement packet:

parseAdvertisementPacket(final byte[] scanRecord) {

    byte[] advertisedData = Arrays.copyOf(scanRecord, scanRecord.length);

    int offset = 0;
    while (offset < (advertisedData.length - 2)) {
        int len = advertisedData[offset++];
        if (len == 0)
            break;

        int type = advertisedData[offset++];
        switch (type) {
            case 0x02: // Partial list of 16-bit UUIDs
            case 0x03: // Complete list of 16-bit UUIDs
                while (len > 1) {
                    int uuid16 = advertisedData[offset++] & 0xFF;
                    uuid16 |= (advertisedData[offset++] << 8);
                    len -= 2;
                    uuids.add(UUID.fromString(String.format(
                            "%08x-0000-1000-8000-00805f9b34fb", uuid16)));
                }
                break;
            case 0x06:// Partial list of 128-bit UUIDs
            case 0x07:// Complete list of 128-bit UUIDs
                // Loop through the advertised 128-bit UUID's.
                while (len >= 16) {
                    try {
                        // Wrap the advertised bits and order them.
                        ByteBuffer buffer = ByteBuffer.wrap(advertisedData,
                                offset++, 16).order(ByteOrder.LITTLE_ENDIAN);
                        long mostSignificantBit = buffer.getLong();
                        long leastSignificantBit = buffer.getLong();
                        uuids.add(new UUID(leastSignificantBit,
                                mostSignificantBit));
                    } catch (IndexOutOfBoundsException e) {
                        // Defensive programming.
                        Log.e("BlueToothDeviceFilter.parseUUID", e.toString());
                        continue;
                    } finally {
                        // Move the offset to read the next uuid.
                        offset += 15;
                        len -= 16;
                    }
                }
                break;
            case 0xFF:  // Manufacturer Specific Data
                Log.d(TAG, "Manufacturer Specific Data size:" + len +" bytes" );
                while (len > 1) {
                    if(i < 32) {
                        MfgData[i++] = advertisedData[offset++];
                    }
                    len -= 1;
                }
                Log.d(TAG, "Manufacturer Specific Data saved." + MfgData.toString());
                break;
            default:
                offset += (len - 1);
                break;
        }
    }

thanks to

how-ibeacons-work

bluetooth org specs

mass for putting me on the right direction!

like image 167
Gilson Avatar answered Nov 13 '22 13:11

Gilson


ADPayloadParser in nv-bluetooth parses the payload of an advertising packet and returns a list of AD structures. The AD structure format is described in "11 ADVERTISING AND SCAN RESPONSE DATA FORMAT" of "Bluetooth Core Specification 4.2".

The following code snippet is an implementation example of onLeScan method.

public void onLeScan(
    BluetoothDevice device, int rssi, byte[] scanRecord)
{
    // Parse the payload of the advertising packet.
    List<ADStructure> structures =
        ADPayloadParser.getInstance().parse(scanRecord);

    // For each AD structure contained in the advertising packet.
    for (ADStructure structure : structures)
    {
        if (structure instanceof IBeacon)
        {
            // iBeacon packet was found.
            handleIBeacon((IBeacon)structure);
        }
        ......
    }
}

You can register a parser of your own for your manufacturer-specific format into ADPayloadParser. Refer to the following links for more information.

Blog: http://darutk-oboegaki.blogspot.jp/2015/03/ibeacon-as-kind-of-ad-structures.html

GitHub: https://github.com/TakahikoKawasaki/nv-bluetooth

JavaDoc: http://takahikokawasaki.github.io/nv-bluetooth/

Maven: http://search.maven.org/#search|ga|1|a%3A%22nv-bluetooth%22

like image 34
Takahiko Kawasaki Avatar answered Nov 13 '22 11:11

Takahiko Kawasaki