onCharacteristicWrite и onNotificationSent вызываются слишком быстро - как получить реальные скорости исходящих данных?

Я реализовал двустороннюю связь BLE (по сути, что-то вроде последовательного кабеля TX/RX), используя две характеристики ATT на периферийном устройстве - одно доступно для записи, другое доступно для чтения с помощью уведомлений. Все работает как положено, кроме заявленной скорости записи.

Когда я вызываю следующий код:

txCharacteristic.setValue(data);
boolean queuedOk = 
   connectedPeripheralGatt.writeCharacteristic(txCharacteristic);

onCharacteristicWrite обратный вызов запускается почти сразу, а затем я повторяю фрагмент кода выше, чтобы применить следующий фрагмент данных к характеристике. В то время как принимающая сторона сообщает правильную скорость (около 10 КБ / с), измеренную по меткам времени, собранным в onCharacteristicWriteRequestотправляющая сторона сообщает о невероятных скоростях (например, 50–300 КБ / с) и сообщает о завершении передачи всех данных в writeCharacteristic, хотя принимающая сторона все еще получает входящие данные.

Итак, ясно onCharacteristicWriteRequest вызывается, пока большая часть данных все еще находится на пути к целевому устройству.

То же самое происходит, когда я отправляю данные с периферийного устройства на центральное устройство, только на этот раз onNotificationSent на периферии лежит о его скорости и onCharacteristicChanged на центральном сообщает правильную скорость.

Есть ли более или менее надежный способ приблизительного измерения скорости исходящих данных со стороны отправителя без включения ACK (что я не хочу делать из соображений производительности)?

Сама информация приходит в целости, я тестирую ее с файлами изображений размером от 0,5 до 5 мегабайт, и изображения всегда правильно декодируются на принимающей стороне.

Чтобы максимизировать пропускную способность, записываемая характеристика была настроена как ненадежная следующим образом:

    BluetoothGattCharacteristic rxCharacteristic = new BluetoothGattCharacteristic(UUID.fromString(RX_CHARACTERISTIC_UUID),
            BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
            BluetoothGattCharacteristic.PERMISSION_WRITE);
    rxCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);

Я не уверен в моем использовании PROPERTY_WRITE_NO_RESPONSE а также WRITE_TYPE_NO_RESPONSE но из некоторых статей на некоторых веб-сайтах смутно показалось, что хорошей практикой является указание обоих; поправьте меня если я ошибаюсь.

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

1 ответ

Решение

Ты прав.

Если вы используете "запись с ответом", то обратный вызов onCharacteristicWrite будет вызван после того, как удаленный отправит обратно ответ на запись. Однако, если вы используете "запись без ответа", Android имеет механизм управления потоком, который буферизует данные, которые вы пишете. Это работает так:

Приложение пишет пакет. Стек Bluetooth помещает этот пакет в свой внутренний буфер. Если после постановки в очередь этого пакета в буфере остается еще больше места, вызывается обратный вызов onCharacteristicWrite, поэтому вы можете немедленно написать другой пакет. Когда буфер заполнен, он ожидает вызова обратного вызова onCharacteristicWrite до тех пор, пока в буфере не будет доступно 50% пространства (если я правильно помню).

Пока есть пакеты во внутреннем буфере, он пытается записать их в контроллер Bluetooth, который также имеет ограниченное количество пакетов, которые могут быть буферизованы. Контроллер Bluetooth отправляет событие "число завершенных пакетов" обратно в стек Bluetooth Android через HCI, которое указывает, что канальный уровень устройства удаленного устройства подтвердил пакет. (Это не означает, что удаленное приложение не получило его; только то, что удаленный контроллер Bluetooth получил его.) Если в контроллере Bluetooth нет свободного места, оно будет ждать, пока освободится место.

Это намного умнее, чем на iOS. Там, если вы отправите много пакетов "запись с ответом", они будут отброшены еще до того, как будут отправлены, если внутренний буфер заполнен. (Эту проблему можно решить, отправив "запись с ответом" каждый 10-й пакет или около того).

К сожалению (в вашем случае), стек Bluetooth в Android не отправляет обратный вызов приложению, когда пакет был подтвержден удаленным канальным уровнем, поэтому вам придется основывать свою скорость на обратном вызове onCharacteristicWrite. Однако вы можете отправлять уведомления о статусе в другом направлении каждый десятый пакет или около того, что, я думаю, даст вам хорошие результаты. Если вместо этого стек Bluetooth Android будет отправлять обратный вызов onCharacteristicWrite при получении подтверждения на канальном уровне, это снизит скорость до одного пакета на событие соединения.

Если ссылка иногда отключается из-за тайм-аута наблюдения, вам следует знать об отчете об ошибке, который я опубликовал некоторое время назад: https://issuetracker.google.com/issues/37121017.

Что касается ваших вопросов со свойствами / разрешениями, это могло бы быть то же самое, но команда Bluetooth решила разделить их. Права доступа относятся к протоколу ATT, сообщая стеку Bluetooth, что разрешено делать клиенту. Свойства - это только некоторые свойства, которые другая сторона может прочитать, чтобы узнать, какой это тип характеристики.

Другие вопросы по тегам