1

[Update]

I am able to reconnect to the ble device after disconnect but can't seem to read or write any characteristics. Logcat spews this out after reconnect, is this my app doing something wrong or is it because of the ble device?

08-09 15:05:45.109 9601-10364/com.project.app D/BluetoothGatt: setCharacteristicNotification() - uuid: cb67d1e1-cfb5-45f5-9123-3f07d9189f1b enable: false
08-09 15:05:45.111 9601-10352/com.project.app D/RxBle#ConnectionOperationQueue:  STARTED DescriptorWriteOperation(54881118)
08-09 15:05:45.114 9601-10364/com.project.app D/RxBle#ConnectionOperationQueue:   QUEUED DescriptorWriteOperation(191754430)
08-09 15:05:45.116 9601-9601/com.project.app D/BtleConnManager:  RETRY 2/-1 :::: com.polidea.rxandroidble.exceptions.BleCannotSetCharacteristicNotificationException
08-09 15:06:15.117 9601-10352/com.project.app D/RxBle#ConnectionOperationQueue: FINISHED DescriptorWriteOperation(54881118)
08-09 15:06:15.118 9601-10365/com.project.app D/BluetoothGatt: setCharacteristicNotification() - uuid: cb67d0e1-cfb5-45f5-9123-3f07d9189f1b enable: false
08-09 15:06:15.120 9601-10352/com.project.app D/RxBle#ConnectionOperationQueue:  STARTED DescriptorWriteOperation(88995281)
08-09 15:06:15.124 9601-10365/com.project.app D/RxBle#ConnectionOperationQueue:   QUEUED DescriptorWriteOperation(108601267)
08-09 15:06:15.124 9601-10366/com.project.app D/BluetoothGatt: setCharacteristicNotification() - uuid: cb67d1e1-cfb5-45f5-9123-3f07d9189f1b enable: true
08-09 15:06:15.126 9601-9601/com.project.app D/BtleConnManager:  RETRY 2/-1 :::: com.polidea.rxandroidble.exceptions.BleCannotSetCharacteristicNotificationException
08-09 15:06:15.131 9601-10366/com.project.app D/RxBle#ConnectionOperationQueue:   QUEUED DescriptorWriteOperation(98838561)
08-09 15:06:45.126 9601-10352/com.project.app D/RxBle#ConnectionOperationQueue: FINISHED DescriptorWriteOperation(88995281)

[Update]

Using rxandroidble1 and rxjava1

Hi I am new to the concept of rxjava and ble connections but I've been put on a existing project with very little documentation and I'm having problems handling the re-connection after a connection is lost.

I've checked out the sample app of rxandroidble but it only handles connection and no the re-connection if it looses it. Or is the library supposed to handle it by it's own or am I missing something.

The general problem can be described as such :

  1. I connect the phone-app to my ble device. Everything works as expected i get notifications from my subsriptions when temperature is changing on my ble device.

  2. I loose connection either by turning of the ble chip on the device or turning off Bluetooth on my phone or walking out of range.

  3. I turn on Bluetooth again either on my phone or the ble device.

  4. I manage to reconnect but my subscriptions aren't re subscribed so i don't get any notifications back to my phone when temperature or other values are changing.

According to my employer this code should have worked fine in the past but I can't seem to get it to work after it looses the connection. So can any of you guys see any errors in the logic of the code. Or might there be a problem with the ble device? Or is this a common bug or problem with the RxBleConnectionSharingAdapter or what? I've tried everything but nothing seems to work.

Or am I missing something in like the onUnsibcribeMethod or something?

I guess the establish connection method is the most relevant part of the code. Iv'e tried re-subscribing via to a characteristic after reconnect via the test method but the app just crashes then.

This is my connection manager class :

private static final String TAG = "HELLOP";
private static RxBleClient rxBleClient;
private RxBleConnection rxBleConnection;

private static final int MAX_RETRIES = 10;
private static final int SHORT_RETRY_DELAY_MS = 1000;
private static final int LONG_RETRY_DELAY_MS = 30000;

private final Context mContext;
private final String mMacAddress;
private final String gatewayName;
private final RxBleDevice mBleDevice;
private PublishSubject<Void> mDisconnectTriggerSubject = PublishSubject.create();
private Observable<RxBleConnection> mConnectionObservable;
private final ProjectDeviceManager mProjectDeviceManager;
private BehaviorSubject<Boolean> connectionStatusSubject = BehaviorSubject.create();
private boolean isAutoSignIn = false;
private BondStateReceiver bondStateReceiver;
private boolean isBonded = false;


//gets the client
public static RxBleClient getRxBleClient(Context context) {
    if (rxBleClient == null) {
        // rxBleClient = /*MockedClient.getClient();*/RxBleClient.create(this);
        // RxBleClient.setLogLevel(RxBleLog.DEBUG);
        // super.onCreate();
        rxBleClient = RxBleClient.create(context);
        RxBleClient.setLogLevel(RxBleLog.DEBUG);
    }
    return rxBleClient;
}

