简体   繁体   English

Android BLE客户端仅在onCharacteristicRead中返回600字节的数据

[英]Android BLE client only returns 600 byte of data in onCharacteristicRead

I've got a Bluetooth server that uses bleno and returns a list of available Wifi networks to the client. 我有一台使用bleno的蓝牙服务器,并向客户端返回可用的Wifi网络列表。 The code for readCharacteristic looks basically like this: readCharacteristic的代码看起来基本上是这样的:

class ReadCharacteristic extends bleno.Characteristic {

constructor(uuid, name, action) {
    super({
        uuid: uuid,
        properties: ["read"],
        value: null,
        descriptors: [
            new bleno.Descriptor({
                uuid: "2901",
                value: name
              })
        ]
    });
    this.actionFunction = action;
}

onReadRequest(offset, callback) {
    console.log("Offset: " + offset);

if(offset === 0) {
        const result = this.actionFunction();
    result.then(value => {
        this.actionFunctionResult = value;
            const data = new Buffer.from(value).slice(0,bleno.mtu);
            console.log("onReadRequest: " + data.toString('utf-8'));

            callback(this.RESULT_SUCCESS, data);
        }, err => {
            console.log("onReadRequest error: " + err);
            callback(this.RESULT_UNLIKELY_ERROR);
        }).catch( err => {
            console.log("onReadRequest error: " + err);
            callback(this.RESULT_UNLIKELY_ERROR);
        });
}
else {
    let data = new Buffer.from(this.actionFunctionResult);
    if(offset > data.length) {
        callback(this.RESULT_INVALID_OFFSET, null);
    }
    data = data.slice(offset+1, offset+bleno.mtu);
    console.log(data.toString('utf-8'));
    callback(this.RESULT_SUCCESS, data);
}
}
}

(I've tried data = data.slice(offset+1, offset+bleno.mtu); and like this data = data.slice(offset+1); ) (我已经尝试过data = data.slice(offset+1, offset+bleno.mtu);并且像这样data = data.slice(offset+1);

The client is an Android app that reads this Characteristic. 客户端是一个读取此特​​征的Android应用程序。

The Android part for reading looks like this: 用于阅读的Android部分如下所示:

            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                                int newState) {
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    gatt.requestMtu(256);

                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    Log.i(TAG, "Disconnected from GATT server.");

                    mFancyShowCaseView.show();
                    gatt.close();
                    scanForBluetoothDevices();
                }
            }


            @Override
            public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
                if (status != BluetoothGatt.GATT_SUCCESS) {
                    Log.e(TAG, "Can't set mtu to: " + mtu);
                } else {
                    Log.i(TAG, "Connected to GATT server. MTU: " + mtu);
                    Log.i(TAG, "Attempting to start service discovery:" +
                            mWifiProvisioningService.discoverServices());
                }
            }


            @Override
            // New services discovered
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    Log.d(TAG, "ACTION_GATT_SERVICES_DISCOVERED");

                    BluetoothGattService wifiProvisioningService = gatt.getService(WIFI_PROVISIONING_SERVICE_UUID);
                    BluetoothGattCharacteristic currentConnectedWifiCharacteristic = wifiProvisioningService.getCharacteristic(WIFI_ID_UUID);
                    BluetoothGattCharacteristic availableWifiCharacteristic = wifiProvisioningService.getCharacteristic(WIFI_SCAN_UUID);

                    // Only read the first characteristic and add the 2nd one to a list as we have to wait
                    // for the read return before we read the 2nd one.
                    if (!gatt.readCharacteristic(currentConnectedWifiCharacteristic)) {
                        Log.e(TAG, "Error while reading current connected wifi name.");
                    }
                    readCharacteristics.add(availableWifiCharacteristic);

                } else {
                    Log.w(TAG, "onServicesDiscovered received: " + status);
                }
            }


            @Override
            // Result of a characteristic read operation
            public void onCharacteristicRead(BluetoothGatt gatt,
                                             BluetoothGattCharacteristic characteristic,
                                             int status) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    UUID characteristicUUID = characteristic.getUuid();
                    if (WIFI_ID_UUID.equals(characteristicUUID)) {
                        Log.d(TAG, "HEUREKA we found the current wifi name: " + new String(characteristic.getValue()));
                        final String currentWifiName = new String(characteristic.getValue());
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                ((TextView) findViewById(R.id.currentWifiTxt)).setText(currentWifiName);
                                findViewById(R.id.currentWifiTxtProgress).setVisibility(View.GONE);
                            }
                        });

                    } else if (WIFI_SCAN_UUID.equals(characteristicUUID)) {
                        Log.d(TAG, "HEUREKA we found the wifi list: " + new String(characteristic.getValue()));
                        List<String> wifiListArrayList = new ArrayList<>();

                        try {
                            JSONObject wifiListRoot = new JSONObject(characteristic.getStringValue(0));
                            JSONArray wifiListJson = wifiListRoot.getJSONArray("list");

                            for (int i = 0; i < wifiListJson.length(); i++) {
                                wifiListArrayList.add(wifiListJson.get(i).toString());
                            }

                        } catch (JSONException e) {
                            Log.e(TAG, e.toString());
                            return;
                        }
                        final String[] wifiList = new String[wifiListArrayList.size()];
                        wifiListArrayList.toArray(wifiList);

                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                ((ListView) findViewById(R.id.availableWifiList)).setAdapter(new ArrayAdapter<String>(mContext, R.layout.wifi_name_list_item, wifiList));
                                findViewById(R.id.currentWifiTxtProgress).setVisibility(View.GONE);
                            }
                        });
                    } else {
                        Log.i(TAG, "Unexpected Gatt vale: " + new String(characteristic.getValue()));
                    }

                    if (readCharacteristics.size() > 0) {
                        BluetoothGattCharacteristic readCharacteristic = readCharacteristics.get(0);
                        if (!gatt.readCharacteristic(readCharacteristic)) {
                            Log.e(TAG, "Error while writing descriptor for connected wifi");
                        }
                        readCharacteristics.remove(readCharacteristic);
                    }
                }
            }

