Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scanning for BLE peripherals with a scan filter based on advertised service UUID

I have a custom BLE peripheral that advertises data like this:

enter image description here

In other words, my BLE peripheral advertises a service UUID associated with a unique identifier in advertised service data, but it does not add that service UUID to advertised service list because if I do that, I don't have room in the BLE frame to add battery level when I need to.

On iOS, I'm able to scan with a filter based on service UUID and see my peripheral. But on Android, with the following scan filter, I don't see my peripheral:

val scanSettingsBuilder = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setReportDelay(0L)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    scanSettingsBuilder
                .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
                .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
}
bluetoothAdapter?.bluetoothLeScanner?.startScan(
    arrayListOf(ScanFilter.Builder().setServiceUuid(ParcelUuid(UUID.fromString("00004865-726f-6e54-7261-636b2d475053"))).build()),
    scanSettingsBuilder.build(),
    leScanCallback
)

Does anyone have more details about how the serviceUUID-based scan filter works, and what are the conditions a peripheral must meet in order to be accepted by the filter?

like image 552
Sebastien Avatar asked Nov 06 '19 13:11

Sebastien


People also ask

How do I scan a BLE device?

To find BLE devices, you use the startScan() method. This method takes a ScanCallback as a parameter. You must implement this callback, because that is how scan results are returned.

What is BLE scanning?

BLE scanning. An Android device can target and scan for specific Bluetooth devices more efficiently when using BLE. BLE APIs let app developers create filters for finding devices with less involvement from the host controller.


Video Answer


1 Answers

I figured out how to make it work... sort of. The problem is that my filter was on serviceUuid, which I assume looks at peripherals that advertise the UUID in the advertisedServices collection. My peripheral only advertises the UUID as a key in its serviceData associative array, so I switched to the serviceData filter as follows, and now I can find my peripheral:

AsyncTask.execute {
    val scanFilters = Settings.scannedBleServices.values.map {
        ScanFilter.Builder().setServiceData(it, null).build()
    }
    val scanSettingsBuilder = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setReportDelay(0L)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        scanSettingsBuilder
                .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
                .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
    }
    bluetoothAdapter?.bluetoothLeScanner?.startScan(
            scanFilters,
            scanSettingsBuilder.build(),
            leScanCallback
    )
}

The problem is that now the filter is too permissive, as I get a callback for every peripheral around, even those without any serviceData, just as if I had specified no filter at all. Maybe it's because I passed null as a second parameter to setServiceData in the filter because I didn't know what else to add there. And the documentation is not exactly helpful.

My guess is that it's enough for the scan to work in the background (I haven't tried yet), but it would make more sense if I could restrict the number of times the callback is called and I didn't have to filter by myself.

like image 196
Sebastien Avatar answered Oct 01 '22 16:10

Sebastien