public BtleConnectionManager(final Context context, final String macAddress, String name) {
    mContext = context;
    mMacAddress = macAddress;
    gatewayName = name;
    mBleDevice = getRxBleClient(context).getBleDevice(macAddress);
    mProjectDeviceManager = new ProjectDeviceManager(this);
}

@Override
public final Context getContext() {
    return mContext;
}

@Override
public final ProjectDeviceManager getProjectDeviceManager() {
    return mProjectDeviceManager;
}

@Override
public final boolean isConnected() {
    return mBleDevice.getConnectionState() == RxBleConnection.RxBleConnectionState.CONNECTED;
}

@Override
public String getConnectionName() {
    if (gatewayName != null && !gatewayName.isEmpty()) {
        return gatewayName;
    } else {
        return mMacAddress;
    }
}

final RxBleDevice getBleDevice() {
    return mBleDevice;
}

public final synchronized Observable<RxBleConnection> getConnection() {
    if (mConnectionObservable == null || mBleDevice.getConnectionState() == RxBleConnection.RxBleConnectionState.DISCONNECTED
            || mBleDevice.getConnectionState() == RxBleConnection.RxBleConnectionState.DISCONNECTING) {
        establishConnection();
    }
    return mConnectionObservable;
}

public void goBack() {
    Intent intent = null;
    try {
        intent = new Intent(mContext,
                Class.forName("com.Project.dcpapp.BluetoothActivity"));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        ((Activity) mContext).startActivity(intent);
        ((Activity) mContext).finish();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

public void setAutoSignIn(boolean value) {
    this.isAutoSignIn = value;
}

public boolean getAutoSignIn() {
    return this.isAutoSignIn;
}

@Override
public void pause() {
}

@Override
public void resume() {
}

@Override
public Observable<Boolean> observeConnectionStatus() {
    return connectionStatusSubject;
}

@Override
public Calendar getLastConnectionTime() {
    return mProjectDeviceManager.getLastUpdateTime();
}

public void disconnect() {
    BluetoothDevice bluetoothDevice = getBleDevice().getBluetoothDevice();
    Log.d("BtleConnManager", " disconnect " + bluetoothDevice.getBondState());
    Handler handler = new Handler(Looper.getMainLooper());
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            mDisconnectTriggerSubject.onNext(null);
            mConnectionObservable = null;
        }
    }, 700);

}

public void removeBond() {
    Method m = null;
    BluetoothDevice bluetoothDevice = getBleDevice().getBluetoothDevice();
    Log.d("BtleConnManager", " removeBond " + bluetoothDevice.getBondState());
    if (bluetoothDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
        try {
            m = bluetoothDevice.getClass().getMethod("removeBond", (Class[]) null);
            m.invoke(bluetoothDevice, (Object[]) null);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

public void bond() {
    BluetoothDevice bluetoothDevice = getBleDevice().getBluetoothDevice();
    Log.d("BtleConnManager  ", "bond state " + bluetoothDevice.getBondState());
    if (bluetoothDevice.getBondState() == BluetoothDevice.BOND_NONE
            && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        bondStateReceiver = new BondStateReceiver();
        final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        getContext().registerReceiver(bondStateReceiver, filter);
        bluetoothDevice.createBond();
    }
}

public void setBonded(boolean value) {
    this.isBonded = value;
}

public boolean isBonded() {
    return this.isBonded;
}

private class BondStateReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
            final int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
            switch (state) {
                case BluetoothDevice.BOND_BONDED:
                    setBonded(true);
                    Log.d("BtleConManager", "Bonded ");
                    break;
                case BluetoothDevice.BOND_BONDING:
                    Log.d("BtleConManager", "Bonding ");
                    break;
                case BluetoothDevice.BOND_NONE:
                    Log.d("BtleConManager", "unbonded ");
                    setBonded(false);
                    final int prevState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR);
                    if (prevState == BluetoothDevice.BOND_BONDING) {
                        Toast.makeText(getContext(), R.string.error_bluetooth_bonding_failed, Toast.LENGTH_LONG).show();
                    }
                    break;
            }
        }
    }
}

private void establishConnection() {
    Log.d("BtleConnManager", " establishConnection");
    mConnectionObservable = mBleDevice
            .establishConnection(false)
            .observeOn(AndroidSchedulers.mainThread())
            .doOnNext(rxBleConnection -> {
                // Save connection to use if retry is done when already connected
                this.rxBleConnection = rxBleConnection;
                // Notify observers that connection is established
                connectionStatusSubject.onNext(true);

            })

            .onErrorResumeNext(error -> {
                // Return the saved connection if already connected
                if (error instanceof BleAlreadyConnectedException && rxBleConnection != null) {
                    return Observable.just(rxBleConnection);

                } else {
                    return Observable.error(error);
                }
            })
            //.retryWhen(getRetryRule()) Do not retry connect here - retry when using getConnection instead (otherwise a double retry connection will be done)
            .takeUntil(mDisconnectTriggerSubject)
            .doOnError(throwable -> {

                this.rxBleConnection = null;
                if (!isConnected()) {
                    // Notify observers that connection has failed
                    connectionStatusSubject.onNext(false);
                }
            }).doOnUnsubscribe(() -> {
                Log.d("BtleConnManager", "establishConnection Unsubscribe ");
                connectionStatusSubject.onNext(false);

            }).doOnCompleted(() -> Log.d("BtleConnManager", "establishConnection completed"))
            .doOnSubscribe(() -> {

            })
            //.subscribeOn(AndroidSchedulers.mainThread())
            //.compose(bindUntilEvent(PAUSE))
            .compose(new ConnectionSharingAdapter());


}


