Rxandroid ble Рекомендации по обработке повторного подключения

[Обновлять]

Я могу повторно подключиться к устройству после отключения, но не могу прочитать или записать какие-либо характеристики. Logcat извергает это после повторного подключения, это мое приложение делает что-то не так или это из-за устройства BLE?

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)

[Обновлять]

Использование rxandroidble1 и rxjava1

Привет, я новичок в концепции соединений rxjava и ble, но меня включили в существующий проект с очень небольшим объемом документации, и у меня возникают проблемы с обработкой повторного подключения после потери соединения.

Я проверил пример приложения rxandroidble, но он обрабатывает только соединение, а не повторное соединение, если оно его теряет. Или библиотека должна справиться с этим сама, или мне что-то не хватает.

Общую проблему можно описать так:

  1. Я подключаю приложение для телефона к своему устройству. Все работает, как ожидалось. Я получаю уведомления от своих подписок при изменении температуры на моем устройстве.

  2. Я теряю соединение, либо выключаю микрочип на устройстве, либо выключаю Bluetooth на моем телефоне, либо выхожу из зоны действия.

  3. Я снова включаю Bluetooth на телефоне или на другом устройстве.

  4. Мне удается повторно подключиться, но на мои подписки не подписываются повторно, поэтому я не получаю никаких уведомлений на свой телефон при изменении температуры или других значений.

По словам моего работодателя, этот код должен был работать нормально в прошлом, но я не могу заставить его работать после потери соединения. Так может ли кто-нибудь из вас увидеть ошибки в логике кода? Или может быть проблема с устройством ble? Или это обычная ошибка или проблема с RxBleConnectionSharingAdapter или как? Я все перепробовал, но ничего не работает.

Или мне что-то не хватает, например, onUnsibcribeMethod или что-то в этом роде?

Я предполагаю, что метод установления соединения - самая важная часть кода. Iv'e попытался повторно подписаться на характеристику после повторного подключения с помощью метода тестирования, но тогда приложение просто вылетало.

Это мой класс диспетчера соединений:

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);
                        }
                    }
                });
    }
}

person AndyFil    schedule 09.08.2018    source источник


Ответы (1)


В installConnection вы устанавливаете для параметра autoConnect значение false, что предотвратит автоматическое переподключение. Если вместо этого вы установите для него значение true, он должен автоматически переподключиться. См. https://stackoverflow.com/a/40187086/556495 и http://polidea.github.io/RxAndroidBle/ в разделе" Автоматическое подключение ".

Учтите, что это не сработает, если на телефоне / планшете отключен / перезапущен Bluetooth. Таким образом, вам, вероятно, также понадобится прослушиватель широковещательной передачи изменения состояния Bluetooth, чтобы перезапустить все, когда это произойдет.

person Emil    schedule 09.08.2018
comment
Привет! Спасибо за Ваш ответ! Мне удалось повторно подключиться даже без использования флага автоматического подключения до истины. Но проблема в том, что я не получаю никаких обновлений после переподключения. Я повторно подключаюсь, но после повторного подключения получаю исключение blecannotsetCharactersticNotificationExeption. Может быть, мое соединение из первого подключения автоматически кэшируется в sharedConnetionAdapter или что-то в этом роде? Т.е. я использую старое устаревшее соединение? - person AndyFil; 09.08.2018
comment
Я считаю, что автоматический флаг относится только к начальному подключению, а не к «повторному подключению». то есть, если вы вызываете connect на неактивном MAC-адресе устройства, оно будет ждать, пока оно не станет активным, но не будет подключаться повторно, если соединение прервется по какой-либо причине, а затем снова станет доступным. Из polidea.github.io/RxAndroidBle: autoConnect boolean: подключаться ли напрямую к удаленному устройству ( false) или автоматически подключаться, как только удаленное устройство становится доступным (true). - person behelit; 30.04.2019
comment
Неправильно. Если вы установите для параметра автоматического подключения значение true, он автоматически восстановит подключение, если соединение будет потеряно. - person Emil; 30.04.2019
comment
Также из файла readme библиотеки RxAndroidBle: 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. - person Dariusz Seweryn; 26.09.2020