The MTU is adjusted to 256 bytes. MTU调整为256字节。 Which I reflected on the server when reading the list. 我在阅读列表时反映在服务器上。 The call itself works fine and returns the list but if the list contains more then 600 bytes only 600 bytes are available on Android. 调用本身工作正常并返回列表,但如果列表包含超过600个字节,则Android上只有600个字节可用。 I'm somehow certain that the JS server sends all the data but for some reason the Android client only receives or caches 600 bytes which does not seem correct. 我在某种程度上确定JS服务器发送所有数据,但由于某种原因,Android客户端只接收或缓存600字节似乎不正确。

I've found this post: Android BLE - Peripheral | 我发现这篇文章: Android BLE - Peripheral | onCharacteristicRead return wrong value or part of it (but repeated) onCharacteristicRead返回错误的值或部分值(但重复)

and this: Android BLE - How is large characteristic value read in chunks (using an offset)? 这个: Android BLE - 如何在块中读取大的特征值(使用偏移量)?

But both didn't solve my issue. 但两者都没有解决我的问题。 I'm aware that I need to wait for one read to return before I start the next read and that I need to wait till MTU is written before I continue to read data. 我知道在开始下一次读取之前我需要等待一次读取才能返回,我需要等到MTU写入才继续读取数据。 To the best of my knowledge this is reflected in the source you see above. 据我所知,这反映在您上面看到的来源中。 I'm kind of lost here. 我有点迷失在这里。

Any idea is highly apprechiated. 任何想法都是高度关注的。

Thanks a lot 非常感谢

