I have already implemented Bluetooth low energy device scanning logic, but I'm struggling a little bit with BluetoothGatt service usage. What I want to do is to connect to each of my ESP32 devices sequentially from my Android phone, so that I can receive the data and then continue to the next device (and disconnect from previous). So there will be only one active connection between ESP32 and Android phone. The ESP32 is already programmed so if android phone connects to it using BluetoothGatt then it sends data (max 20 bytes).
The real struggle is to understand how to manage these connections to properly close/release the resources.
What would be the correct/simplest way to manage these BluetoothGatt
connections?
My implementation basically connects to a new device Gatt service when the sent data is "end"
. The problem is that this works if one ESP32 device is active. If more devices are active then something happens and the data is not received from Gatt service.
Here is part of my implementation (sorry, I couldn't reduce the code size more):
1) I use BLE scanner to discover new devices
private Handler _leScanHandler = new Handler();
private final BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
final int newRSSI = rssi;
_leScanHandler.post(new Runnable() {
@Override
public void run() {
addDeviceToListOnLEScan(device, newRSSI);
}
});
}
};
2) And the called method basically manages the BluetoothGatt
conenctions..
public void addDeviceToListOnLEScan(BluetoothDevice device, int rssi) {
// Gets only user defined devices
boolean isUserDefinedDevice = _definedDevicesHashMap.containsKey(device.getAddress());
if (!isUserDefinedDevice) {
return;
}
// Adds device and updates 'LastModifiedTime = Date(System.currentTimeMillis())'
addOrUpdateDevice(device.getAddress(), _scannedDevices);
// Called only once on each connect button press to enable gatt operations
if (!_isInitialConnectionHasBeenMade) {
_isDataSendingCompleteFromCurrentGatt = true;
_isInitialConnectionHasBeenMade = true;
}
// Sequential BLE device connect/disconnect operations
if (_isDataSendingCompleteFromCurrentGatt) {
BluetoothGatt previousGatt = _definedDevicesHashMap(previousAddress);
if (previousGatt != null) {
previousGatt.disconnect(); // ?
}
BluetoothGatt nextGatt = _definedDevicesHashMap(nextAddress);
if (/* Checks if 'nextAddress' is in _scannedDevices */
/* And whether device 'IsActive()' */) {
if (nextGatt == null) {
nextGatt = connectToDeviceGattService(nextGatt)
}
else {
// Do something here ?
}
_isDataSendingCompleteFromCurrentGatt = false;
}
}
}
3) The following variables/classes that I'm using
private boolean _isDataSendingCompleteFromCurrentGatt = false;
private boolean _isInitialConnectionHasBeenMade = false;
private HashMap<String, BluetoothGatt> _definedDevicesHashMap;
_definedDevicesHashMap.put("ff:ff:9f:c8:c2:93", null);
_definedDevicesHashMap.put("ff:ff:9f:c8:c4:91", null);
...
private HashMap<String, MyBLEDevice> _scannedDevices;
public class MyBLEDevice
{
private final int deviceInactivityTimeout = 10;
private String MacAddress;
private Date _lastModifiedDate;
public boolean isDeviceActive() {
// Just gets the time difference (DateNow - lastModified) / 1000 < 10s
}
}
4) Method which I use to connect to device
public BluetoothGatt connectToDeviceGattService(BluetoothGatt currentGatt, BluetoothDevice device, BluetoothGattCallback callback) {
_bluetoothAdapter.cancelDiscovery();
if (currentGatt == null) {
currentGatt = device.connectGatt(_activity, true, callback);
}
else {
// Is anything here needed ?
}
return currentGatt;
}
5) The BluetoothGatt
callback
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
// Discover services ?
} else if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_DISCONNECTED) {
// Do nothing ?
} else if (status != BluetoothGatt.GATT_SUCCESS) {
// Disconnect from current BluetoothGatt instance?
// Also close the connection ?
_isDataSendingCompleteFromCurrentGatt = true;
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
enableGattConfigurationCharacteristic();
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
_gattServiceHandler.post(new Runnable() {
@Override
public void run() {
handleMessage(Message.obtain(null, MSG_CHARACTERISTIC_CHANGE, characteristic));
}
});
}
};
6) And finally, the handler which is used to receive data from Gatt callback
private static final int MSG_CHARACTERISTIC_CHANGE = 0;
private Handler _gattServiceHandler = new Handler();
private void handleMessage(Message msg) {
BluetoothGattCharacteristic characteristic;
switch (msg.what) {
case MSG_CHARACTERISTIC_CHANGE: {
characteristic = (BluetoothGattCharacteristic) msg.obj;
if (BLELogic.PROPERTY_NOTIFY_CHAR_UUID.equals(characteristic.getUuid())) {
String notification = BluetoothExtension.getCharacteristicValue(characteristic);
if (notification.equals("end")) {
_isDataSendingCompleteFromCurrentGatt = true;
} else {
UpdateViewOnGattNotificationSent(notification);
}
}
}
default:
return;
}
}
At the beginning I wanted to simplify all the logic, but it looks like when using BLE/Gatt service connections, there is nothing simple about this.