4
votes

Background:

I have a BLE peripheral with two modes: "Application" and "Bootloader". In both modes, the device advertises with the same MAC address.

To switch from one mode to the other, the BLE peripheral must reboot itself. In doing so, it has to disconnect any active BLE connection.

The BLE peripheral only stays in Bootloader mode for about 5 seconds. If nobody connects to it within that window, it switches to Application mode.

The Problem:

Android takes a very long time to reconnect to the BLE device, long enough that I'm missing the 5 second window. The raw code has a few layers down to the BluetoothGATT and BluetoothAdapter layers, but the sequence of calls boils down to:

BluetoothGattCharacteristic c = mCharacteristics.get(POWER_STATE_UUID);
c.setValue(SHUTDOWN_VALUE);
mBluetoothGatt.writeCharacteristic(c);
// Signalled by BluetoothGattCallback.onCharacteristicWrite
bleWriteCondition.await();

mBluetoothGatt.disconnect();
// Wait for the underlying layer to confirm we're disconnected
while( mConnectionState != BluetoothProfile.STATE_DISCONNECTED ) {
    // Signalled by BluetoothGattCallback.onConnectionStateChange
    bleStateCondition.await(); 
}
mBluetoothGatt.connect();
while (mConnectionState != BluetoothProfile.STATE_CONNECTED) {
    // Signalled by BluetoothGattCallback.onConnectionStateChange
    bleStateCondition.await();
    if (bleStateCondition.stat != 0) {
        break;
    }
}

Am I going about this entirely the wrong way? I've tried calling close() on the BluetoothGatt instance, then generating a new one with BluetoothDevice.connectGatt, but I get the same extremely slow behavior.

I'm testing on a Samsung Galaxy S4, API level 21.

1
How often does the device advertise?323go
In bootloader mode, once every 100ms. In application mode, once per second.James Whong
did you solve this problem? I'm seeing that after losing connection to a ble device, it takes exactly 20 seconds to reconnect to the same peripheral, even if you manually call disconnect(), close(), connect(), regardless of the using direct or backgroundMark Ch
I managed a work-around that works for some versions of Android. I installed a delay in the reboot command of the peripheral, so that the Android device could initiate the disconnect. So the peripheral receives the reboot command, but doesn't actually reboot until the Android side closes the connection. Android is then OK with re-connecting to the peripheral. Obviously this only works if you have access to the firmware of your peripheral, but I hope it helps.James Whong
@James thanks that is an interesting way to do it. I've added in a 0.5 sec delay before peripheral reboot, and now the peripheral actually has time to disconnect properly before the power is cycled. At some lower layer the disconnect routine must include notifying the other end of the link, because now the Android device disconnects immediately and is able to reconnect quickly. ThanksMark Ch

1 Answers

3
votes

The problem here is that the gatt connect call issues a background connection request. It can take quite a long time for this call to result in a connection. A description of the two types of connection request is here : Direct vs Background connections

The absolute fastest way to get a connection is to do a scan and upon finding your device issue a direct connection request to it. As the scan has just found it, you know it is there and the connection will complete quickly. This is more complicated than your example code, but will be most effective given your small window. A scan is the most aggressive way to find a device. However, if you already have the device object, you could just call a direct connection request on the device.

Scans are issued using code like this :

scanner = bluetoothAdapter.getBluetoothLeScanner();
settings = new ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .build();
filters = new ArrayList<ScanFilter>();
ScanFilter uuidFilter = new ScanFilter.Builder()
            .setServiceUuid(YOUR_SERVICE_UUID).build();
filters.add(uuidFilter);
scanner.startScan(filters, settings, myScanCallback);

Upon finding your device (using the scan callback), issue a direct connection request via this method call :

myGatt = myDevice.connectGatt(this, false, myGattCallback);

The key part being the parameter of false. The connection request will time out in around 30s if the device is not found.