简体   繁体   English

Android:低功耗蓝牙扫描仪接收空数据

[英]Android: Bluetooth Low Energy scanner receives null data

This is the advertiser (notice data passed as AdvertiseData type)这是广告商(通知data作为AdvertiseData类型传递)

  private void advertise() {
    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    BluetoothLeAdvertiser advertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
    AdvertiseSettings settings = new AdvertiseSettings.Builder()
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
            .setConnectable(false)
            .build();
    ParcelUuid pUuid = new ParcelUuid(UUID.fromString("cf2c82b6-6a06-403d-b7e6-13934e602664"));
    AdvertiseData data = new AdvertiseData.Builder()
            //.setIncludeDeviceName(true)
            .addServiceUuid(pUuid)
            .addServiceData(pUuid, "123456".getBytes(Charset.forName("UTF-8")))
            .build();
    AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            Log.i(tag, "Advertising onStartSuccess");
            super.onStartSuccess(settingsInEffect);
        }

        @Override
        public void onStartFailure(int errorCode) {
            Log.e(tag, "Advertising onStartFailure: " + errorCode);
            super.onStartFailure(errorCode);
        }
    };
    advertiser.startAdvertising(settings, data, advertiseCallback);
}

It starts succesfully.它成功启动。

This is the scanner这是扫描仪

 private void discover() {
    ScanSettings settings = new ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
            .build();
    mBluetoothLeScanner.startScan(null, settings, mScanCallback);
}

private ScanCallback mScanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        super.onScanResult(callbackType, result);
        Log.i(tag, "Discovery onScanResult");
        if (result == null) {
            Log.i(tag, "no result");
            return;
        }
        ScanRecord scanRecord = result.getScanRecord();
        List<ParcelUuid> list = scanRecord != null ? scanRecord.getServiceUuids() : null;
        if (list != null) {
            Log.i(tag, scanRecord.toString());
            for (ParcelUuid uuid : list) {
                byte[] data = scanRecord.getServiceData(uuid);
            }
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        super.onBatchScanResults(results);
        Log.i(tag, "Discovery onBatchScanResults");
    }

    @Override
    public void onScanFailed(int errorCode) {
        super.onScanFailed(errorCode);
        Log.e(tag, "Discovery onScanFailed: " + errorCode);
    }
};

In the callback onScarnResult I log the scan record toString() that produces this output在回调onScarnResult我记录了产生此输出的扫描记录toString()

 ScanRecord [mAdvertiseFlags=2, 
         mServiceUuids=[cf2c82b6-6a06-403d-b7e6-13934e602664],
         mManufacturerSpecificData={}, 
         mServiceData={000082b6-0000-1000-8000-00805f9b34fb=[49, 50, 51, 52, 53, 54]}, 
         mTxPowerLevel=-2147483648, mDeviceName=null]

The uuid matches, unfortunately the result of uuid 匹配,不幸的是结果

  byte[] data = scanRecord.getServiceData(uuid) 

is null .null I noticed that the toString output had the ASCII codes of the advertised data characters "123456", that are 49,50,51,52,53,54我注意到toString输出有广告数据字符“123456”的 ASCII 码,即 49,50,51,52,53,54

 mServiceData={000082b6-0000-1000-8000-00805f9b34fb=[49, 50, 51, 52, 53, 54]}

I'd like to receive the right advertised data, am I doing something wrong?我想收到正确的广告数据,我做错了什么吗?

EDIT: the manifest has permissions for bluetooth, bt admin and location.编辑:清单具有蓝牙、bt 管理员和位置的权限。 The third one launches a request at runtime in Android 6第三个在 Android 6 中在运行时发起请求

EDIT: by printing the whole scanRecord you get this output编辑:通过打印整个 scanRecord 你得到这个输出

ScanRecord [mAdvertiseFlags=-1, mServiceUuids=[cf2c82b6-6a06-403d-b7e6-13934e602664], mManufacturerSpecificData={}, mServiceData={000082b6-0000-1000-8000-00805f9b34fb=[49, 50, 51, 52, 53, 54]}, mTxPowerLevel=-2147483648, mDeviceName=null] ScanRecord [mAdvertiseFlags=-1, mServiceUuids=[cf2c82b6-6a06-403d-b7e6-13934e602664], mManufacturerSpecificData={}, mServiceData={000082b6-0000-1005-05,5,5,5,5,5,4,500-80,50,54,805-84 54]},mTxPowerLevel=-2147483648,mDeviceName=null]

Basically you can't use the uuid decided by the advertiser, which is in mServiceUuids array, because the key associated to mServiceData is another one.基本上你不能使用广告商决定的 uuid,它在 mServiceUuids 数组中,因为与 mServiceData 关联的键是另一个。 So I changed the code in this way, to navigate the data map and get the value (please, see the two if-blocks)所以我用这种方式改变了代码,以导航数据映射并获取值(请参阅两个 if 块)

   public void onBatchScanResults(List<ScanResult> results) {
        super.onBatchScanResults(results);
        for (ScanResult result : results) {
            ScanRecord scanRecord = result.getScanRecord();
            List<ParcelUuid> uuids = scanRecord.getServiceUuids();
            Map<ParcelUuid, byte[]> map = scanRecord.getServiceData();
            if (uuids != null) {
                for (ParcelUuid uuid : uuids) {
                    byte[] data = scanRecord.getServiceData(uuid);
                    Log.i(tag, uuid + " -> " + data + " contain " + map.containsKey(uuid));
                }
            }

            if (map != null) {
                Set<Map.Entry<ParcelUuid, byte[]>> set = map.entrySet();
                Iterator<Map.Entry<ParcelUuid, byte[]>> iterator = set.iterator();
                while (iterator.hasNext()) {
                    Log.i(tag, new String(iterator.next().getValue()));
                }
            }
        }
    }

