The BluetoothLeGatt Android BLE example contains the following code:
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
// This is specific to Heart Rate Measurement.
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
My question is basically, why is the marked code specific to Heart Rate Measurement? It seems like having a Client Characteristic Config Descriptor (CCCD) characteristic is the standard way to control characteristic notification, so why doesn't setCharacteristicNotification() take care of writing to it? And since it doesn't do that, what does setCharacteristicNotification() actually do?
I'm pretty new to BLE and there aren't any explanations of it on the internet that don't assume that you already understand it all! So don't assume I know what a CCCD or whatever is! It was difficult enough finding out what CCCD even stands for!
Edit: See also this answer which supports my understanding of CCCDs (and makes me continue to wonder why you have to write to them manually in Android when there is a function that looks like it should do that for you): https://devzone.nordicsemi.com/index.php/what-does-cccd-mean
I think is a litte bit late for give an answer but today I had the same doubt and I found a clear answer.
Using setCharacteristicNotification() you enable notification localy (on android device) and setting CCC descriptor to ENABLE_NOTIFICATION_VALUE you enable notification on ble peripheral. In fact for enabling CCC notification you have to use setValue() and writeDescriptor() that are methods used for writing characteristics (in this case characteristics descriptors) to remote device.
I found this on: http://processors.wiki.ti.com/index.php/SensorTag_User_Guide
Here is an excerpt from the O'Reilly book "Getting Started With Bluetooth Low Energy":
To enable notifications on Android, you normally have to locally
enable the notification for the particular characteristic you are
interested in.
Once that’s done, you also have to enable notifications on the peer
device by writing to the device’s client characteristic configuration
descriptor (CCCD)
I believe this answers your question.
So
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
refers to the first part
and
mBluetoothGatt.writeDescriptor(descriptor);
refers to the 2nd.
For future peoples coming across this, here's the best answer I could find:
By writing to the Client Characteristic Config descriptor, you, the client, are telling the BLE server to switch configurations. (This made no sense to me either initially, but in english:)
This tells the BLE device to switch modes (configurations) to actively gather and report changes to this characteristic instead of changing and only reporting when requested.
It's poorly named, but digging through the docs it appears this is also going to be used for other possible characteristic changes that the client might request: hence the confusing name.
https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
This begs the question, why call BluetoothGatt.setCharacteristicNotification() if we're only going to duplicate our efforts by modifying the descriptor?! Digging through the BluetoothGatt source shows us that setCharacteristicNotification only prepares the local service to receive notifications, not to enable persistent updates.
I know it looks silly, but setting CCCD value is the only way you can tell the API whether you are going to turn on notification or indication.
Currently there is no setCharacteristicIndication. To enable indication, you have to call setCharacteristicNotification (confusing) and then write BluetoothGattDescriptor.ENABLE_INDICATION_VALUE to CCCD, similar to what you did to enable Notification.
All the other answers do not really answer the question.
My guess would be that the Android BLE team took the assumption an application could have more than one BluetoothGattCallback().
By splitting the notification enabling (and disabling) in two steps it would allow the BluetoothGattCallback observers to listen for GATT notification (only invoke setCharacteristicNotification()) - and leave only one BluetoothGattCallback implemention doing the write operations to the GATT server aka the BLE peripheral.
"The Client Characteristic Configuration descriptor defines how the characteristic may be configured by a specific client."
Related
Reading the documentation, one would think that setCharacteristicNotification enables notifications for a BLE characteristic:
Enable or disable notifications/indications for a given
characteristic.
But this method doesn't seem to do this? Reading the BLE documentation on receiving BLE notifications, it turns out to be a multi-step process where you have to call this method and then write a file into a descriptor.
If this is the case, then what does setCharacteristicNotification by itself do?
The descriptor write is needed in order to tell the remote device to send notifications. setCharactersticNotification only tells the Bluetooth stack that it should forward any received notification to the app.
Interesting read at Why does setCharacteristicNotification() not actually enable notifications? . The answerers there dig through the source code and docs to find that:
"setCharacteristicNotification only prepares the local service to receive notifications."
I would suggest a wrapper function for set notifications, because, in addition to the documentation being not-so-clear, it is a confusing concept to enable notification reception locally as well as enable notification sending on the peripheral. I would suggest something like what the kind answerers at Enabling Bluetooth characteristic Notification in Android (Bluetooth Low Energy ) Not Working use:
public boolean setCharacteristicNotification(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic characteristic,boolean enable) {
Logger.d("setCharacteristicNotification");
bluetoothGatt.setCharacteristicNotification(characteristic, enable);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID);
descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : new byte[]{0x00, 0x00});
return bluetoothGatt.writeDescriptor(descriptor); //descriptor write operation successfully started?
}
On Android when you want to receive BLE notifications for characteristic changes you use something like the following:
BluetoothGattCharacteristic characteristic = ...
gatt.setCharacteristicNotification(characteristic, true);
BluetoothGattDescriptor descriptor =
characteristic.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
My question is: what is the gatt.setCharacteristicNotification() doing?
My assumption is that the above code represents:
Tell Android to pay attention for notifications from characteristic X
Tell the BLE device to start broadcasting change notifications for X
It seems logical, but I haven't seen it documented anywhere - instead the docs simply tell you to do what I wrote above. I'd like to confirm the purpose of setCharacteristicNotification() in this process.
I have the following problem - I must change the descriptor (BluetoothGattDescriptor) in my characteristic (BluetoothGattCharacteristic) dynamically as my application works.
Unfortunately function notifyCharacteristicChanged() from BluetoothGattServer updates only value of the characteristic (looked up this function in the Andoird sources - https://github.com/android/platform_frameworks_base/blob/master/core/java/android/bluetooth/BluetoothGattServer.java), it ignores the descriptors.
My descriptor is read only once - when the connection is established. But I want to change it dynamically and reconnection every time is not an option here.
Adding two services or characteristics with the same UUID but different descriptors did not solve the problem - distinguishing them was not possible.
Anyone faced similar problem?
This is the code:
BluetoothGattCharacteristic bc = mBluetoothGattServer.getService(service).getCharacteristic(characteristic);
bc.setValue(data);
bc.getDescriptor(descriptor).setValue(new byte[]{0, 0}); <- no effect here
mBluetoothGattServer.notifyCharacteristicChanged(connectedDevice, bc, false);
Thank you for any advice!
What you want to do indicates your system suffers from bad design.
Characteristic descriptors are generally not meant to be changed over time by the Server's Host. Either way, in the spirit of BLE, it's only the Characteristic's value that can be notified to Clients.
The fact that you want to change it "dynamically" is just not right. Why do you?
If it has some important, meaningful data for your entire application, then create a new Characteristic.
A Characteristic Descriptor should only have the purpose of "explaining" how the Characteristic's value is supposed to be used, its format, its units etc.
using the android 4.4 BLE APIs on my Nexus7, i'm able to successfully interact with a peripheral BLE device -- connect, disconnect, read, write....
if however an active connection breaks for whatever reason (in this case, the peripheral is reset), i observe the following behavior....
my peripheral (by design) begins advertising after any active connection is terminated (for whatever reason); i can see this via my bluetooth packet sniffer....
i receive the onConnectionStateChanged callback as expected in my android app, at which point i invoke close() on my active BluetoothGatt instance; this is the same procedure i follow during a "normal" disconnect initiated from the client...
shortly after this, the android BLE stack tries to re-connect to the same peripheral; through the packet sniffer i can see the BLE connection request going out over the air...
my app, however, did not initiate this re-connection; indeed, i see no information from any bluetooth log suggesting this even happened!!!!
is there some "mode" in the BLE stack where it attempts to re-establish busted connections automatically???
thanks....
This happens on various Android phones whether the autoConnect flag is set to false or true.
Couldn't yet find a complete solution, it seems as the android BLE stack is spontaneously re-initiating the connection once it is getting the advertising signal again, just ignoring that it was the app that disconnected on purpose...
A partial solution may involve not using the BluetoothGatt.connect() method as explained here:
https://stackoverflow.com/a/23749770/4144487
So, a sample connect method can look like:
void connect(Context context) {
if (mGatt != null) {
mGatt.close();
}
mGatt = mDevice.connectGatt(context, false, callback);
}
To explain the importance of this issue, when it happens the peripheral thinks it is connected and my "real" app can't find it any more. At some phones like Galaxy S3 and Redmi note 3 I found that closing the bluetooth switch from the notification bar is "releasing" the peripheral and allowing me to discover the device. At others like Nexus 5x only a phone reboot will do the trick.
I've observed this happening if you use autoConnect=true when calling BluetoothGatt#connectGatt(). Generally I've found that it is best to use autoConnect=false, but with some devices you simply cannot connect unless you use true, so I usually do both. I try false first and if that fails then use true and then the behavior you're describing is something you simply have to work around.
i am trying to read temperature value from health profile.health thermometer service. according to official google ble devlopment page when i try to notify a health thermometer characteristic to read temperature i try to write descriptor value (Client Characteristic Configuration) using writeDescriptor a callback method of writeDescriptor return status 5 according to google it's GATT_INSUFFICIENT_AUTHENTICATION.
so i comment the code of writedescriptor and try to call the method mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); but onCharacteristicChanged method never called. when i talk to my ios devloper team. they said that they never write a descriptor value they just set the notification to true and they get temperature value from the same chip.
to write a descriptor value is necessary for android developer to get notification ???
so at last i try to run the official bluetooth app from the bluetooth.com site for test purpose they just disable all three buttons notify read and write???
thank you and sorry for my English
[UPDATE]
find out some log which may cause some bonding issues please help me if you have any solution for that.
Short-Term Key generated still log as error
11-08 11:26:44.392: E/bt-smp(1014): STK Generated
after that bond state change dramatically may this cause insufficient authentication
11-08 11:26:49.437: I/BluetoothBondStateMachine(1014): bondStateChangeCallback: Status: 0 Address: 00:16:A4:C0:FF:EE newState: 2
11-08 11:26:49.437: D/BtGatt.btif(1014): btif_gattc_upstreams_evt: Event 9
11-08 11:26:49.437: E/BluetoothBondStateMachine(1014): In stable state, received invalid newState: 12
[UPDATE]
after upgrading kitkat 4.4 on nexus 7 . add two method
device.createBond();
device.setPairingConfirmation(true);
works for me now i can read the encrypted characteristic. but still unstable. but some success i got.
Yes, in Android for enabling BLE notification, you must both call mBluetoothGatt.setCharacteristicNotification(characteristic, enabled) and write suitable value to descriptor 0x2902 of that characteristic.
I am not sure why you get GATT_INSUFFICIENT_AUTHENTICATION, this may be caused by the implementation of the thermometer.
EDIT :
From the new information in the comment and also the screen captured provided, there are a few things you may want to check :
The characteristic is a indication characteristic, but not a notification characteristic. The value you write to the descriptor should be BluetoothGattDescriptor.ENABLE_INDICATION_VALUE, but not BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE. Notice that you still need to turn on the indication by setCharacteristicNotification(). (Confusing terminology here, but it is necessary as per the docs)
For the unstable Bluetooth stack on Android, try to restart the Bluetooth, and turn of WiFi. This will increase the stability. (Although not 100% solving the problem)
In my case the problem was that BluetoothGatt object can accept only one pending operation. Solution was to do read/write operations in sequence, waiting for the completion callback of first before requesting second read/write operation.