Как получать все уведомления в RxAndroidBle

Я пытаюсь связаться с регистратором данных / датчиком BLE, используя rxBleAndroid, работающий как на телефоне Android, так и на Raspberry Pi с использованием Android Things.

Однако в настоящее время у меня возникает проблема, когда мое приложение никогда не получает до 5 первых уведомлений.

Я подтвердил, что устройство BLE действительно успешно отправляет все ожидаемые уведомления. Я сделал это через приложение nRF Connect, и там все работает, как ожидалось.

Когда я делаю это через приложение nRF Connect, я делаю следующие шаги:

  1. Запишите в пароль характеристики для разблокировки устройства
  2. Запишите в характеристику режима, чтобы перевести устройство в правильный режим
  3. Подпишитесь на уведомления (и уведомления сразу начинают работать)

Делая это через RxAndroidBle, я подозреваю, что, возможно, .subscribe () настраивается недостаточно быстро.

Может быть, есть способ выполнить setupNotification () и затем написать характеристики, чтобы устройство начало отправлять уведомления?

Вот мой текущий код:

rxBleClient = RxBleClient.create(this);
RxBleDevice device = rxBleClient.getBleDevice(mac_address);

device.establishConnection(false)
        .flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(pword_uuid, pword)
                .flatMap(ignored1 -> rxBleConnection.writeCharacteristic(mode_uuid, mode))
                .flatMap(ignored2 -> rxBleConnection.setupNotification(log_uuid))
        )
        .flatMap(notificationObservable -> notificationObservable)
        .subscribe(
                bytes -> {
                    System.out.println(">>> data from device " + bytesToHex(bytes));
                },
                throwable -> {
                    System.out.println("error");
                    System.out.println(throwable);
                });

person James E    schedule 13.09.2017    source источник


Ответы (1)


Большинство действий, которые можно выполнять через BLE, являются асинхронными и требуют времени для завершения. Настройка уведомлений не является исключением - это двухэтапная процедура:

  1. настроить локальное уведомление
  2. написать дескриптор конфигурации характеристик клиента для характеристики, от которой требуется получать уведомления

Если ваше периферийное устройство сначала настроено на отправку уведомлений до того, как уведомления будут готовы к получению центральным сервером, то некоторые данные могут быть потеряны во время процедуры настройки уведомлений.

Может быть, есть способ выполнить setupNotification (), а затем записать характеристики, чтобы устройство начало отправлять уведомления?

Конечно (именно так обычно обрабатываются похожие сценарии) - существует несколько возможных реализаций. Один из них мог выглядеть так:

device.establishConnection(false) // establish the connection
        .flatMap(rxBleConnection -> rxBleConnection.setupNotification(log_uuid) // once the connection is available setup the notification
                .flatMap(logDataObservable -> Observable.merge( // when the notification is setup start doing three things at once
                        rxBleConnection.writeCharacteristic(pword_uuid, pword).ignoreElements(), // writing the `pword` but ignore the result so the output of this .merge() will contain only log data
                        rxBleConnection.writeCharacteristic(mode_uuid, mode).ignoreElements(), // same as the line above but for `mode`
                        logDataObservable // observing the log data notifications
                ))
        )
        .subscribe(
                bytes -> System.out.println(">>> data from device " + bytesToHex(bytes)),
                throwable -> {
                    System.out.println("error");
                    System.out.println(throwable);
                }
        );

Редактировать:

Как было упомянуто в комментариях ниже, периферийное устройство не допускает никаких взаимодействий с BLE до установки режима и написания пароля. Как я уже писал выше, настройка уведомлений представляет собой двухэтапную процедуру с локальным шагом и удаленным (выполняемым на периферии), который выполняется перед режимом / паролем в приведенном выше фрагменте кода. Эти два шага можно разделить, используя режим NotificationSetupMode.COMPAT и записав Client Characteristic Configuration Descriptor вручную позже:

UUID clientCharacteristicConfigDescriptorUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
device.establishConnection(false) // establish the connection
        .flatMap(
                RxBleConnection::discoverServices,  // once the connection is available discover the services of the peripheral
                (rxBleConnection, rxBleDeviceServices) -> // and when we have the connection and services
                        rxBleDeviceServices.getCharacteristic(log_uuid) // we get the log characteristic (on which we will setup the notification and write the descriptor)
                                .flatMap(logDataCharacteristic -> // once the log characteristic is retrieved
                                        rxBleConnection.setupNotification(logDataCharacteristic, NotificationSetupMode.COMPAT) // we setup the notification on it in the COMPAT mode (without writing the CCC descriptor)
                                                .flatMap(logDataObservable -> Observable.merge( // when the notification is setup start doing four things at once
                                                        rxBleConnection.writeCharacteristic(pword_uuid, pword).ignoreElements(), // writing the `pword` but ignore the result so the output of this .merge() will contain only log data
                                                        rxBleConnection.writeCharacteristic(mode_uuid, mode).ignoreElements(), // same as the line above but for `mode`
                                                        rxBleConnection.writeDescriptor(logDataCharacteristic.getDescriptor(clientCharacteristicConfigDescriptorUuid), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE).ignoreElements(), // and we write the CCC descriptor manually
                                                        logDataObservable // observing the log data notifications
                                                ))
                                )
        )
        .flatMap(observable -> observable) // flatMap to get the raw byte[]
        .subscribe(
                bytes -> System.out.println(">>> data from device " + bytesToHex(bytes)),
                throwable -> {
                    System.out.println("error");
                    System.out.println(throwable);
                }
        );

