Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to filter on manufacturer data when using BluetoothLeScanner for android?

I am working with my own BLE-devices. When listening after these devices I would like to use ScanFilter, so I only get the devices I am interested in. My solution right now is to filter inside the callback but it would be better if this filtration could happen earlier and according to the specification it should be possible. I am trying to filter on the manufacturer specific data but I can not get it to work. This is my code:

BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
ScanFilter filter = getScanFilter();
List<ScanFilter> scanFilters = new ArrayList<>();
scanFilters.add(filter);
ScanSettings scanSettings = getScanSettings();
bleScanner.startScan(scanFilters, scanSettings, scanCallback);

This is the functions that creates the filter and settings:

private ScanSetting getScanSettings(){
    ScanSettings.Builder builder = new ScanSettings.Builder();
    builder.setReportDelay(0);
    builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
    return builder.build();
}

private ScanFilter getScanFilter(){
    ScanFilter.Builder builder = new ScanFilter.Builder();
    ByteBuffer manData = ByteBuffer.allocate(6); //The sensors only sends 6 bytes right now
    ByteBuffer manMask = ByteBuffer.allocate(6);
    manData.put(0, (byte)0x50);
    manData.put(1, (byte)0x41);
    manData.put(2, (byte)0x43);
    manData.put(3, (byte)0x4b);
    manData.put(4, (byte)0x45);
    manData.put(5, (byte)0x54);
    for(int i = 0; i < 6; i++){
        manMask.put((byte)0x01);
    }
    builder.setManufacturerData(20545, manData.array(), manMask.array()); //Is this id correct?
    return builder.build();
}

If I don't use any filters or settings with only this function:

bluetoothLeScanner.startScan(scanCallback);

I get my BLE-devices, so I know they are broadcasting correctly. I can also print the manufacturer specific data and can see that it is the 6 same bytes that I use in my filter. I am unsure if the id (the first parameter in the .setManufacturerData function) is correct because the only info about this I could find was from the following text from the android developer page for the ScanFilter.Builder:

"Note the first two bytes of the manufacturer Data is the manufacturerId"

When I use this code and try to scan after the devices I get nothing. What am i missing here?

like image 888
FewWords Avatar asked Oct 06 '16 07:10

FewWords


2 Answers

I manage to get it to work. It was the manufacturerId that was not correct. It was not 20545 which I got from the first two bytes. Instead I found out that I could get this id from the ScanResult (when I used no filter) by doing the following:

ScanRecord scanRecord = scanResult.getScanRecord();
SparseArray<byte[]> manufacturerData = scanRecord.getManufacturerSpecificData();
for(int i = 0; i < manufacturerData .size(); i++){
    int manufacturerId = manufacturerData.keyAt(i);
}

By doing this I got the correct manufacturerId that I then could place in the bleScanner.startScan function.

like image 51
FewWords Avatar answered Oct 16 '22 03:10

FewWords


SIG defines "manufacturer specific data" to include a manufacturer ID as the first 2 bytes, followed by any additional data. Android then takes care of determining the manufacturer ID by extracting the first 2 bytes, leaving the rest. If your advertising packet does not use a manufacturer ID (not recommended, but was my situation), Android's "manufacturer data" will actually be missing 2 of your bytes.

In my case, firmware engineer required the full allotment of bytes for the data. Because of this I ended up with incorrectly parsed manufacturer data on the Android side.

Solution was to separate the first two bytes of my filter as an integer for the ID. The remaining bytes went to the data. Something like this:

fun createAndroidManufacturerDataFilter(query: String): ScanFilter {
    return ScanFilter.Builder().setManufacturerData(
        convertASCIItoByteArrayToInt(query.substring(0, 2)),
        convertASCIItoByteArray(query.substring(2))
    ).build()
}
like image 35
August W. Gruneisen Avatar answered Oct 16 '22 03:10

August W. Gruneisen