4
votes

I am trying to write and android application that scans for BLE devices and when it finds the certain devices with a naming scheme it connects to it and reads a characteristic (A user defined name for the device) then disconnects right after. It would then display the device in a list with any other devices found and read the user defined name. The user can then chose a device to connect to (Or multiple devices) and connect to it and stream data from it.

The problem that keeps happening is after it gets the user defined name and is disconnected the BLE devices stop broadcasting and I can no longer find it when I scan or if I try to connect to it after I read the user defined name and disconnected to it.

Is this an issue with the Android BLE stack or do I need to add more delays (I have 100 millisecond delays throughout the bluetoothservice I use)

Here is part of the code I use in my service

 public boolean initialize() {

    Log.i(TAG, "Initializing");
    try {
        synchronized (Thread.currentThread()) {
            Thread.currentThread().wait(100);
        }
    }catch(InterruptedException e){
        //ignore
    }
    if (mBluetoothManager == null) {
        mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
        if (mBluetoothManager == null) {
            Log.e(TAG, "Unable to initialize BluetoothManager.");
            return false;
        }
    }

    mBluetoothAdapter = mBluetoothManager.getAdapter();
    if (mBluetoothAdapter == null) {
        Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
        return false;
    }


    mReadyToWrite = true;
    mReadyToRead = true;
    mReady = true;
    mCharacteristicWriteQueue   = new ArrayDeque<BluetoothGattCharacteristic>();
    mCharacteristicReadQueue    = new ArrayDeque<BluetoothGattCharacteristic>();
    mDescriptorWriteQueue       = new ArrayDeque<BluetoothGattDescriptor>();
    mDescriptorReadQueue        = new ArrayDeque<BluetoothGattDescriptor>();

    //mBluetoothGattMap = new HashMap<String, BluetoothGatt>();
    return true;
}

/**
 * Connects to the GATT server hosted on the Bluetooth LE device.
 *
 * @param address The device address of the device.
 *
 * @return Return true if the connection is initiated successfully. The connection result
 *         is reported asynchronously through the
 *         {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
 *         callback.
 */
public boolean connect(final String address) {
    if (mBluetoothAdapter == null || address == null) {
        Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
        return false;
    }



    if(mBluetoothGattMap.containsKey(address)) {
        Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
        if (mBluetoothGattMap.get(address).connect()) {
            mConnectionState = STATE_CONNECTING;
            return true;
        } else {
            return false;
        }
    }

    final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
    if (device == null) {
        Log.w(TAG, "Device not found.  Unable to connect.");
        return false;
    }

    try {
        synchronized (Thread.currentThread()) {
            Thread.currentThread().wait(100);
        }
    }catch(InterruptedException e){
        //ignore
    }
    mBluetoothGattMap.put(address, device.connectGatt(this, false, mGattCallback));


    Log.d(TAG, "Trying to create a new connection to address " + address);
    //mBluetoothDeviceAddress = address;
    mConnectionState = STATE_CONNECTING;
    return true;
}

/**
 * Disconnects an existing connection or cancel a pending connection. The disconnection result
 * is reported asynchronously through the
 * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
 * callback.
 */
public void disconnect(String address) {
    if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }
    Log.i(TAG, "Disconnecting from gatt");
    try {
        synchronized (Thread.currentThread()) {
            Thread.currentThread().wait(100);
        }
    }catch(InterruptedException e){
        //ignore
    }
    mBluetoothGattMap.get(address).disconnect();
}


public void close(String address) {
    try {
        synchronized (Thread.currentThread()) {
            Thread.currentThread().wait(100);
        }
    }catch(InterruptedException e){
        //ignore
    }
    mBluetoothGattMap.get(address).close();
    mBluetoothGattMap.remove(address);
    Log.w(TAG, "Succeeed removing it");
}

