Android BLE API: уведомление GATT не получено
Устройство, используемое для тестирования: Nexus 4, Android 4.3
Соединение работает нормально, но метод onCharacteristicChanged
моего обратного вызова никогда не вызывается. Однако я регистрируюсь для уведомлений, используя setCharacteristicNotification(char, true)
внутри onServicesDiscovered
, и эта функция даже возвращает true.
Журнал устройств (на самом деле сообщений нет вообще, когда уведомления должны появляться/отправляться через устройство Bluetooth):
07-28 18:15:06.936 16777-16809/de.ffuf.leica.sketch D/BluetoothGatt: setCharacteristicNotification() - uuid: 3ab10101-f831-4395-b29d-570977d5bf94 enable: true
07-28 18:15:06.936 4372-7645/com.android.bluetooth D/BtGatt.GattService: registerForNotification() - address=C9:79:25:34:19:6C enable: true
07-28 18:15:06.936 4372-7645/com.android.bluetooth D/BtGatt.btif: btif_gattc_reg_for_notification
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1018
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.GattService: onRegisterForNotifications() - address=null, status=0, registered=1, charUuid=3ab10101-f831-4395-b29d-570977d5bf94
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1016
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1018
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.GattService: onRegisterForNotifications() - address=null, status=0, registered=1, charUuid=3ab10102-f831-4395-b29d-570977d5bf94
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1016
07-28 18:15:06.946 4372-7684/com.android.bluetooth E/bt-btif: already has a pending command!!
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1013
07-28 18:15:06.946 4372-7684/com.android.bluetooth E/bt-btif: already has a pending command!!
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1013
07-28 18:15:06.946 4372-7684/com.android.bluetooth E/bt-btif: already has a pending command!!
07-28 18:15:06.976 4372-7645/com.android.bluetooth D/BtGatt.btif: btif_gattc_upstreams_evt: Event 9
Уведомления GATT работают отлично, используя iOS, и приложение в основном делает то же самое, что и на Android (регистрируется для уведомления и т.д.).
Кто-нибудь еще испытал это с возможным решением?
Ответы
Ответ 1
Похоже, вы забыли написать дескриптор, который говорит вашему устройству BLE идти в этом режиме. См. Строки кода, относящиеся к дескриптору, в http://developer.android.com/guide/topics/connectivity/bluetooth-le.html#notification
Без настройки этого дескриптора вы никогда не будете получать обновления для характеристики. Вызов setCharacteristicNotification
недостаточен. Это распространенная ошибка.
сокращен код
protected static final UUID CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
public boolean setCharacteristicNotification(BluetoothDevice device, UUID serviceUuid, UUID characteristicUuid,
boolean enable) {
if (IS_DEBUG)
Log.d(TAG, "setCharacteristicNotification(device=" + device.getName() + device.getAddress() + ", UUID="
+ characteristicUuid + ", enable=" + enable + " )");
BluetoothGatt gatt = mGattInstances.get(device.getAddress()); //I just hold the gatt instances I got from connect in this HashMap
BluetoothGattCharacteristic characteristic = gatt.getService(serviceUuid).getCharacteristic(characteristicUuid);
gatt.setCharacteristicNotification(characteristic, enable);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID);
descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : new byte[] { 0x00, 0x00 });
return gatt.writeDescriptor(descriptor); //descriptor write operation successfully started?
}
Ответ 2
@Boni2k - У меня такие же проблемы. В моем случае у меня есть 3 уведомления и несколько характеристик чтения/записи.
Я обнаружил, что существует зависимость между writeGattDescriptor
и readCharacteristic
. Все writeGattDescriptors должны заходить сначала и, прежде чем вы будете вызывать любые вызовы readCharacteristic.
Вот мое решение, используя Queues
. Теперь я получаю уведомления, и все остальное работает нормально:
Создайте две очереди как:
private Queue<BluetoothGattDescriptor> descriptorWriteQueue = new LinkedList<BluetoothGattDescriptor>();
private Queue<BluetoothGattCharacteristic> characteristicReadQueue = new LinkedList<BluetoothGattCharacteristic>();
Затем напишите все свои дескрипторы сразу после обнаружения с помощью этого метода:
public void writeGattDescriptor(BluetoothGattDescriptor d){
//put the descriptor into the write queue
descriptorWriteQueue.add(d);
//if there is only 1 item in the queue, then write it. If more than 1, we handle asynchronously in the callback above
if(descriptorWriteQueue.size() == 1){
mBluetoothGatt.writeDescriptor(d);
}
}
и этот обратный вызов:
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "Callback: Wrote GATT Descriptor successfully.");
}
else{
Log.d(TAG, "Callback: Error writing GATT Descriptor: "+ status);
}
descriptorWriteQueue.remove(); //pop the item that we just finishing writing
//if there is more to write, do it!
if(descriptorWriteQueue.size() > 0)
mBluetoothGatt.writeDescriptor(descriptorWriteQueue.element());
else if(readCharacteristicQueue.size() > 0)
mBluetoothGatt.readCharacteristic(readQueue.element());
};
Метод считывания характеристики обычно выглядит следующим образом:
public void readCharacteristic(String characteristicName) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
BluetoothGattService s = mBluetoothGatt.getService(UUID.fromString(kYourServiceUUIDString));
BluetoothGattCharacteristic c = s.getCharacteristic(UUID.fromString(characteristicName));
//put the characteristic into the read queue
readCharacteristicQueue.add(c);
//if there is only 1 item in the queue, then read it. If more than 1, we handle asynchronously in the callback above
//GIVE PRECEDENCE to descriptor writes. They must all finish first.
if((readCharacteristicQueue.size() == 1) && (descriptorWriteQueue.size() == 0))
mBluetoothGatt.readCharacteristic(c);
}
и мой обратный вызов:
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
readCharacteristicQueue.remove();
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
else{
Log.d(TAG, "onCharacteristicRead error: " + status);
}
if(readCharacteristicQueue.size() > 0)
mBluetoothGatt.readCharacteristic(readCharacteristicQueue.element());
}
Ответ 3
При установке значения дескриптору вместо размещения descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
, поместите descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)
. Вызываются обратные вызовы для onCharacteristicChanged.
Ответ 4
Опытные проблемы в ранних версиях Android, получающих уведомления (признак, который был зарегистрирован), и всегда имели странное событие разъединения. Как оказалось, это было потому, что мы зарегистрировались для уведомлений по пяти характеристикам.
Ошибка, обнаруженная в LogCat, была:
02-05 16:14:24.990 1271-1601/? E/bt-btif﹕ Max Notification Reached, registration failed.
До 4.4.2 количество регистраций ограничено 4! 4.4.2 увеличил этот предел до 7.
Уменьшая количество регистраций в более ранних версиях, мы смогли обойти это ограничение.
Ответ 5
Я предполагаю (вы не указали свой исходный код), что вы его не реализовали как Google хотел:
(1)
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
а затем
(2)
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
Я предполагаю, что 2 отсутствует. В этом случае я верю, что уведомление на низком уровне будет запущено, но они никогда не будут сообщаться на прикладном уровне.
Ответ 6
Ну, это имя API, безусловно, приводит некоторые недоумения разработчику приложения, если он/она не является программным программистом Bluetooth.
С точки зрения спецификации ядра Bluetooth, цитата из основной спецификации 4.2 Vol 3, раздел Part G 3.3.3.3 "Конфигурация клиентских характеристик":
Значение характеристического дескриптора - бит. Когда бит установлен, это действие должно быть активировано, иначе оно не будет использоваться.
и раздел 4.10
Уведомления могут быть сконфигурированы с использованием дескриптора конфигурации характеристик клиента (см. раздел 3.3.3.3).
в котором четко указано, что если клиент хочет получить уведомление (или указание, требующее ответа) с сервера, должен записать бит "Уведомление" в 1 (бит "Индикация" также в 1 в противном случае).
Однако имя "setCharacteristicNotification" дает нам подсказку: если мы установим параметры этого API как TURE, клиент получит уведомления; к сожалению, этот API только устанавливает локальный бит, чтобы разрешить отправку уведомлений в приложения в случае удаленного уведомления. См. Код от Bluedroid:
/*******************************************************************************
**
** Function BTA_GATTC_RegisterForNotifications
**
** Description This function is called to register for notification of a service.
**
** Parameters client_if - client interface.
** bda - target GATT server.
** p_char_id - pointer to GATT characteristic ID.
**
** Returns OK if registration succeed, otherwise failed.
**
*******************************************************************************/
tBTA_GATT_STATUS BTA_GATTC_RegisterForNotifications (tBTA_GATTC_IF client_if,
BD_ADDR bda,
tBTA_GATTC_CHAR_ID *p_char_id)
{
tBTA_GATTC_RCB *p_clreg;
tBTA_GATT_STATUS status = BTA_GATT_ILLEGAL_PARAMETER;
UINT8 i;
if (!p_char_id)
{
APPL_TRACE_ERROR("deregistration failed, unknow char id");
return status;
}
if ((p_clreg = bta_gattc_cl_get_regcb(client_if)) != NULL)
{
for (i = 0; i < BTA_GATTC_NOTIF_REG_MAX; i ++)
{
if ( p_clreg->notif_reg[i].in_use &&
!memcmp(p_clreg->notif_reg[i].remote_bda, bda, BD_ADDR_LEN) &&
bta_gattc_charid_compare(&p_clreg->notif_reg[i].char_id, p_char_id))
{
APPL_TRACE_WARNING("notification already registered");
status = BTA_GATT_OK;
break;
}
}
if (status != BTA_GATT_OK)
{
for (i = 0; i < BTA_GATTC_NOTIF_REG_MAX; i ++)
{
if (!p_clreg->notif_reg[i].in_use)
{
memset((void *)&p_clreg->notif_reg[i], 0, sizeof(tBTA_GATTC_NOTIF_REG));
p_clreg->notif_reg[i].in_use = TRUE;
memcpy(p_clreg->notif_reg[i].remote_bda, bda, BD_ADDR_LEN);
p_clreg->notif_reg[i].char_id.srvc_id.is_primary = p_char_id->srvc_id.is_primary;
bta_gattc_cpygattid(&p_clreg->notif_reg[i].char_id.srvc_id.id, &p_char_id->srvc_id.id);
bta_gattc_cpygattid(&p_clreg->notif_reg[i].char_id.char_id, &p_char_id->char_id);
status = BTA_GATT_OK;
break;
}
}
if (i == BTA_GATTC_NOTIF_REG_MAX)
{
status = BTA_GATT_NO_RESOURCES;
APPL_TRACE_ERROR("Max Notification Reached, registration failed.");
}
}
}
else
{
APPL_TRACE_ERROR("Client_if: %d Not Registered", client_if);
}
return status;
}'
так что важно было записать действие дескриптора.
Ответ 7
У меня была еще одна причина, которую я хотел бы добавить, поскольку это заставило меня сходить с ума целый день:
На моем Samsung Note 3 я не получал уведомления об измененных значениях, пока тот же код работал на любом другом устройстве, с которым я тестировал.
Перезагрузка устройства решила все проблемы. Очевидно, но когда вы входите в проблему, вы забудете думать.
Ответ 8
Вот простой способ сделать это, но дайте мне знать, если увидите какие-то недостатки.
Шаг 1
Объявить логические переменные
private boolean char_1_subscribed = false;
private boolean char_2_subscribed = false;
private boolean char_3_subscribed = false;
Шаг 2
подписаться на первый признак в onServicesDiscovered обратном вызове:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!char_1_subscribed)
subscribeToNotification(gatt.getService(UUID_SERVICE).getCharacteristic(UUID_CHAR_1)); char_1_subscribed = true;
}
Шаг 3
Подпишитесь на любые другие сообщения после срабатывания обратного вызова onCharacteristicChanged.
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if(UUID_CHAR_1.equals(characteristic.getUuid()))
{
if(!char_1_subscribed)
subscribeToNotification(gatt.getService(UUID_SERVICE).getCharacteristic(UUID_CHAR_2)); char_2_subscribed = true;
}
if(UUID_CHAR_2.equals(characteristic.getUuid()))
{
if(!char_3_subscribed)
subscribeToNotification(gatt.getService(UUID_SERVICE).getCharacteristic(UUID_CHAR_3)); char_3_subscribed = true;
}
}
Ответ 9
Этот работает для меня:
чтобы уведомить ведущее устройство о том, что какая-либо характеристика изменяется, вызовите эту функцию на вашем pheripheral:
private BluetoothGattServer server;
//init....
//on BluetoothGattServerCallback...
//call this after change the characteristic
server.notifyCharacteristicChanged(device, characteristic, false);
в главном устройстве: включите setCharacteristicNotification после обнаружения службы:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
services = mGatt.getServices();
for(BluetoothGattService service : services){
if( service.getUuid().equals(SERVICE_UUID)) {
characteristicData = service.getCharacteristic(CHAR_UUID);
for (BluetoothGattDescriptor descriptor : characteristicData.getDescriptors()) {
descriptor.setValue( BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
mGatt.writeDescriptor(descriptor);
}
gatt.setCharacteristicNotification(characteristicData, true);
}
}
if (dialog.isShowing()){
mHandler.post(new Runnable() {
@Override
public void run() {
dialog.hide();
}
});
}
}
теперь вы можете проверить, что ваше значение признака - это изменение, например функция onCharacteristicRead (это также работает и над функцией onCharacteristicChanged):
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.i("onCharacteristicRead", characteristic.toString());
byte[] value=characteristic.getValue();
String v = new String(value);
Log.i("onCharacteristicRead", "Value: " + v);
}
Ответ 10
У меня возникли проблемы с уведомлениями для BLE на Android. Однако есть полностью работающая демонстрация, которая включает оболочку bluetooth вокруг BluetoothAdapter
. Обертка называется BleWrapper
и поставляется с демо-приложением BLEDemo, содержащимся в пакете Application Accelerator. Скачать здесь: https://developer.bluetooth.org/Pages/Bluetooth-Android-Developers.aspx. Перед загрузкой вам необходимо зарегистрироваться на свой адрес электронной почты вверху справа. Лицензия на проект допускает бесплатное использование, модификацию кода и публикацию.
По моему опыту, демонстрационное приложение Android отлично обрабатывает подписки на уведомления BLE. Я еще не слишком много погрузился в код, чтобы увидеть, как обертка обертывается.
В Play Маркете доступно приложение для Android, которое представляет собой демонстрацию демонстрации приложения-ускорителя. Поскольку пользовательский интерфейс выглядит почти таким же, я полагаю, что он также использует BleWrapper
. Загрузите приложение здесь: https://play.google.com/store/apps/details?id=com.macdom.ble.blescanner