In fact, the line事实上,线

 map.containsKey(uuid)

returns false because the uuid of the advertiser is not used by the data map.返回 false,因为数据映射未使用广告商的 uuid。

I had to navigate the map to find the value (2nd if-block), but I don't have any means to know if that's the value I'm interested in. Either way I can't get the value if the system put another key that I can't know while running the scanner's code on the receiver app.我必须导航地图才能找到值(第二个 if 块),但我没有任何方法知道这是否是我感兴趣的值。 无论哪种方式,如果系统放置,我都无法获得该值在接收器应用程序上运行扫描仪代码时我不知道的另一个密钥。

How can I handle this problem on the receiver?如何在接收器上处理此问题? I'd like to use the data field, but the string key to get them is not known a priori and decided by the system.我想使用数据字段,但获取它们的字符串键不是先验已知的,而是由系统决定的。

I know it's an old thread, but since I had the same issue and found a solution...我知道这是一个旧线程,但是由于我遇到了同样的问题并找到了解决方案......

UUIDs to be used with .addServiceUuid() and .addServiceData() in advertiser are different objects.在广告商中与.addServiceUuid().addServiceData()一起使用的 UUID 是不同的对象。 The first one identifies the service, and is a 128-bits UUID.第一个标识服务,是一个 128 位的 UUID。 The second one identifies the serviceData within that service, and is expected to be a 16-bits UUID.第二个标识该服务中的 serviceData,预计为 16 位 UUID。

This is why the scanner receives这就是为什么扫描仪会收到

0000**82b6**-0000-1000-8000-00805f9b34fb

Note that 16 bits 0x82b6 are common with the UUID passed to .addServiceData:请注意,16 位0x82b6与传递给 .addServiceData 的 UUID 相同:

cf2c**82b6**-6a06-403d-b7e6-13934e602664

A 16-bits UUID is converted to a 128 bits by left-shifting of 96 bits and adding a Bluetooth constant UUID.通过左移96位并添加蓝牙常量UUID,将16位UUID转换为128位。

The solution is just to use an UUID of this form [ 0000xxxx-0000-1000-8000-00805f9b34fb ] to identify the serviceData in both advertiser and scanner.解决方案只是使用这种形式的 UUID [ 0000xxxx-0000-1000-8000-00805f9b34fb ] 来识别广告商和扫描仪中的 serviceData。 You can keep your orignal 128-bits UUID to identify the service.您可以保留原始 128 位 UUID 以识别服务。

I found the error.我发现了错误。 Change this line:改变这一行:

.addServiceData(pUuid, "123456".getBytes(Charset.forName("UTF-8")))

to:到:

.addServiceData(pUuid, "123456".getBytes()

That's it.就是这样。 I used the same example code as you did.我使用了与您相同的示例代码。

Find a better example here:在这里找到一个更好的例子:
https://github.com/googlesamples/android-BluetoothAdvertisements https://github.com/googlesamples/android-BluetoothAdvertisements

Try adding the ScanFilter and retreive the results尝试添加 ScanFilter 并检索结果

 private void discover() 
{
    List<ScanFilter> filters = new ArrayList<ScanFilter>();

    ScanFilter filter = new ScanFilter.Builder()
            .setServiceUuid( new ParcelUuid(UUID.fromString( 
   getString(R.string.ble_uuid ) ) ) )
            .build();
    filters.add( filter );

Link for source code: https://github.com/tutsplus/Android-BluetoohLEAdvertising/blob/master/app/src/main/java/com/tutsplus/bleadvertising/MainActivity.java源代码链接: https : //github.com/tutsplus/Android-BluetoohLEAdvertising/blob/master/app/src/main/java/com/tutsplus/bleadvertising/MainActivity.java

@guiv gave the right answer https://stackoverflow.com/a/54726135/1869562 here @guiv 在这里给出了正确答案https://stackoverflow.com/a/54726135/1869562

Let me add by putting it in a more concise way.让我以更简洁的方式补充一下。

addServiceData expects 16-bit UUIDS while addServiceUUIDs can accept either 16-bit or 128. Rather than raise an exception addServiceData automatically adjusts 128-bit UUIDs to 16-bit, hence the difference in input parameter of addServiceData (in Advertiser) and keys of the return value of getServiceData() (in Scanner). addServiceData 需要 16 位 UUIDS,而 addServiceUUIDs 可以接受 16 位或 128。而不是引发异常 addServiceData 自动将 128 位 UUID 调整为 16 位,因此 addServiceData(在广告商中)的输入参数和getServiceData() 的返回值(在 Scanner 中)。

Solution - Ensure you use 16-bit UUIDs in your advertiser解决方案 - 确保您在广告客户中使用 16 位 UUID

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM