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:
BluetoothDevice#connectGatt(...)
. If it takes longer than 1 second to connect, it means that the connection is "stuck" and therefore it won't connect, so BluetoothDevice#connectGatt(...)
is called again. This is done with a limit of 5 attempts.onConnectionStateChange(...)
is called with newState
CONNECTED and begins the services discovery.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:
onConnectionStateChange(...)
is never called. When the problem ends, onConnectionStateChange(...)
is called the number of times that the app tried to connect. For example, if BluetoothDevice#connectGatt(...)
is called 15 times, onConnectionStateChange(...)
is called 15 times with newState
equal to DISCONNECTED. This is curious because never in any of those connection attempts the status changed to CONNECTED. 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.
Create a new project Open Android Studio and you should be greeted with the following screen. Select Import an Android code sample. On the next screen select the sample Bluetooth Le Gatt under Connectivity. This project will set us up with a framework to build off of for our application.
x protocol SPEC, when the iPhone/Android phone play as role of BLE central mode, the limitation to have active connection at the same time is up to 8 devices.
Android provides built-in platform support for Bluetooth Low Energy (BLE) in the central role and provides APIs that apps can use to discover devices, query for services, and transmit information. Common use cases include the following: Transferring small amounts of data between nearby devices.
If someone is having a similar issue, the problem was finally solved by changing the BLE chip used by the peripheral (arduino). Before that change, a workaround I found was turning off and on the BLE after each connection. The solution was not perfect, but improved the connection rate a lot.
Android Bluetooth needs to be recycled occasionally, have you tried restarting the BLE on the device when you encounter this timeount?
Here's a snippet I've used to restart the BLE when strange things start happening.
static Handler mHandler = new Handler();
public static void restartBle() {
final BluetoothManager mgr = (BluetoothManager) ApplicationBase.getAppContext().getSystemService(Context.BLUETOOTH_SERVICE);
final BluetoothAdapter adp = mgr.getAdapter();
if (null != adp) {
if (adp.isEnabled()) {
adp.disable();
// TODO: display some kind of UI about restarting BLE
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!adp.isEnabled()) {
adp.enable();
} else {
mHandler.postDelayed(this, 2500);
}
}
}, 2500);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With