Вызов rxBleConnection.discoverServices() может быть опущен, если мы знаем службу характеристик журнала UUID и используем функцию rxBleConnection.writeDescriptor(UUID serviceUuid, UUID characteristicUuid, UUID descriptorUuid.

UUID clientCharacteristicConfigDescriptorUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
device.establishConnection(false) // establish the connection
        .flatMap(rxBleConnection -> rxBleConnection.setupNotification(log_uuid, NotificationSetupMode.COMPAT) // once the connection is available setup the notification w/o setting Client Characteristic Config Descriptor
                .flatMap(logDataObservable -> Observable.merge( // when the notification is setup start doing three things at once
                        rxBleConnection.writeCharacteristic(pword_uuid, pword).ignoreElements(), // writing the `pword` but ignore the result so the output of this .merge() will contain only log data
                        rxBleConnection.writeCharacteristic(mode_uuid, mode).ignoreElements(), // same as the line above but for `mode`
                        rxBleConnection.writeDescriptor(log_service_uuid, log_uuid, clientCharacteristicConfigDescriptorUuid).ignoreElements(), // same as the above line but for writing the CCC descriptor
                        logDataObservable // observing the log data notifications
                ))
        )
        .subscribe(
                bytes -> System.out.println(">>> data from device " + bytesToHex(bytes)),
                throwable -> {
                    System.out.println("error");
                    System.out.println(throwable);
                }
        );

Изменить 2:

Начиная с версии 1.8.0 существует новый NotificationSetupMode.QUICK_SETUP, который сначала включает внутренние уведомления, а затем записывает значение дескриптора CCC.

rxBleConnection.setupNotification(log_uuid, NotificationSetupMode.QUICK_SETUP)

Плюсы:

  • Observable<byte[]> испускается до записи дескриптора, что позволяет отслеживать уведомления с самого начала (если начало записывает дескриптор)

Минусы:

  • Невозможно определить, когда именно был записан дескриптор.
person Dariusz Seweryn    schedule 13.09.2017
comment
Я только что попробовал и теперь не получаю никаких уведомлений. Оказывается, мое устройство BLE ДОЛЖНО сначала получить пароль и режим, иначе уведомления никогда не появятся. Есть ли способ добиться следующего: [запись пароля, затем режим записи, затем подписка], не пропуская первые несколько уведомлений? - person James E; 14.09.2017
comment
Существует вероятность того, что настройка уведомления в режиме COMPAT и затем ручная запись дескриптора конфигурации характеристик клиента после пароля и режима могут сработать. Дело в том, что отсутствует информация о том, что позволяет периферийное устройство, а что нет. Я не на своем компьютере, поэтому не обновлю ответ до понедельника. - person Dariusz Seweryn; 14.09.2017
comment
Большое спасибо! Настройка уведомлений в режиме COMPAT и отдельная запись дескриптора устранили мою проблему, и теперь она работает хорошо. Спасибо за вашу помощь. - person James E; 16.09.2017
comment
Рад помочь. Я отредактировал ответ с двумя вариантами решения вашего варианта использования в зависимости от того, известен ли log_service_uuid или нет. Если вы нашли ответ полезным - не стесняйтесь отмечать его как таковой. - person Dariusz Seweryn; 18.09.2017
comment
Я пытался использовать этот код, но не уверен, что понимаю, как это работает. Когда я пытаюсь установитьConnection, я могу получить BleAlreadyConnectedException, например, и будет вызвана onError, поэтому мне удается отключиться и создать новое соединение, затем вызываются наблюдаемые внутри installConnection one, и все кажется в порядке, но onNext никогда не вызывается. Есть идеи, что я делаю неправильно? @DariuszSeweryn - person Carla Urrea Stabile; 02.10.2017
comment
Без понятия. Я не могу представить ваш код, поскольку в нем явно есть какое-то управление состоянием. Не стесняйтесь открывать новый вопрос со связанным кодом. - person Dariusz Seweryn; 02.10.2017