Reputation: 397
I would like to set a ScanFilter to scan a BLE device based on its advertisement data: name, services UUID, and the first character of its manufacture data. The problem is: when I use a ScanFilter, there is no callback, which means the .onScanResult() is not called.
The device name is correct because I have tested by finding the device in callback. When I don't use a ScanFilter, there is callback and the device can be found by its name. And it can successfully connect to the device.
Here is my problematic code. Can you help with indicating its problem?
Also, I have another question: how to input the three parameters for .setManufacturerData()?
protected void scan(){
List<ScanFilter> filters = new ArrayList<>();
ScanFilter filterName = new ScanFilter.Builder().setDeviceName(this.mDeviceName).build();
filters.add(filterName);
//ScanFilter filterUUID = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("00001814-0000-1000-8000-00805F9B34FB")).build();
//filters.add(filterUUID);
//ScanFilter filterManu = new ScanFilter.Builder().setManufacturerData().build();
//filters.add(filterManu);
ScanSettings settings = (new Builder()).setScanMode(2).build();
this.mBluetoothLeScanner.startScan(filters, settings, this);
this.mScanning = true;
}
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice _device = result.getDevice();
Log.i("onScanResult", " callback function is being called");
// find device by name
if (_device != null && _device.getName() != null && _device.getName().matches(this.mDeviceName) ) {
this.stopScan();
this.mDevice = _device;
Log.i(TAG, "mac address : " + this.mDevice.getAddress() + ", name : " + this.mDevice.getName());
this.mDeviceFoundLatch.countDown();
}
long m = mDeviceFoundLatch.getCount();
Log.i("onResultscallbackLatch", String.valueOf(m));
}
I got these errors:
E/ScanRecord: unable to parse scan record: [2, 1, 6, 3, 2, 20, 24, 2, -1, 82, 20, 9, 83, 116, 114, 105, 100, 97, 108, 121, 122, 101, 114, 32, 73, 78, 83, 73, 71, 72, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
E/BluetoothServiceJni: An exception was thrown by callback 'btgattc_scan_result_cb'.
E/BluetoothServiceJni: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.Map.get(java.lang.Object)' on a null object reference
at android.bluetooth.le.ScanRecord.getServiceData(ScanRecord.java:121)
at android.bluetooth.le.ScanFilter.matches(ScanFilter.java:305)
at com.android.bluetooth.gatt.GattService.matchesFilters(GattService.java:659)
at com.android.bluetooth.gatt.GattService.onScanResult(GattService.java:611)
Here is the correctly running code. I can use it to find the device. But I would like to filter the device by its manufacture data. Do I have to set the filter when scanning? Is there anyway to filter the device by its manufacture data here?
protected void scan(){
ScanSettings settings = (new Builder()).setScanMode(2).build();
this.mBluetoothLeScanner.startScan(null, settings, this);
this.mScanning = true;
}
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice _device = result.getDevice();
if (_device != null && _device.getName() != null && _device.getName().matches(this.mDeviceName) ) {
this.stopScan();
this.mDevice = _device;
this.mDeviceFoundLatch.countDown();
}
}
Upvotes: 2
Views: 1702
Reputation: 397
Finally get it worked! Here is my solution:
Due to the invalid manufacturer data (see the explanation by @code above), we cannot directly use Android-provided function - scanRecord.getManufacturerSpecificData(). Instead, I manually parse the advertisement by using the following codes:
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice _device = result.getDevice();
ScanRecord scanRecord = result.getScanRecord();
byte[] byte_ScanRocord = scanRecord.getBytes();
int isLeft = byte_ScanRocord[9];//the 9th byte stores the information that can be used to filter the device
String byte_ScanRocord_str = Arrays.toString(byte_ScanRocord);
Log.i("onScanRecordAdv",byte_ScanRocord_str);
Log.i("onScanRecordisLeft",String.valueOf(isLeft));
if (_device != null && _device.getName() != null && _device.getName().matches(this.mDeviceName) && isLeft == 76 ) {
this.stopScan();
this.mDevice = _device;
Log.i("InsoleScanner", "mac address : " + this.mDevice.getAddress() + ", name : " + this.mDevice.getName());
this.mDeviceFoundLatch.countDown();
}
}
Upvotes: 1
Reputation: 78835
Indeed the data sent by the device is invalid. The format is basically a sequence of:
So if we split the data, we get:
2: length 2 bytes
1: field type "flags"
6: data (flags)
3: length 3 bytes
2: field type "partial 16 bit UUID"
20, 24: data (UUID bytes 0x14 0x18, that will be converted into the full UUID 00001814-0000-1000-8000-00805F9B34FB)
2: length 2 bytes
-1 (or 0xff): field type "manufacturer data"
82: data (manufacturer data)
20: length 20 bytes
9: field type "local name complete"
83, 116, 114, 105, 100, 97, 108, 121, 122, 101, 114, 32, 73, 78, 83, 73, 71, 72, 84: data (local name, decoded as "Stridalyzer INSIGHT")
0, 0, ...: ignored data
Unfortunately, the manufacturer data is invalid. It would need to be at least 3 bytes (4 with the type field) and start with 2 bytes for the manufacturer ID. So at this point, Android discard the parsing and creates a ScanResult with no data at all.
So you won't have any luck with filters. Instead, you will need to filter it yourself.
Upvotes: 2