public int getConnectionState(String address) {
    Log.i(TAG, "getting connection state for " + address);
    BluetoothGatt gatt = mBluetoothGattMap.get(address);
    return mBluetoothManager.getConnectionState(gatt.getDevice(), BluetoothProfile.GATT);
}

/**
 * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
 * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
 * callback.
 *
 * @param characteristic The characteristic to read from.
 */
public void readCharacteristic(String address, BluetoothGattCharacteristic characteristic) {
    Log.i(TAG, "reading characteristic");
    if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }
    try {
        synchronized (Thread.currentThread()) {
            Thread.currentThread().wait(100);
        }
    }catch(InterruptedException e){
        //ignore
    }
    if(mReadyToRead && mReady) {
        boolean result = mBluetoothGattMap.get(address).readCharacteristic(characteristic);
        mReadyToRead = false;
        mReady = false;
        if(!result) {
            Log.i(TAG, "read failed");
        }
    }else {
        mCharacteristicReadQueue.push(characteristic);
    }
}

public void writeCharacteristic(String address, BluetoothGattCharacteristic characteristic) {
    Log.i(TAG, "writeCharacteristic - readyToWrite = " + mReadyToWrite + " queue size = " + mCharacteristicWriteQueue.size());
    if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }
    try {
        synchronized (Thread.currentThread()) {
            Thread.currentThread().wait(100);
        }
    }catch(InterruptedException e){
        //ignore
    }
    if(mReadyToWrite && mReady) {
        boolean result = mBluetoothGattMap.get(address).writeCharacteristic(characteristic);
        mReadyToWrite = false;
        mReady = false;
        if(!result) {
            Log.i(TAG, "characteristic write failed");
        }
    }else {
        mCharacteristicWriteQueue.push(characteristic);
    }
}

public void readDescriptor(String address, BluetoothGattDescriptor descriptor) {
    Log.i(TAG, "reading descriptor");
    if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }
    try {
        synchronized (Thread.currentThread()) {
            Thread.currentThread().wait(100);
        }
    }catch(InterruptedException e){
        //ignore
    }
    if(mReadyToRead && mReady) {
        boolean result = mBluetoothGattMap.get(address).readDescriptor(descriptor);
        mReadyToRead = false;
        mReady = false;
        if(!result) {
            Log.i(TAG, "descriptor read failed");
        }
    }else {
        mDescriptorReadQueue.push(descriptor);
    }
}

public void writeDescriptor(String address, BluetoothGattDescriptor descriptor) {
    Log.i(TAG, "writing descriptor for characteristic " + descriptor.getCharacteristic().getUuid().toString());
    if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }
    try {
        synchronized (Thread.currentThread()) {
            Thread.currentThread().wait(100);
        }
    }catch(InterruptedException e){
        //ignore
    }
    if(mReadyToWrite && mReady) {
        boolean result = mBluetoothGattMap.get(address).writeDescriptor(descriptor);
        mReadyToWrite = false;
        mReady = false;
        if(!result) {
            Log.i(TAG, "descriptor write failed");
        }
    }else {
        mDescriptorWriteQueue.push(descriptor);
    }
}

public BluetoothGattCharacteristic getCharacteristic(String address, UUID uuid) {
    if(!mBluetoothGattMap.containsKey(address)) {
        Log.i(TAG, "Device address " + address + " not found");
        return null;
    }
    try {
        synchronized (Thread.currentThread()) {
            Thread.currentThread().wait(100);
        }
    }catch(InterruptedException e){
        //ignore
    }
    for(BluetoothGattService service : mBluetoothGattMap.get(address).getServices()) {
        Log.i(TAG, "Service: " + service.getUuid().toString());
        for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
            Log.i(TAG, "Characteristic: " + characteristic.getUuid().toString());
            if(characteristic.getUuid().equals(uuid)) {
                return characteristic;
            }
        }
    }
    Log.i(TAG, "Characteristic not found");
    return null;
}

public Set<String> getConnectedDevices(){
    return this.mBluetoothGattMap.keySet();
}

