安卓BLE客户端在onCharacteristicRead中只返回了600字节的数据

7

我有一个使用bleno的蓝牙服务器,向客户端返回可用Wifi网络列表。 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);
}
}
}

我尝试过 data = data.slice(offset+1, offset+bleno.mtu); 和像这样的 data = data.slice(offset+1);

客户端是一个读取此特征的 Android 应用程序。

读取的 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);
                    }
                }
            }

MTU已经调整为256字节。读取列表时,我在服务器上做了反映。调用本身工作正常并返回列表,但如果列表包含超过600字节,则Android仅可用600字节。我敢肯定JS服务器发送了所有数据,但由于某种原因,Android客户端仅接收或缓存了600字节,这似乎不正确。
我找到了这篇文章:Android BLE - Peripheral | onCharacteristicRead return wrong value or part of it (but repeated)以及这篇文章:Android BLE - How is large characteristic value read in chunks (using an offset)?,但都没有解决我的问题。我知道我需要等待一个读取返回才能开始下一个读取,并且需要在继续读取数据之前等待MTU被写入。据我所知,这在上面的源代码中有体现。我有点迷失了方向。
非常感谢任何想法。
1个回答

2

对于任何遇到此问题的人,想知道为什么Android似乎只返回长GATT特征值的600个字节,就像这个问题所问的那样,这完全取决于Bluedroid(Android的蓝牙堆栈)实现其GATT客户端的方式以及其超出规范的方式。在我的情况下,我使用基于ESP32的物联网设备作为我的GATT服务器,使用Android(SDK 24)作为GATT客户端。

根据规范(Bluetooth Core 4.2; Vol 3, Part F: 3.2.9),特征值(继承自ATT的属性值)的最大大小为512字节。然而,由于某种原因,Bluedroid不尝试强制执行此要求,而是决定将最大大小设置为600;如果您深入了解Bluedroid源代码并找到宏,则可以看到这一点()。由于在我的情况下(以及您的情况似乎也是如此),我正在实现读取请求响应代码,因此我没有看到要强制执行512字节限制以读取特征的特性。

现在,重要的是要意识到Bluedroid似乎如何读取特征以及它与MTU大小,读取的最大大小(应为512,但对于Bluedroid为600)以及如何处理超过该最大大小的数据之间的关系。 MTU大小是您可以使用的ATT级别上的最大数据包大小。因此,对于每次调用,您可能会发送一个或多个读取请求到服务器,具体取决于Bluedroid是否认为特征大小超过MTU大小。在低级别上,Bluedroid将首先发送ATT读取请求(<0x0a>),如果数据包长度为MTU字节,则会跟随ATT读取Blob请求(<0x0c>),偏移量设置为MTU大小。它将继续发送ATT读取Blob请求,直到ATT读取Blob响应小于MTU字节或达到最大特征大小(即,对于Bluedroid为600)。重要的是要注意,如果MTU大小不是长于600字节的数据的完美倍数,则剩余的字节将被丢弃(因为Bluedroid实际上从不期望读取600字节,因为它认为GATT服务器将强制执行512字节限制特性大小)。因此,如果您的数据超过600字节限制(或为了安全起见,超过512限制),则应该预计要多次调用。这里是在Android端读取大量数据的简单示例(抱歉我没有使用bleno,因此无法为您提供修复该方面的代码),它依赖于首先将数据长度作为无符号32位整数发送,然后使用重复调用读取数据(如果数据超过600字节):

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接