Tao
Tao

Reputation: 397

no callback when using ScanFilter in Bluetooth LE Connection

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.

  1. Here is my problematic code. Can you help with indicating its problem?

  2. 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

Answers (2)

Tao
Tao

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

Codo
Codo

Reputation: 78835

Indeed the data sent by the device is invalid. The format is basically a sequence of:

  • data length (incl. field type)
  • field type
  • data

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

Related Questions