Android BLE API: GATT Notification not received

85,592

Solution 1

It seems like you forgot to write the Descriptor which tells your BLE device to go in this mode. See the code lines that deal with descriptor at http://developer.android.com/guide/topics/connectivity/bluetooth-le.html#notification

Without setting this descriptor, you never receive updates to a characteristic. Calling setCharacteristicNotification is not enough. This is a common mistake.

code snipped

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

Solution 2

@Boni2k - I have the same issues. In my case, I have 3 notifying characteristics and a handful of read/write characteristics.

What I did find is that there is some dependency between writeGattDescriptor and readCharacteristic. All of the writeGattDescriptors must come first and complete before you issue any readCharacteristic calls.

Here is my solution using Queues. Now I am getting notifications and everything else works fine:

Create two Queues like this:

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

Then write all of your descriptors immediately after discovery with this method:

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

and this callback:

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

The method for reading a characteristic normally then looks like this:

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

and my read callback:

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

Solution 3

When setting the value to the descriptor instead of putting descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE), put descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE). The callbacks for onCharacteristicChanged are called now.

Solution 4

I assume (you did not provide your source code) that you did not implement it as Google wanted:

(1)

mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

and then

(2)

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

I suppose 2 is missing. In that case I believe on low-level notification will be triggered but they will never be reported to application layer.

Solution 5

Experienced issues in earlier versions of Android receiving notifications (an indication that was registered) and always had a strange disconnect event afterwards. As it turns out, this was because we registered for notifications on five characteristics.

The error discovered in LogCat was:

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

Prior to 4.4.2, the number of registrations was capped at 4! 4.4.2 increased this limit to 7.

By reducing the number of registrations in earlier versions, we were able to step around this limitation.

Share:
85,592
Boni2k
Author by

Boni2k

Updated on July 05, 2022