public void test(){
    mConnectionObservable
            .flatMap(rxBleConnection -> rxBleConnection.setupNotification(UUID.fromString("cb67d1c1-cfb5-45f5-9123-3f07d9189f1b")))
            .flatMap(notificationObservable -> notificationObservable)
            .observeOn(AndroidSchedulers.mainThread())
            .retryWhen(errors -> errors.flatMap(error -> {
                if (error instanceof BleDisconnectedException) {
                    Log.d("Retry", "Retrying");
                    return Observable.just(null);
                }
                return Observable.error(error);
            }))
            .doOnError(throwable -> {

                Log.d(TAG, "establishConnection: " + throwable.getMessage());

            })
            .subscribe(bytes -> {
                Log.d(TAG, "establishConnection: characteristics changed" + new String(bytes));
                // React on characteristic changes
            });
}


public RetryWithDelay getRetryRule() {
    return new RetryWithDelay(MAX_RETRIES, SHORT_RETRY_DELAY_MS);
}

public RetryWithDelay getInfiniteRetryRule() {
    return new RetryWithDelay(RetryWithDelay.NO_MAX, LONG_RETRY_DELAY_MS);
}

public class RetryWithDelay implements
        Func1<Observable<? extends Throwable>, Observable<?>> {

    public static final int NO_MAX = -1;

    private final int maxRetries;
    private final int retryDelayMs;
    private int retryCount;

    public RetryWithDelay(final int maxRetries, final int retryDelayMs) {
        this.maxRetries = maxRetries;
        this.retryDelayMs = retryDelayMs;
        this.retryCount = 0;
    }

    @Override
    public Observable<?> call(Observable<? extends Throwable> attempts) {
        return attempts
                .flatMap(new Func1<Throwable, Observable<?>>() {
                    @Override
                    public Observable<?> call(Throwable throwable) {
                        ++retryCount;
                        if (mConnectionObservable == null) {
                            // If manually disconnected return empty observable
                            return Observable.empty();
                        } else if (throwable instanceof BleAlreadyConnectedException) {
                            return Observable.error(throwable);
                        } else if (retryCount < maxRetries || maxRetries == NO_MAX) {
                            Log.d("BtleConnManager", " RETRY " + retryCount + "/" + maxRetries + " :::: " + throwable.getClass().getName());
                            // When this Observable calls onNext, the original
                            // Observable will be retried (i.e. re-subscribed).
                            return Observable.timer(retryDelayMs, TimeUnit.MILLISECONDS);
                        } else {
                            //Last try
                            Log.d("BtleConnManager", " LAST RETRY " + retryCount + "/" + maxRetries + " :::: " + throwable.getClass().getName());
                            return Observable.error(throwable);
                        }
                    }
                });
    }
}
Dariusz Seweryn
  • 3,212
  • 2
  • 14
  • 21
AndyFil
  • 73
  • 7

1 Answers1

1

In establishConnection you set the autoConnect parameter to false, which will prevent automatic reconnection. If you set it to true instead, it should automatically reconnect. See https://stackoverflow.com/a/40187086/556495 and http://polidea.github.io/RxAndroidBle/ under Auto connect.

Note that this will not work if Bluetooth is turned off/restarted on the phone/tablet. So you'll probably also need a Bluetooth state change broadcast listener to restart everything when that happens.

Emil
  • 16,784
  • 2
  • 41
  • 52
  • Hi! Thanks for your answer! I managed to re-connect even without using the auto connect flag to true before. But the problem is that I can't seem to get any updates after the reconnect. So I reconnect but then i get blecannotsetCharactersticNotificationExeption after a reconnect. Maybe my connection from the first connection is automatically cashed in the sharedConnetionAdapter or something? I.e I'm using an old stale connection? – AndyFil Aug 09 '18 at 13:22
  • I believe the auto flag only pertains to the initial connection, and not 'reconnecting'. i.e. if you call connect on a device MAC that isn't active, it will wait until it is, but it does not reconnect if the connection dies for any reason and then becomes available again. From http://polidea.github.io/RxAndroidBle/: autoConnect boolean: Whether to directly connect to the remote device (false) or to automatically connect as soon as the remote device becomes available (true). – behelit Apr 30 '19 at 02:20
  • Incorrect. If you set the auto connect parameter to true, it will automatically reconnect if the connection is lost. – Emil Apr 30 '19 at 08:54
  • 1
    Also from the `RxAndroidBle` library readme: `Unlike the native Android API, if autoConnect=true while using this library there will be NO attempts to automatically reconnect if the original connection is lost.` – Dariusz Seweryn Sep 26 '20 at 07:41