For anyone who comes across this post also wondering why Android seems to return only 600 bytes for long GATT characteristics like this question is asking about, it all comes down to how Bluedroid (Android's Bluetooth stack) implements their GATT Client and how its out of spec. 对于任何遇到这篇文章的人也想知道为什么Android似乎只返回600字节的长GATT特征就像这个问题一样,这一切都归结为Bluedroid(Android的蓝牙堆栈)如何实现他们的GATT客户端以及它的规格如何。 In my case, I was using an ESP32-based IoT device as my GATT Server and Android (SDK 24) for the GATT Client. 就我而言,我使用基于ESP32的物联网设备作为我的GATT服务器和Android(SDK 24)用于GATT客户端。

According the spec (Bluetooth Core 4.2; Vol 3, Part F: 3.2.9), the maximum size for a characteristic value (inherited from ATT's attribute value) is 512 bytes. 根据规范(蓝牙核心4.2;第3卷,F部分:3.2.9),特征值(继承自ATT的属性值)的最大大小为512字节。 However, for some reason, Bluedroid does not attempt to enforce this requirement, instead decided upon a maximum size of 600; 但是,出于某种原因,Bluedroid并未尝试强制执行此要求,而是决定最大尺寸为600; which can be seen if you dive into the Bluedroid source and find the macro GATT_MAX_ATTR_LEN which is set to 600 ( stack/include/gatt_api.h:125 ). 如果您深入了解Bluedroid源并找到设置为600的宏GATT_MAX_ATTR_LENstack/include/gatt_api.h:125 ),则可以看到这一点。 Since in my case (and yours it seems) I was implementing the read request response code, I did not see to enforce the 512 byte limit on reads for characteristics either. 因为在我的情况下(和你的一样)我正在实现读取请求响应代码,我没有看到对特性的读取强制执行512字节限制。

Now, its important to realize just how it seems Bluedroid reads characteristics and how that relates to both the MTU size, the maximum size of a read (should be 512, but is 600 for Bluedroid) and how to handle data longer than that maximum size. 现在,重要的是要实现Bluedroid看起来如何读取特征以及它与MTU大小,读取的最大大小(应该是512,但Bluedroid为600)以及如何处理超过最大大小的数据的关系。 The MTU size is the largest packet size on the ATT level you can use. MTU大小是您可以使用的ATT级别上最大的数据包大小。 So, for each call to BluetoothGatt.readCharacteristic , you may be sending one or more read requests to the server depending on if Bluedroid thinks the characteristic size exceeds the MTU size. 因此,对于每次对BluetoothGatt.readCharacteristic调用,您可能会向服务器发送一个或多个读取请求,具体取决于Bluedroid是否认为特征大小超过MTU大小。 On a low level, Bluedroid will first send a ATT Read Request ( 0x0a ) and if the packet is MTU bytes in length, it will follow up with a ATT Read Blob Request ( 0x0c ) with the offset set to the MTU size. 在低级别上,Bluedroid将首先发送ATT读取请求( 0x0a ),如果数据包的长度为MTU字节,则将跟随ATT读取Blob请求( 0x0c ),并将偏移量设置为MTU大小。 It will continue to send ATT Read Blob Requests until either the ATT Read Blob Response is less than MTU bytes in length or until the maximum characteristic size is reached (ie, 600 for Bluedroid). 它将继续发送ATT读取Blob请求,直到ATT读取Blob响应长度小于MTU字节或直到达到最大特征大小(即Bluedroid为600)。 Its important to note that if the MTU size is not a perfect multiple of 600 for data longer than 600 bytes, the remaining bytes will be discarded (as Bluedroid never actually expects to read 600 bytes since it thinks the GATT Server will enforce the 512 byte limit on characteristic sizes). 重要的是要注意,如果对于长度超过600字节的数据,MTU大小不是600的完美倍数,则剩余的字节将被丢弃(因为Bluedroid从未实际期望读取600字节,因为它认为GATT服务器将强制执行512字节特征尺寸限制)。 So, if your data exceeds the 600 byte limit (or 512 limit for safety), you should expect to call BluetoothGatt.readCharacteristic multiple times. 因此,如果您的数据超过600字节限制(或512安全限制),您应该多次调用BluetoothGatt.readCharacteristic Heres a simple example for reading lots of data on the Android side (sorry Im not using bleno so cannot give you the code to fix that side), it relies on first sending the length of the data as a unsigned 32-bit integer and then reading out the data with repeated calls to BluetoothGatt.readCharacteristic if the data is longer than 600 bytes: 下面是一个简单的例子,用于在Android端读取大量数据(对不起我不使用bleno所以不能给你修复那边的代码),它依赖于首先将数据长度作为无符号的32位整数发送然后如果数据长度超过600字节,则重复调用BluetoothGatt.readCharacteristic读取数据:

private int readLength;
private StringBuilder packet; // In my case, Im building a string out of the data

@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                    int newState) {
    if (newState == BluetoothProfile.STATE_CONNECTED) {
        gatt.requestMtu(201); // NOTE: If you are going to read a long piece of data, its best to make this value a factor of 600 + 1, like 51, 61, 101, 151, etc due to the risk of data loss if the last packet contains more than 600 bytes of cumulative data
    }
}

@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
    gatt.discoverServices();
}

@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    // Kick off a read
    BluetoothGattCharacteristic characteristic = gatt.getService(UUID.fromString(SERVICE_UUID)).getCharacteristic(UUID.fromString(CHAR_UUID));
    readLength = 0;
    gatt.readCharacteristic(characteristic);
}

@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    if (readLength == 0) {
        readLength = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0);
        packet = new StringBuilder();
        gatt.readCharacteristic(characteristic);
    } else {
        byte[] data = charactertic.getValue();
        packet.append(new String(data));
        readLength -= data.length;

        if (readLength == 0) {
            // Got all data this time; you can now process the data however you want
        } else {
            gatt.readCharacteristic(characteristic);
        }
    }
}

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

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