Клиент Android BLE возвращает только 600 байт данных в onCharacteristicRead

У меня есть сервер Bluetooth, который использует bleno и возвращает клиенту список доступных сетей Wi-Fi. Код для 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 - Периферийный | onCharacteristicRead возвращает неверное значение или его часть (но повторяется)

и это: Android BLE — как большое значение характеристики считывается порциями (с использованием смещения)?

Но оба не решили мою проблему. Я знаю, что мне нужно дождаться возврата одного чтения, прежде чем я начну следующее чтение, и что мне нужно дождаться записи MTU, прежде чем продолжить чтение данных. Насколько мне известно, это отражено в источнике, который вы видите выше. Я тут как-то потерялся.

Любая идея высоко ценится.

Большое спасибо


person Thor_Bux    schedule 12.02.2018    source источник


Ответы (1)


Для тех, кто наткнется на этот пост, который также задается вопросом, почему Android, кажется, возвращает только 600 байтов для длинных характеристик GATT, о которых спрашивает этот вопрос, все сводится к тому, как Bluedroid (стек Bluetooth Android) реализует свой клиент GATT и как его не соответствует спецификации . В моем случае я использовал устройство IoT на базе ESP32 в качестве сервера GATT и Android (SDK 24) для клиента GATT.

Согласно спецификации (Bluetooth Core 4.2; том 3, часть F: 3.2.9), максимальный размер значения характеристики (унаследованного от значения атрибута ATT) составляет 512 байт. Однако по какой-то причине Bluedroid не пытается обеспечить соблюдение этого требования, вместо этого решил установить максимальный размер 600; что можно увидеть, если погрузиться в исходный код Bluedroid и найти макрос GATT_MAX_ATTR_LEN, для которого установлено значение 600 (stack/include/gatt_api.h:125). Поскольку в моем случае (и, кажется, в вашем) я реализовывал код ответа на запрос на чтение, я также не видел ограничения в 512 байт на чтение для характеристик.

Теперь важно понять, как Bluedroid читает характеристики и как это связано как с размером MTU, максимальным размером чтения (должно быть 512, но 600 для Bluedroid), так и с тем, как обрабатывать данные, превышающие этот максимальный размер. . Размер MTU — это максимальный размер пакета на уровне ATT, который вы можете использовать. Таким образом, для каждого вызова BluetoothGatt.readCharacteristic вы можете отправлять один или несколько запросов на чтение на сервер в зависимости от того, считает ли Bluedroid характерный размер превышающим размер MTU. На низком уровне Bluedroid сначала отправит запрос на чтение ATT (0x0a), а если длина пакета составляет MTU байт, за ним последует запрос на чтение большого двоичного объекта ATT (0x0c) со смещением, равным размеру MTU. Он будет продолжать отправлять запросы ATT Read Blob до тех пор, пока длина ответа ATT Read Blob не станет меньше MTU байтов или пока не будет достигнут максимальный характеристический размер (т. е. 600 для Bluedroid). Важно отметить, что если размер MTU не является кратным 600 для данных длиннее 600 байт, оставшиеся байты будут отброшены (поскольку Bluedroid фактически никогда не ожидает чтения 600 байтов, поскольку он думает, что сервер GATT будет применять 512 байтов). ограничение характерных размеров). Таким образом, если ваши данные превышают ограничение в 600 байт (или ограничение в 512 байтов для безопасности), вам следует ожидать вызова BluetoothGatt.readCharacteristic несколько раз. Вот простой пример для чтения большого количества данных на стороне Android (извините, я не использую bleno, поэтому не могу дать вам код для исправления этой стороны), он сначала посылает длину данных как 32-битное целое число без знака, а затем чтение данных повторными вызовами BluetoothGatt.readCharacteristic, если данные длиннее 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);
        }
    }
}
person Unn    schedule 25.10.2018