web-dev-qa-db-fra.com

Android API BLE: notification GATT non reçue

Appareil utilisé pour les tests: Nexus 4, Android 4.3

La connexion fonctionne bien, mais la méthode onCharacteristicChanged de mon rappel n'est jamais appelée. Cependant, je m'enregistre pour les notifications en utilisant setCharacteristicNotification(char, true) à l'intérieur de onServicesDiscovered, et cette fonction retourne même true.

Journal de l'appareil (il y a en réalité non messages du tout lorsque des notifications devraient apparaissent/sont envoyées via le périphérique 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

Les notifications GATT fonctionnent très bien avec iOS et l'application fait essentiellement la même chose que sur Android (enregistrement pour notification, etc.).

Quelqu'un d'autre en a-t-il fait l'expérience avec une solution possible?

68
Boni2k

Il semble que vous ayez oublié d'écrire le descripteur qui indique à votre appareil BLE de passer dans ce mode. Voir les lignes de code qui traitent du descripteur à l'adresse http://developer.Android.com/guide/topics/connectivity/bluetooth-le.html#notification

Sans définir ce descripteur, vous ne recevez jamais de mises à jour d'une caractéristique. Appeler setCharacteristicNotification ne suffit pas. C'est une erreur commune.

code saisi

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? 
}
82
OneWorld

@ Boni2k - J'ai les mêmes problèmes. Dans mon cas, j'ai 3 caractéristiques de notification et une poignée de caractéristiques de lecture/écriture.

Ce que j’ai trouvé, c’est qu’il existe une certaine dépendance entre writeGattDescriptor et readCharacteristic. Tous des writeGattDescriptors doit venir en premier et compléter avant d'émettre des appels readCharacteristic.

Voici ma solution en utilisant Queues. Maintenant, je reçois des notifications et tout le reste fonctionne bien:

Créez deux files d'attente comme ceci:

private Queue<BluetoothGattDescriptor> descriptorWriteQueue = new LinkedList<BluetoothGattDescriptor>();
private Queue<BluetoothGattCharacteristic> characteristicReadQueue = new LinkedList<BluetoothGattCharacteristic>();

Ensuite, écrivez tous vos descripteurs immédiatement après la découverte avec cette méthode:

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);      
    }
}

et ce rappel:

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());
    };

La méthode pour lire une caractéristique se présente normalement comme suit:

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);              
}

et mon rappel de lecture:

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());
    }
41
miznick

Lorsque vous définissez la valeur du descripteur au lieu de descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE), insérez descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE). Les callbacks pour onCharacteristicChanged sont appelés maintenant.

11
user2926265

Problèmes rencontrés dans les versions précédentes de Android recevant des notifications (une indication qui était enregistrée) et toujours par la suite un événement de déconnexion étrange. Il s’avère que c’est parce que nous avons enregistré des notifications sur cinq caractéristiques.

L'erreur découverte dans LogCat était:

02-05 16:14:24.990    1271-1601/? E/bt-btif﹕ Max Notification Reached, registration failed.

Avant 4.4.2, le nombre d’inscriptions était limité à 4! 4.4.2 augmenté cette limite à 7.

En réduisant le nombre d'enregistrements dans les versions précédentes, nous avons pu contourner cette limitation.

6
D. Wescotty

Je suppose (vous n'avez pas fourni votre code source) que vous ne l'avez pas implémenté comme Google voulait :

(1)

mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

et alors

(2)

BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);

Je suppose que 2 est manquant. Dans ce cas, je pense que la notification de bas niveau sera déclenchée mais ne sera jamais signalée à la couche application.

6
Marian Paździoch

Eh bien, ce nom d’API risque de semer la confusion parmi les développeurs d’applications s’il/elle n’était pas le programmeur en arrière-plan Bluetooth.

Du point de vue des spécifications de base Bluetooth, citez la section 3.3.3.3 de la spécification de base 4.2 Vol 3, partie G, "Configuration des caractéristiques du client":

La valeur du descripteur de caractéristique est un champ de bits. Lorsqu'un bit est défini, cette action doit être activée, sinon elle ne sera pas utilisée.

et section 4.10

Les notifications peuvent être configurées à l'aide du descripteur de configuration des caractéristiques du client (voir la section 3.3.3.3).

ce qui indique clairement que si le client souhaite recevoir la notification (ou l’indication nécessitant une réponse) du serveur, le bit "Notification" doit être écrit sur 1 (le bit "Indication" également sur 1 sinon).

Cependant, le nom "setCharacteristicNotification" nous donne un indice: si nous définissons les paramètres de cette API en tant que TURE, le client reçoit des notifications. Malheureusement, cette API définit uniquement le bit local pour autoriser la notification envoyée aux applications en cas de notification à distance. Voir le code de 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;
}'

donc, ce qui compte, c'est l'action d'écriture du descripteur.

4
Guo Xingmin

Celui-ci travaille pour moi:

pour informer le périphérique maître que certaines caractéristiques changent, appelez cette fonction sur votre périphérique:

private BluetoothGattServer server;
//init....

//on BluetoothGattServerCallback...

//call this after change the characteristic
server.notifyCharacteristicChanged(device, characteristic, false);

dans votre appareil maître: activez setCharacteristicNotification après avoir découvert le service:

@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();
                }
            });
        }
   }

maintenant, vous pouvez vérifier que votre valeur caractéristique change, par exemple, la fonction onCharacteristicRead (ceci fonctionne également sur la fonction 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);
}
1
Doni

Voici un moyen simple de le faire, mais laissez-moi savoir si vous voyez des inconvénients.

Étape 1 Déclarer des variables booléennes

private boolean char_1_subscribed = false;
private boolean char_2_subscribed = false;
private boolean char_3_subscribed = false;

Étape 2 souscrivez à la première caractéristique du rappel 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;
}

étape

Abonnez-vous aux autres publications après le déclenchement du rappel 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;
    }
}
1
JBaczuk

J'avais une autre raison à ajouter, cela me rendait fou toute la journée:

Sur mon Samsung Note 3, je n'ai reçu aucune notification de modification des valeurs alors que le même code fonctionnait sur tout autre appareil avec lequel j'avais testé.

Le redémarrage de l'appareil a résolu tous les problèmes. Évident, mais lorsque vous êtes dans le problème, vous oubliez de penser à.

0
Christian