/**
 * Enables or disables notification on a give characteristic.
 *
 * @param characteristic Characteristic to act on.
 * @param enabled If true, enable notification.  False otherwise.
 */
public void setCharacteristicNotification(String address, BluetoothGattCharacteristic characteristic, boolean enabled) {
    if (mBluetoothAdapter == null || !mBluetoothGattMap.containsKey(address)) {
        Log.w(TAG, "BluetoothAdapter not initialized");
        return;
    }
    try {
        synchronized (Thread.currentThread()) {
            Thread.currentThread().wait(100);
        }
    }catch(InterruptedException e){
        //ignore
    }
    mBluetoothGattMap.get(address).setCharacteristicNotification(characteristic, enabled);
    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(GattAttributes.CLIENT_CHAR_CONFIG));
    if(descriptor != null) {
        boolean status = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        Log.i(TAG, "descriptor " + descriptor.getUuid().toString() + " setValue() status: " + status);
        Log.i(TAG, "descriptor value: " + descriptor.getValue());
        writeDescriptor(address, descriptor);
    }
}

public void setPhoneEvents(byte priorities) {
    for(String address : mBluetoothGattMap.keySet()) {
        BluetoothGattCharacteristic characteristic = getCharacteristic(address, UUID.fromString(GattAttributes.ALERT_ATTRIBUTE));
        if (characteristic != null) {
            byte prioritiesBuf[] = new byte[1];
            prioritiesBuf[0] = priorities;
            characteristic.setValue(prioritiesBuf);
            writeCharacteristic(address, characteristic);
            Log.i(TAG, String.format("Forwarded phone alert priorities: 0x%X", priorities));
        } else {
            Log.e(TAG, "Failed to get the Alert ID characteristic from Gatt Server for device address " + address);
        }
    }
}

/**
 * Retrieves a list of supported GATT services on the connected device. This should be
 * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
 *
 * @return A {@code List} of supported services.
 */
public List<BluetoothGattService> getSupportedGattServices(String address) {
    if (mBluetoothGattMap.get(address) == null) return null;

    return mBluetoothGattMap.get(address).getServices();
}
2
Did you ever fix this? I'm having a similar issue... I disconnect the BluetoothGatt, then when the call indicates disconnection, I close the BluetoothGatt, but it seems the connection doesn't actually disconnect/close. Once I close my app, the connection closes and the peripheral goes back to advertising, so I know the peripheral is behaving correctly.mtrewartha

2 Answers

3
votes

The problem that keeps happening is after it gets the user defined name and is disconnected the BLE devices stop broadcasting and I can no longer find it when I scan or if I try to connect to it after I read the user defined name and disconnected to it.

Shouldn't be your BLE device need start advertising soon after it's connection dropped? I would also suggest that you can let your BLE device advertise your customized service other than your app to connect then read the characteristic; you just use "ScanFilter" to filter out what your favorite devices. You just let low level code do to this.

1
votes

The observed behaviour is really a feature of the BLE Peripheral device and not Android, or more generally, the Central device program.

I've recently been working with a Laird BL600 - and it has an 'Over the Air', OTA, programming mode - and when that mode is enabled the module advertises the OTA service for up to 10 seconds after being powered on: and that's it. The Peripheral application in this case has been desinged not to drop back to advertising after any connection is broken. To re-enter OTA mode a power cycle of the device is intended.

As suggested by Guo, if there is control over the Peripheral program, an easier scheme would be for the BLE Peripheral to include a readable identifier (device name) and the GUID of its main service in its advertising packet - which would make filtering which devices to present to the user easier.

When the user wishes to connect to the Peripheral, then that will only succeed using device.connectGatt(this, false, mGattCallback) if the device is still advertising and in range. I think the intention of the second parameter in this call - 'autoconnect' - is to mark the connection, within Android, as pending - and that the connection should happen automatically when the device is back in range and advertising - but I've not found this to be very reliable.