Comments

  • Boni2k
    Boni2k almost 2 years

    Device used for testing: Nexus 4, Android 4.3

    Connection is working fine but the onCharacteristicChangedMethod of my callback is never called. However I am registering for notifications using setCharacteristicNotification(char, true) inside onServicesDiscovered and that function even returns true.

    Device log (there are actually no messages at all when notifications should appear / are sent via the Bluetooth device):

    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 Notifications work fine using iOS and the app basically does the same as on Android (registering for notification etc.).

    Has anyone else experienced this with a possible solution?

  • ZhangChn
    ZhangChn almost 11 years
    I have just tried the code from Android 4.3 examples but failed to notify. Could you please share the piece of codes related to notification? Thank you.
  • Boni2k
    Boni2k almost 11 years
    I've done that, too, even with your method (which returns true). Still no luck. I'm using the Descriptor UUID (descUuid) I found in logcat on the line D/BtGatt.GattService: onGetDescriptor() - address=C9:79:25:34:19:6C, status=0, descUuid=00002902-0000-1000-8000-00805f9b34fb
  • OneWorld
    OneWorld almost 11 years
    The UUID you found is the same as the one I am using. It's actually a UUID within the BLE standard. Check, if you get error status codes (other than 0) in your callbacks. E.g. connect(), onDescriptorWrite()
  • reTs
    reTs almost 11 years
    @Boni2k It seems a stupid question, but did you actually confirm that the characteristic has the descriptor 0x2902(using 16-bit here for convenience)
  • OneWorld
    OneWorld almost 11 years
    @reTs Your characteristic has to offer this descriptor (0x2902) to enable characteristic update notifications.
  • reTs
    reTs almost 11 years
    @OneWorld I know, I am just asking the OP to confirm that this descriptor really exists in the characteristic.
  • Boni2k
    Boni2k almost 11 years
    @OneWorld: Thanks for the hint. Indeed I am getting status 128 inside onDescriptorWrite, which indicates an error. Although I have no idea what that status code could mean...
  • OneWorld
    OneWorld almost 11 years
    @Boni2k GATT_NO_RESOURCES = -128 or 128; GATT_INTERNAL_ERROR = -127 or 129; GATT_ERROR = -123 or 133; GATT_ALREADY_OPEN = -115 or 141. I got them from the Samsung BLE Sdk guys. Those status codes seem to be equal to the Android BLE SDK except for having signed byte as type. The whole list is here: img-developer.samsung.com/onlinedocs/samsung_ble_docs_200/…
  • OneWorld
    OneWorld almost 11 years
    Take care of the synchronous nature of the BLE implementation in Android. You easily can cancel requests unwillingly, when not using such queues as you do. See stackoverflow.com/questions/18011816/… I'm not sure, if you are right with your assumption, that writeDescriptor() needs to be done prior readCharacteristic. Maybe your solution (queues) just takes care of the synchronous nature and fixes your problem that way. I actually do read and write characteristics before writing the descriptor.
  • miznick
    miznick over 10 years
    I do believe you are correct. The transactions are synchronous and the order is irrelevant. Any transaction must complete it's callback before another transaction is issued and everything will work properly. Might make sense to use one queue for all transactions and just tag each one with a type (write descriptor, read characteristic, write characteristic, etc.)
  • Boni2k
    Boni2k over 10 years
    Thanks. FWIW, here's the stupid mistake I made: ENABLE_INDICATION_VALUE had to be used instead of ENABLE_NOTIFCATION_VALUE...
  • jkraybill
    jkraybill over 10 years
    Can you clarify what "indication" is supposed to do? Based on the docs, I was sure that the right flag to set in this case was ENABLE_NOTIFICATION_VALUE but you're now the second person on this page who has suggested that ENABLE_INDICATION_VALUE is the right flag. What is the difference and use case for each?
  • Boni2k
    Boni2k over 10 years
    It depends on the Bluetooth Profile implementation inside the bluetooth device. It either uses "Notification" or "Indication" for posting updates. So you'll have to find out which one your device uses.
  • user2926265
    user2926265 over 10 years
    Like Boni2k said, it depends on the Bluetooth Profile. If your service follows the standard profiles you should be able to find that information [here] (developer.bluetooth.org/gatt/services/Pages/ServicesHome.as‌​px). If you want to know this programatically, you can call the getProperties() method for your characteristic and do a bitwise AND against the property you want to check if it's different than 0, then it supports that activity.
  • phreakhead
    phreakhead over 10 years
    hmmm I tried this but still no updates, whether I set ENABLE_NOTIFICATION_VALUE or ENABLE_INDICATION_VALUE. Do you know how iOS does this? My device works fine in iOS and you don't have to write any descriptors, so I'm wondering if they do it in the background or something?
  • Brian Reinhold
    Brian Reinhold about 10 years
    What bothers me here is that the Android documentation states that setCharacteristicNotifications works for EITHER notifications or indications. So I assume they are checking the properties internally and writing the correct flags to the characteristic. They also use the method in their example which has worked with some HeartRate monitors. Do you know if the method is flawed? My understanding is that one cannot set BOTH indication/notification flags though some devices may accept that. IF they are doing that then I would say their method IS wrong.
  • Brabbeldas
    Brabbeldas over 9 years
    @OneWorld Do you know where to actually find this UUID in the BLE standard? Could you maybe provide us with an url? Thanks in advance!
  • OneWorld
    OneWorld over 9 years
    @Brabbeldas Sorry, I just can help you by providing the right search terms: The UUID of the descriptor is a 16bit UUID which also can be named "2902". You'll find plenty of docs as PDF on bluetooth.org/en-us/specification/adopted-specifications and browseable HTML content on developer.bluetooth.org/Pages/default.aspx
  • Deepak
    Deepak over 9 years
    your code is good to go , but here i am facing some problem to use this, their are some issues i am facing because i am new to BLE. Mean on readCharacteristicQueue and readQueue i am getting erros. Please attach some complete code to get rid of this error. Thanks
  • Thomas
    Thomas over 9 years
    Code looks ok in theory but sometimes I don't get onCharacteristicWrite callback so...
  • Timmmm
    Timmmm about 9 years
    Nordic have written a class to handle this called GattManager. See github.com/NordicSemiconductor/puck-central-android
  • swooby
    swooby about 9 years
    This quite nice Nordic GattManager code does an odd "Thread.sleep(1000)" in its GattSetNotificationOperation code (github.com/NordicSemiconductor/puck-central-android/blob/ma‌​ster/…). Is this sleep really necessary? I am seeing that if I comment out the sleep, I don't get the onCharacteristicChanged.
  • swooby
    swooby about 9 years
    I am seeing that I have to induce a delay (ex: Thread.sleep, or Handler.post) between the setCharacteristicNotification and the writeDescriptor. If I don't, I never get the onCharacteristicChanged. Can anyone confirm that they see something similar? Note that the example at developer.android.com/guide/topics/connectivity/… has a subtle "..." between the two calls, which I am seeing indicates that you can't reliably always do these two calls immediately back to back.
  • swooby
    swooby about 9 years
    If my previous comment is true: I don't see that "setCharacteristicNotification" itself has any callback, so can anyone think of any way other than a delay before calling writeDescriptor that can make this more reliable?
  • swooby
    swooby about 9 years
    I think it may have nothing to do with the delay between the commands; i think is it just the delay in general before any attempt to set the notification is even started. Looking at the code for setCharacteristicNotification, it doesn't appear to have the "only one gatt request at a time" limitation.
  • swooby
    swooby about 9 years
    I think it may have nothing to do with the delay between the commands; i think is it just the delay in general before any attempt to set the notification is even started. Looking at the code for setCharacteristicNotification, it doesn't appear to have the "only one gatt request at a time" limitation.
  • Glenn Maynard
    Glenn Maynard about 9 years
    It's not a mistake, it's a bug in the API. The whole point of setCharacteristicNotification is to enable notifications--it should be setting this.
  • Gregory Higley
    Gregory Higley over 8 years
    Capped at 4? Words cannot describe how pathetic this is. Even 7 is ludicrous.
  • Lars Blumberg
    Lars Blumberg over 8 years
    These additional necessary steps should really be documented. There are a too many and too heavy undocumented caveats that developers need to be aware of when implementing BLE communication on Android.
  • Jason Nelson
    Jason Nelson over 8 years
    We're working closely with Google on something similar... not entirely sure if it is identical to your issue, but we found that when Google's OS does the gatt.WriteDescriptor(), they're sending it as a WRITE_CMD and not a WRITE_REQUEST. I believe, although I could be mistaken, that they're planning to change it so that it does BOTH. We worked around this issue in the meantime by responding to either CMD or REQUEST for that message, however it requires us to update the firmware in our device.. and our customers need something other than android to do that over BLE or a USB cable
  • swooby
    swooby over 8 years
    Wow, I cannot believe that I am revisiting this issue over 9 months later. I am recently seeing, and had occasionally seen in the past, that "characteristic.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICA‌​TION_DESCRIPTOR_UUID‌​)" is returning null. if the descriptor is null then the given sequence is unusable. Can anyone think of what causes that to happen, or if there could be any workaround?
  • racs
    racs about 8 years
    @swooby Maybe try to scan the services again when this happens.
  • mesopotamia
    mesopotamia almost 8 years
    What do you mean by mGattInstances how can I get this instance?
  • Tom K
    Tom K about 5 years
    I found that after service discovery, a characteristic will have the 0x2902 "notify" descriptor attached as expected. However if I go and write some other characteristic first, that descriptor no longer appears in the getDescriptors() list and returns null. If, however, I locate the notify BluetoothGattDescriptor in onServicesDiscovered() and save it for later, I am able to come back to it later, set its value to enable notifications, and write the descriptor.
  • Chase Roberts
    Chase Roberts about 5 years
    Isn't there a case where you have 2 items in your readqeue and while it's doing a read, we add an item to the writeDescriptor queue, and then we are writing to the descriptor at the same time as reading from the readqueue?