Android BLE BluetoothGatt.writeDescriptor() return sometimes false
The documentation lacks information. However you can read the source code to find out the rules, which (currently) are the following:
For each BluetoothGatt
object, you can only have one outstanding request at a time, including requestMtu
, readCharacteristic
, writeCharacteristic
, readDescriptor
, writeDescriptor
and executeReliableWrite
. So if you issue a read request you need to wait for the read response before you issue a write request. While they implemented the code that returns false if there is an ongoing operation in BluetoothGatt.java, they forgot to do this for requestMtu
, so if you have multiple requests at a time where requestMtu
is one of them, you will get random errors sooner or later (in the latest versions at the time of this post).
So yes, every developer has to manually serialize the requests. Note that the Bluetooth stack actually has a queue of requests, but it is limited to only one request per client (i.e. BluetoothGatt object). So if two apps on the same phone talk to the same device simultaneously you will never get "busy" errors. The only exception is if you use Write Without Response for which the current data flow implementation is quite buggy (see https://issuetracker.google.com/issues/37121017 which Google seems to have ignored).
You can send notifications at the same time as you write a characteristic, since the server and client roles are separated.
Regarding updating the documentation, you can always try to file an issue at https://issuetracker.google.com (but I get the feeling nobody reads that), or, since Android is open source, send a pull request to https://android-review.googlesource.com/ which updates the Javadoc from which the documentation is generated.
gatt.writeDescriptor() returning false all the time:
I have fixed the issue.
Upon much debugging, I discovered that for some estrange reason (I'm not very experience with Kotlin or Android, so I don't know this reason), the method gatt.writeDescriptor()
returns 3 times, (in my case, at least). And only the last time does it return true
and the descriptor
actually gets written.
So because my code only checked whether it returned true
or false
the first time, it was obviously failing.
I have now modified my code, to make it wait until it returns true
which happens always on the third time it returns.
private suspend fun setNotification(
char: BluetoothGattCharacteristic,
descValue: ByteArray,
enable: Boolean
) {
val desc = char.getDescriptor(UUID_CLIENT_CHAR_CONFIG)
?: throw IOException("missing config descriptor on $char")
val key = Pair(char.uuid, desc.uuid)
if (descWriteCont.containsKey(key))
throw IllegalStateException("last setNotification() not finish")
if (!gatt.setCharacteristicNotification(char, enable))
throw IOException("fail to set notification on $char")
return suspendCoroutine { cont ->
descWriteCont[key] = cont
desc.value = descValue
while (!gatt.writeDescriptor(desc)) {
}
}
}
Now I have successfully subscribed for notifications, and can read data from the BLE Device without much issues.
Thanks to all who helped by offering some input, and I hope this will hopefully help someone in the future facing the same situation.
BluetoothGatt.writeCharacteristic return false half the time
Never use timeouts to try to workaround this issue. The proper way is to wait for the callback and then perform the next request. See Android BLE BluetoothGatt.writeDescriptor() return sometimes false.
BLE Data lost on reading characteristic when the device sends data frequently
Declared those lines before device.connectGatt()
AdvertiseSettings advertiseSettings = new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
.setConnectable(true)
.setTimeout(0)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
.build();
AdvertiseData advertiseData = new AdvertiseData.Builder()
.setIncludeDeviceName(true)
.setIncludeTxPowerLevel(false)
.addServiceUuid(new ParcelUuid(FP_SERVICE_UUID))
.build();
mBluetoothAdapter.getBluetoothLeAdvertiser().startAdvertising(advertiseSettings, advertiseData, new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
// Log.d(TAG, "BluetoothLeAdvertiser, onStartSuccess --> $isConnectable : " + settingsInEffect.isConnectable());
}
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
// Log.d(TAG, "BluetoothLeAdvertiser, onStartSuccess --> errorCode : " + errorCode);
}
});
...
bluetooth ble writecharacteristics return write_empty ondeviceresult
You can't have multiple outstanding GATT requests at a time. Now you call writeDescriptor followed by writeCharacteristic without waiting for onDescriptorWrite.
Related Topics
Bufferoverflowexception When Building Application
Why Does Navigation Not Work in the Navigation Drawer Activity Template with Version 2.4.1
How to Detect When the Notification/System Bar Is Opened
How to Solve Nullpointerexception Error in Android
Get Response Status Code Using Retrofit 2.0 and Rxjava
Android Design Considerations: Asynctask VS Service (Intentservice)
Android:How to Upload .Mp3 File to Http Server
Java.Lang.Illegalstateexception: the Specified Child Already Has a Parent
Android Layout Animations from Bottom to Top and Top to Bottom on Imageview Click
Change Navigationview Items When User Is Logged
Android Options Menu Icon Won't Display
Using Intent.Action_Pick for Specific Path
How to Import Contacts from Phonebook to Our Application
Call Getlayoutinflater() in Places Not in Activity
How to Allocate These Folders in Another Place
Advantages of Using Bundle Instead of Direct Intent Putextra() in Android