I have a bluetooth headset which is paired with my Nexus 5X (running Android 7.1) and I would like to connect to a GATT Server of the headset. I tried it with the following code:
private BluetoothGattCallback btleGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.d(TAG, "onConnectionStateChange: " + status + ", " + newState);
if(newState == STATE_CONNECTED) {
Log.d(TAG, "Device connected");
boolean ans = gatt.discoverServices();
Log.d(TAG, "Discover Services started: " + ans);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.d(TAG, "Number of Services: " + gatt.getServices().size());
}
};
public void onDeviceClicked(BluetoothDevice device) {
BluetoothGatt gatt = device.connectGatt(this, false, btleGattCallback);
Log.d(TAG, "Connected to GATT: " + gatt.connect());
}
If I click on the headset in my UI onDeviceClicked
is called and it comes to this Log output:
<!-- language: lang-none -->
Connected to GATT: true
onConnectionStateChange: 0, 2 // GATT_SUCCESS, STATE_CONNECTED
Device connected
Discover Services started: true
As you can see onServicesDiscovered
is never fired. I tried to call connectGatt
with TRANSPORT_LE
(ref) but then I get a onConnectionStateChange: 133, 0
. I also found this question which is why I added the gatt.connect()
method as mentioned in answer two.
Do you have any ideas why I don't get the onServicesDiscovered
callback?
Something that has been really useful for me is to wait for about 600ms after the connection has been established and then start the service discovery.
BLE on Android can be a little finicky.
Make sure you are calling mBluetoothGatt.discoverServices() on the UI thread.
if(newState == STATE_CONNECTED) {
Log.d(TAG, "Device connected");
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
boolean ans = mBluetoothGatt.discoverServices();
Log.d(TAG, "Discover Services started: " + ans);
}
});
}
Also try making BluetoothGatt gatt
a field variable instead of a local variable.
If you are doing any significant work, try using a library that masks all of the idiosyncrasies so you can focus on the high level logic. https://github.com/Polidea/RxAndroidBle.
Here is an example of how to read a characteristic.
connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(characteristicUuid))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> {
readOutputView.setText(new String(bytes));
readHexOutputView.setText(HexString.bytesToHex(bytes));
writeInput.setText(HexString.bytesToHex(bytes));
}, this::onReadFailure);
Or with Java 7 syntax
connectionObservable
.flatMap(new Func1<RxBleConnection, Observable<byte[]>>() {
@Override
public Observable<byte[]> call(RxBleConnection rxBleConnection) {
return rxBleConnection.readCharacteristic(characteristicUuid);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<byte[]>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
onReadFailure(e);
}
@Override
public void onNext(byte[] bytes) {
readOutputView.setText(new String(bytes));
readHexOutputView.setText(HexString.bytesToHex(bytes));
writeInput.setText(HexString.bytesToHex(bytes));
}
});
This may help
I will explain in two steps : connecting and discovering services
connecting:
connect from mainthread
set auto reconnect to false
if Version greater than or equals to M , set Tranposrt type
else directly use reflection and handle it properly
Handler(applicationContext.mainLooper).post {
Log.d(TAG, " Post is called inside mainlooper")
mBluetoothGatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, " Is Or Greater than M $mBluetoothDevice")
mBluetoothDevice!!.connectGatt(this, false,
onGhattListener, TRANSPORT_LE)
} else {
Log.d(TAG, " Less than M")
try {
Log.d(TAG, " Trying TRANPORT LE with reflection")
val m = mBluetoothDevice!!.javaClass.getDeclaredMethod("connectGatt", Context::class.java, Boolean::class.javaPrimitiveType, BluetoothGattCallback::class.java, Int::class.javaPrimitiveType)
m.isAccessible = true
val transport = mBluetoothDevice!!.javaClass.getDeclaredField("TRANSPORT_LE").getInt(null)
m.invoke(mBluetoothDevice, this, false, onGhattListener, transport) as BluetoothGatt
} catch (e: Exception) {
e.printStackTrace()
Log.d(TAG, " Catch to call normal connection")
mBluetoothDevice!!.connectGatt(this, false,
onGhattListener)
}
}
Log.d(TAG, "mBluetooth gatt is $mBluetoothGatt")
mBluetoothGatt?.let {
refreshDeviceCache(mBluetoothGatt!!)
}
}
disover services : in onGhattListener , if device is connected fire discoverServices()
from main thread
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int,
newState: Int) {
Log.d(TAG, "onConnectionStateChange $gatt and status $status and newstate $newState")
when (newState) {
BluetoothGatt.STATE_CONNECTED -> {
Handler(Looper.getMainLooper()).post {
gatt.discoverServices()
}
}
BluetoothGatt.STATE_DISCONNECTED -> {
}
BluetoothGatt.STATE_CONNECTING -> {
}
BluetoothGatt.STATE_DISCONNECTING -> {
}
}
}
this may solve your problem
call refresh method with reflection
fun refreshDeviceCache(gatt: BluetoothGatt): Boolean {
try {
val localMethod = gatt.javaClass.getMethod("refresh")
if (localMethod != null) {
return localMethod.invoke(gatt) as Boolean
}
} catch (localException: Exception) {
Log.e(TAG, "An exception occured while refreshing device")
localException.printStackTrace()
}
return false
}
In fact,I solve this problem by Execution the method mBluetoothGatt.discoverServices() several times(10 or more),
int i = 10;
while (i > 0)
{
if (!mIsBLE_Finded) //如果服务发现失败,继续执行discoverServices方法
{
i--;
mBluetoothGatt.discoverServices();
System.out.println("BLEService-->" + "尝试次数:" + i);
}
else //在10次的尝试中,存在某次服务发现成功了
{
i = -1;
}
}
Had the same problem, but waiting 600 ms wasn't enough. This is probably due to the BLE module used. I fixed the problem by calling my method
discoverServices();
after calling
device.connectGatt(this,false,gattCallback)
I'm basically just calling discoverServices every 5 seconds (this is arbitrarily chosen)
private void discoverServices() {
if(!gattConnected) { //just a boolean
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
gatt.discoverServices();
}
});
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
discoverServices();
}
}, 5000);
}
}
In the onServicesDiscovered(...) method of my gattCallback I make gattConnected true.
This worked for me.