it-swarm.com.de

Android BLE API: GATT-Benachrichtigung nicht erhalten

Zum Testen verwendetes Gerät: Nexus 4, Android 4.3

Die Verbindung funktioniert einwandfrei, aber die onCharacteristicChangedMethode meines Rückrufs wird nie aufgerufen. Allerdings registriere ich mich für Benachrichtigungen mit setCharacteristicNotification(char, true) in onServicesDiscovered und diese Funktion gibt sogar true zurück.

Geräteprotokoll (es gibt tatsächlich keine Meldungen, wenn Benachrichtigungen sollten erscheinen/über das Bluetooth-Gerät gesendet werden):

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-Benachrichtigungen funktionieren unter iOS einwandfrei, und die App funktioniert im Wesentlichen genauso wie unter Android (Registrierung für Benachrichtigungen usw.).

Hat jemand dies mit einer möglichen Lösung erlebt?

68
Boni2k

Sie haben anscheinend vergessen, den Deskriptor zu schreiben, der Ihrem BLE-Gerät mitteilt, in diesen Modus zu wechseln. Informationen zu den Codezeilen für den Deskriptor finden Sie unter http://developer.Android.com/guide/topics/connectivity/bluetooth-le.html#notification

Ohne diesen Deskriptor erhalten Sie niemals Aktualisierungen eines Merkmals. Es reicht nicht aus, setCharacteristicNotification aufzurufen. Dies ist ein häufiger Fehler.

Code abgeschnitten

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 - ich habe die gleichen Probleme. In meinem Fall habe ich 3 Benachrichtigungsmerkmale und eine Handvoll Lese-/Schreibmerkmale.

Was ich herausgefunden habe, ist, dass es eine gewisse Abhängigkeit zwischen writeGattDescriptor und readCharacteristic gibt. Alle der writeGattDescriptors muss zuerst kommen nd abgeschlossen, bevor Sie readCharacteristic-Aufrufe absetzen.

Hier ist meine Lösung mit Queues. Jetzt erhalte ich Benachrichtigungen und alles andere funktioniert einwandfrei:

Erstellen Sie zwei Warteschlangen wie folgt:

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

Dann schreiben Sie alle Ihre Deskriptoren sofort nach der Entdeckung mit dieser Methode:

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

und dieser Rückruf:

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

Die Methode zum Lesen eines Merkmals sieht dann normalerweise so aus:

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

und mein Rückruf:

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

Wenn Sie den Wert auf den Deskriptor setzen, anstatt descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) zu setzen, setzen Sie descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE). Die Rückrufe für onCharacteristicChanged werden jetzt aufgerufen.

11
user2926265

In früheren Versionen von Android) aufgetretene Probleme, bei denen Benachrichtigungen empfangen wurden (ein Hinweis, der registriert wurde) und anschließend immer ein seltsames Disconnect-Ereignis aufgetreten ist. Wie sich herausstellte, haben wir uns für Benachrichtigungen zu fünf Merkmalen registriert.

Der in LogCat entdeckte Fehler war:

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

Vor 4.4.2 war die Anzahl der Registrierungen auf 4 begrenzt! 4.4.2 erhöhte dieses Limit auf 7.

Durch die Reduzierung der Anzahl der Registrierungen in früheren Versionen konnten wir diese Einschränkung umgehen.

6
D. Wescotty

Ich gehe davon aus (Sie haben Ihren Quellcode nicht angegeben), dass Sie ihn nicht als Google implementiert haben :

(1)

mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

und dann

(2)

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

Ich vermute 2 fehlt. In diesem Fall wird meines Erachtens eine Benachrichtigung auf niedriger Ebene ausgelöst, diese wird jedoch niemals an die Anwendungsebene gemeldet.

6

Nun, dieser API-Name hat dem App-Entwickler mit Sicherheit einige Verwirrungen bereitet, wenn er nicht der Bluetooth-Hintergrundprogrammierer war.

Aus Sicht der Bluetooth-Kernspezifikation zitieren Sie aus Kernspezifikation 4.2, Band 3, Teil G, Abschnitt 3.3.3.3 "Konfiguration der Client-Eigenschaften":

Der charakteristische Deskriptorwert ist ein Bitfeld. Wenn ein Bit gesetzt ist, muss diese Aktion aktiviert werden, andernfalls wird sie nicht verwendet.

und Abschnitt 4.10

Benachrichtigungen können mithilfe des Client Characteristic Configuration-Deskriptors konfiguriert werden (siehe Abschnitt 3.3.3.3).

dies bedeutet eindeutig, dass der Client, wenn er die Benachrichtigung (oder die Anzeige, die eine Antwort benötigt) vom Server erhalten möchte, das Bit "Benachrichtigung" auf 1 schreiben sollte (andernfalls auch das Bit "Anzeige" auf 1).

Der Name "setCharacteristicNotification" gibt jedoch einen Hinweis darauf, dass der Client Benachrichtigungen erhalten würde, wenn die Parameter dieser API auf TURE gesetzt würden. Leider hat diese API nur das lokale Bit gesetzt, um die Benachrichtigung zuzulassen, die an Apps gesendet wird, wenn eine Remotebenachrichtigung eingeht. Siehe Code von 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;
}'

also, worauf es ankam, war die Deskriptor-Schreibaktion.

4
Guo Xingmin

Dieser arbeitet für mich:

um dem Master-Gerät mitzuteilen, dass sich eine Eigenschaft geändert hat, rufen Sie diese Funktion auf Ihrem Peripheriegerät auf:

private BluetoothGattServer server;
//init....

//on BluetoothGattServerCallback...

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

in Ihrem Master-Gerät: Aktivieren Sie setCharacteristicNotification, nachdem Sie den Dienst gefunden haben:

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

jetzt können Sie überprüfen, ob sich Ihr Merkmalswert geändert hat, z. B. die Funktion onCharacteristicRead (dies gilt auch für die Funktion 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

Hier ist eine einfache Möglichkeit, dies zu tun, aber lassen Sie mich wissen, wenn Sie Nachteile feststellen.

Schritt 1 Deklarieren Sie boolesche Variablen

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

Schritt 2 abonniert das erste Merkmal im onServicesDiscovered-Rückruf:

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

Schritt 3

Abonnieren Sie weitere, nachdem der Rückruf für onCharacteristicChanged ausgelöst wurde

@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

Ich möchte noch einen weiteren Grund hinzufügen, der mich den ganzen Tag verrückt machte:

Auf meinem Samsung Note 3 habe ich keine Benachrichtigungen über geänderte Werte erhalten, während derselbe Code auf einem anderen Gerät funktioniert hat, mit dem ich getestet habe.

Ein Neustart des Geräts löste alle Probleme. Offensichtlich, aber wenn Sie in dem Problem sind, vergessen Sie zu denken.

0
Christian