Scanning BLE on Android
BLE Packets and Beacons (27 Sep 2016)
Getting Started with Scanning
Scanning for devices requires the BLUETOOTH_ADMIN
and ACCESS_COURSE_LOCATION
permissions.
Once you have these permissions it’s pretty easy to start scanning:
// get ble scanner
BluetoothAdapter btAdapter = BluetoothAdapter.getAdapter();
BluetoothLeScanner bleScanner = btAdapter.getBluetoothLeScanner();
// start the scan
ScanCallback scanCallback = /* todo */;
bleScanner.startScan(scanCallback);
The ScanCallback receives incoming advertising packets in the onScanResult
method.
Understanding the Advertising Packet Structure
The broadcast data section of the packet is 31 bytes long and consists of several data sections.
Broadcast Data | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Flag Data | Data #1 | Data #2 | ... | |||||||||||||||||||||||||||
0 | 8 | 16 | 24 | 30 |
Each data segment is layed out according to the following format:
Data Segment | ||||
---|---|---|---|---|
Length (1 byte) | Type (1 byte) | Content | ||
0 | 1 | 2 | ... | length |
Note: length does not include the one byte it uses, only the bytes after
The type of the data segment determines it’s content.
A table of types can be found on the bluetooth website.
I will describe type 0xFF - Manufacturer Data
in the following sections.
Some of the types like Device Local Name
are already parsed by the Android API.
Note: 16 bit numbers tend to be represented in little endian
There are two separate advertising packets that are sent: the normal broadcast packet that is sent all the time and the scan response packet that is sent when the device is scanned. Each packet has 31 bytes to advertise data.
Implementing ScanCallback
The ScanRecord contains the data described above. The Android API automatically concatentates the scan result packets data to the end of the first packet.
class BleCallback extends ScanCallback {
// called when a beacon is scanned
public void onScanResult(int callbackType, ScanResult result) {
// the broadcast data from both packets
ScanRecord record = result.getScanRecord();
// the ScanRecord class provides some methods to
// access common data.
// for example: (0x004c is Apple's designated identifier)
byte[] appleData = record.getManufacturerSpecificData(0x004c);
if (appleData != null) {
// parse the data
}
}
}
Example: Parsing iBeacons
Once we have the data we can it! The following will describe parsing the iBeacon advertising data.
iBeacon Data Segment | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Length | Type | Company | Data Type | UUID | Major | Minor | Tx Power | |||||||||||||||||||
26 | 0xFF | 0x4c00 | 0x0215 | {uuid bytes} | {major number} | {minor number} | {tx power} | |||||||||||||||||||
0 | 1 | 2 | 4 | 6 | 22 | 24 | 26 |
Source: kontakt.io
Once this format is understood extracting the data is easy!
// note: getManufacturerData() returns a byte array starting
// at index 4 in the previous table.
// extract UUID
ByteBuffer bb = ByteBuffer.wrap(appleData, 2, 16);
UUID uuid = new UUID(bb.getLong(), bb.getLong());
// extracting the other data is also easy and left as
// an exercise to the reader.
// note: major and minor are in big endian
Conclusion
Scanning for BLE devices and parsing advertising data on Android is quite simple. I wish you luck on developing your applications!