Как я могу достичь максимальной безопасности потоков с помощью 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
;
- Создать
HandlerThread
- Создать
Handler
прикреплен к вашемуHandlerThread
- Передай свой
Handler
вBluetoothDevice.connectGatt()
- поздравляю, когда уведомление полученоsetValue()
а такжеonCharacteristicChanged()
будет вызван на вашHandlerThread
, - Если вы хотите написать характеристику, опубликуйте
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
поле, которое вам еще нужно установить с помощью отражения.