Как я могу достичь максимальной безопасности потоков с помощью BLE Gatt Характеристика для чтения / записи?

Я общаюсь с устройством BLE, которое отправляет мне много данных по одной характеристике. Тот же CH используется для отправки данных на устройство.

Внутри Android BluetoothGattCharacteristic Есть методы

public byte[] getValue() {
    return mValue;
}

public boolean setValue(byte[] value) {
    mValue = value;
    return true;
}

Однако выполнение происходит из разных потоков. Андроид запускает около 5 разных связующих потоков, и они вызывают

onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)

Сейчас я пытаюсь захватить массив как первую операцию в обратном вызове, но НЕ гарантируется, что другой поток (не под моим контролем) одновременно устанавливает массив.

Хотя вышеприведенное, похоже, и помогает, более сложным является отправка данных "против входящего потока данных".

Я должен использовать тот же CH для отправки данных на устройство, поэтому я setValue() а потом BluetoothGatt.writeCharacteristic,

public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
// some null checks etc

//now it locks the device
synchronized(mDeviceBusy) {
    if (mDeviceBusy) return false;
    mDeviceBusy = true;
}

//the actual execution

return true;
}

Затем я в какой-то момент получу ответный звонок от какого-то потока

onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 

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

Как я могу сделать это более поточно-ориентированным, не имея доступа к Android BLE API, стеку и т. Д.?

0 ответов

В SDK27, onNotify() функция обратного вызова в BluetoothGatt.java была обновлена ​​для вызова ОБА BluetoothGattCharacteristic.setValue() а также BluetoothGattCallback.onCharacteristicChanged() в Runnable's run(),

Это изменение позволяет нам заставить все звонки BluetoothGattCharacteristic.setValue() - как для нашей исходящей записи в характеристику, так и для входящих уведомлений - в один и тот же поток, что устраняет условие гонки, приводящее к повреждению BluetoothGattCharacteristic.mValue;

  1. Создать HandlerThread
  2. Создать Handler прикреплен к вашему HandlerThread
  3. Передай свой Handler в BluetoothDevice.connectGatt() - поздравляю, когда уведомление получено setValue() а также onCharacteristicChanged() будет вызван на ваш HandlerThread,
  4. Если вы хотите написать характеристику, опубликуйте setValue() а также writeCharacteristic() на ваш HandlerThread через ваш обработчик

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

До появления SDK27 было невозможно обеспечить надежную полнодуплексную связь по одной характеристике - с использованием общедоступного API.

Довольно досадно.

Хотя, похоже, есть способ, если вы хотите немного обмануть. (Вероятно, существует более одного способа обмана; описанный ниже довольно слабый эффект.)

Проблема заключается в использовании mValue Поле в BluetoothGattCharacteristic для двух конфликтующих целей - отправить и получить. Это довольно плохой дизайн API; одно исправление уровня API состояло бы в том, чтобы ввести другое поле, чтобы было одно для каждого направления. Другим было бы не использовать метод setValue() при отправке данных, а вместо этого предоставлять данные в качестве параметра для writeCharacteristic() (хотя это усложняется тем фактом, что поле значения может быть закодировано во время получения / установки, поддержка нескольких типов данных). Однако, похоже, что сопровождающие API выбрали другой путь.

В любом случае - чтобы устранить проблему, убедитесь, что полученное значение и значение, подлежащее отправке, хранятся в разных местах. Более конкретно, в полях значений двух разных экземпляров BluetoothGattCharacteristic.

Теперь клонирование BGC невозможно с использованием официального API. Используя открытый конструктор, вы можете получить экземпляр, в котором установлены все поля, кроме service а также instanceId, У них есть публичные геттеры - чтобы установить их, используйте отражение для доступа setService() а также setInstanceId() - это обманная часть.

Я проверил этот подход [1], и он, кажется, работает как хотелось бы.:-)

[1] Вариант этого подхода, на самом деле; в более новых версиях SDK рассматриваемый класс является Parcelable, поэтому, когда это возможно, я клонирую объект с помощью Parcel-сериализации, на случай, если будущие реализации добавят больше полей. Это заботится обо всем, кроме service поле, которое вам еще нужно установить с помощью отражения.

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