I have a custom BLE peripheral that advertises data like this:
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?
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.