I have a problem trying to connect to a peripheral. Sometimes the callback onConnectionStateChange(...)
is not called after BluetoothDevice#connectGatt(...)
. What I'm trying to achieve is fast and short connections triggered by user action.
This situation occurs about 1 every 10 times without specific prior action. It lasts about 20 to 30 seconds or until the application is killed and reopened. The normal sequence of steps I follow is:
- Scan devices to find the peripheral.
- Call
BluetoothDevice#connectGatt(...)
. If it takes longer than 1 second to connect, it means that the connection is "stuck" and therefore it won't connect, soBluetoothDevice#connectGatt(...)
is called again. This is done with a limit of 5 attempts. onConnectionStateChange(...)
is called withnewState
CONNECTED and begins the services discovery.- The rest of the operations are performed without problems.
- After disconnection
BluetoothGatt#close()
is called.
The problem occurs at point 3. Sometimes onConnectionStateChange(...)
is not called. I have noticed that most of the times the problem starts with a specific behavior. After calling BluetoothDevice#connectGatt(...)
, onConnectionStateChange(...)
is called with newState
CONNECTED, but almost immediately afterwards (~40 milliseconds) is called again with newStatus
DISCONNECTED. Due to the short time of the status change, I can deduce that the device does not even tried to make the connection and changed the state to DISCONNECTED.
The problem ends when:
- 20-30 seconds have passed. During this time
onConnectionStateChange(...)
is never called. When the problem ends,onConnectionStateChange(...)
is called the number of times that the app tried to connect. For example, ifBluetoothDevice#connectGatt(...)
is called 15 times,onConnectionStateChange(...)
is called 15 times withnewState
equal to DISCONNECTED. This is curious because never in any of those connection attempts the status changed to CONNECTED. - The app is killed and started again.
This error occurs in SDK18 and SDK 21.
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
String deviceName = device.getName();
if (deviceName == null) return;
Log.d("BLUETOOTH CONNECTION", "Device found: " + device.getName());
if (mMode == SCAN_MODE) {
mListener.deviceFound(device, rssi, scanRecord);
}
else {
mDevices.put(device.hashCode(), device);
stopScan();
// Samsung devices with SDK 18 or 19 requires that connectGatt is called in main thread.
mHandler.post(new Runnable() {
@Override
public void run() {
Log.d("BLUETOOTH CONNECTION", "Executing first device.connectGatt()");
BluetoothGatt gatt = device.connectGatt(mContext, false, mGattCallback);
retryIfNecessary(device, gatt);
mTryingToConnect = true;
}
});
}
}
private void retryIfNecessary(final BluetoothDevice device, final BluetoothGatt gatt) {
if (isRetryLimitReached()) {
Log.d("BLUETOOTH CONNECTION", "Try count limit reached");
finishConnection(gatt);
mRetryCount = 0;
mListener.error(TIMEOUT);
return;
}
mRetryCount++;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d("BLUETOOTH CONNECTION", "Check if it is frozen.");
if (isWorking()) {
Log.d("BLUETOOTH CONNECTION", "Frozen, create new connection.");
BluetoothGatt gatt = device.connectGatt(mContext, false, mGattCallback);
retryIfNecessary(device, gatt);
}
}
}, RETRY_INTERVAL_MS);
}
@Override
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
Log.d("BLUETOOTH CONNECTION", "On connection state changed. Device: "+ gatt.getDevice().getAddress());
if (!mConnected && BluetoothGatt.STATE_CONNECTED == newState) {
Log.d("BLUETOOTH CONNECTION", "Connected");
mTryingToConnect = false;
mTryingToDiscoverServices = true;
mConnected = true;
gatt.discoverServices();
}
else if(BluetoothGatt.STATE_DISCONNECTED == newState) {
Log.d("BLUETOOTH CONNECTION", "Disconnected and closing gatt.");
mConnected = false;
gatt.close();
if (!mConnectionFinished && mRetryCount == 0) {
finishConnection(gatt);
}
}
}
I think that the peripheral is not relevant, because the iOS app can always connect without this problem.
Any ideas? Thanks in advance.
Edit!
This answer say that:
Direct connection has interval of 60ms and window of 30ms so connections complete much faster. Additionally there can only be one direct connection request pending at a time and it times out after 30 seconds. onConnectionStateChange() gets called with state=2, status=133 to indicate this timeout.
So in this 30 seconds interval there is a pending connection request and times out at the second 30. It's unlikely but, is there anything I can do to make this time shorter? Or maybe there is an explanation for the connection failure that I am not seeing. Thanks.
EDIT 02/03/2016
A new information that may help. When the problem starts (when onConnectionStateChange(...)
is called with newState=DISCONNECTED
after ~40ms of being called with newState=CONNECTED
), the status is 62 = 0x03E. Looking here that status code means GATT_CONN_FAIL_ESTABLISH. When I detect this status I'm closing the gatt connection, but the problem persists. I also tried disconnecting and closing. Ideas? Thanks.