Android BLE客户端仅在onCharacteristicRead

时间:2018-02-12 07:13:37

标签: javascript android bluetooth-lowenergy android-bluetooth bleno

我有一台使用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 个答案:

答案 0 :(得分:1)

对于任何遇到这篇文章的人,也想知道为什么Android似乎只为长GATT特性返回600个字节,就像这个问题所要问的那样,这全都归结于Bluedroid(Android的蓝牙堆栈)如何实现其GATT客户端以及其实现方式。不合规格就我而言,我使用基于ESP32的IoT设备作为GATT服务器,并使用GATT客户端的Android(SDK 24)。

根据规范(Bluetooth Core 4.2;第3卷,F部分:3.2.9),特征值的最大大小(从ATT的属性值继承)为512字节。但是,由于某些原因,Bluedroid不会尝试执行此要求,而是决定最大大小为600;如果您深入Bluedroid源并找到设置为600(GATT_MAX_ATTR_LEN)的宏stack/include/gatt_api.h:125,则可以看到该代码。因为就我而言(似乎是您自己),我正在实现读取请求响应代码,所以我也没有看到针对特征读取对512字节的限制。

现在,重要的是要认识到Bluedroid读取特征的方式以及与MTU大小,读取的最大大小(应为512,但对于Bluedroid为600)以及如何处理比最大尺寸。 MTU大小是您可以使用的ATT级别上最大的数据包大小。因此,对于每个对BluetoothGatt.readCharacteristic的调用,您可能正在向服务器发送一个或多个读取请求,具体取决于Bluedroid是否认为特征大小超出了MTU大小。在较低级别上,Bluedroid将首先发送ATT读取请求(0x0a),如果数据包的长度为MTU字节,它将跟进ATT读取Blob请求(0x0c),其偏移量为设置为MTU大小。它将继续发送ATT读取Blob请求,直到ATT读取Blob响应的长度小于MTU字节,或者直到达到最大特征大小为止(例如,Bluedroid为600)。重要的是要注意,如果MTU大小不是大于600字节的数据的600的完美倍数,则剩余的字节将被丢弃(因为Bluedroid从未真正期望读取600字节,因为它认为GATT服务器将强制执行512字节限制特征尺寸)。因此,如果您的数据超过600字节的限制(或出于安全性考虑为512的限制),则应该期望多次调用BluetoothGatt.readCharacteristic。这是一个在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);
        }
    }
}