如何在 RxAndroidBle 中接收所有通知

How to receive all notifications in RxAndroidBle

我正在尝试使用 rxBleAndroid 运行 在 Android phone 和 Raspberry Pi 使用 Android 东西。

不过,我目前遇到了一个问题,即我的应用从未收到大约 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);
                });

可以通过 BLE 执行的大多数操作都是异步的,需要一些时间才能完成。设置通知也不例外——它是一个两步过程:

  1. 设置本地通知
  2. 编写一个客户端特征配置描述符,描述一个想要从中获取通知的特征

如果您的外围设备首先设置为在通知准备好被中央接收之前发送通知,那么在通知设置过程中一些数据可能会丢失。

Is there maybe some way to do setupNotification(), and then write the characteristics to tell the device to start sending notifications?

当然(这是通常处理类似场景的方式)——有多种可能的实现。其中之一可能如下所示:

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 交互。正如我在上面写的那样,设置通知是一个两步过程,一个是本地步骤,另一个是远程(在外围设备上执行),它在上面的代码片段中的 mode/password 之前执行。可以通过使用 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);
                }
        );
如果我们知道日志特征服务 UUID 并使用 rxBleConnection.writeDescriptor(UUID serviceUuid, UUID characteristicUuid, UUID descriptorUuid 函数,则可以省略

rxBleConnection.discoverServices() 调用。

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[]> 在写入描述符之前发出,允许从一开始就进行通知观察(如果开头正在写入描述符)

缺点:

  • 无法判断描述符的确切写入时间。