在蓝牙 LE 连接中使用 ScanFilter 时没有回调

no callback when using ScanFilter in Bluetooth LE Connection

我想设置一个 ScanFilter 以根据其广告数据扫描 BLE 设备:名称、服务 UUID 及其制造数据的第一个字符。问题是:当我使用 ScanFilter 时,没有回调,这意味着没有调用 .onScanResult()。

设备名称是正确的,因为我已经通过在回调中找到设备进行了测试。当我不使用 ScanFilter 时,有回调,可以通过名称找到设备。并且可以成功连接到设备。

  1. 这是我有问题的代码。你能帮忙指出它的问题吗?

  2. 另外,我还有一个问题:.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));
    
        }
    

我遇到了这些错误:

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)

这是正确的 运行 代码。我可以用它来找到设备。但我想通过制造数据过滤设备。扫描时是否必须设置过滤器?这里有没有根据制造数据过滤设备的方法?

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();
            }
        }

确实是设备发送的数据无效。格式基本上是一个序列:

  • 数据长度(包括字段类型)
  • 字段类型
  • 数据

所以如果我们拆分数据,我们得到:

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

很遗憾,制造商数据无效。它至少需要 3 个字节(类型字段为 4 个字节),并以制造商 ID 的 2 个字节开头。所以在这一点上,Android 放弃解析并创建一个 ScanResult,根本没有数据。

所以你不会对过滤器有任何好感。相反,您需要自己过滤它。

终于成功了!这是我的解决方案:

由于制造商数据无效(见上面@code的解释),我们不能直接使用Android-provided函数-scanRecord.getManufacturerSpecificData()。相反,我使用以下代码手动解析广告:

@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();
    }
}