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