我正在尝试使用在 Android 手机和使用 Android Things 的 Raspberry Pi 上运行的 rxBleAndroid 与 BLE 数据记录器/传感器进行通信。
但是,我目前遇到了一个问题,我的应用程序从未收到最多大约 5 个通知。
我已经验证 BLE 设备实际上已成功发送所有预期的通知。我已经通过 nRF Connect 应用程序做到了这一点,一切都按预期进行。
当我通过 nRF Connect 应用程序执行此操作时,这些是我采取的步骤:
通过 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);
});
Run Code Online (Sandbox Code Playgroud)
可以通过 BLE 执行的大多数操作都是异步的,需要一些时间才能完成。设置通知也不例外——它是一个两步程序:
如果您的外围设备在通知准备好被中央设备接收之前首先设置为发送通知,那么在通知设置过程中某些数据可能会丢失。
有没有办法做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);
}
);
Run Code Online (Sandbox Code Playgroud)
编辑:
正如在下面的评论中提到的那样 - 在设置模式和写入密码之前,外设不允许任何 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);
}
);
Run Code Online (Sandbox Code Playgroud)
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);
}
);
Run Code Online (Sandbox Code Playgroud)
编辑2:
由于版本1.8.0有一个新的NotificationSetupMode.QUICK_SETUP,它首先打开内部通知,然后写入 CCC 描述符值。
rxBleConnection.setupNotification(log_uuid, NotificationSetupMode.QUICK_SETUP)
Run Code Online (Sandbox Code Playgroud)
优点:
Observable<byte[]> 在写入描述符之前发出,允许从一开始就进行通知观察(如果开头正在写入描述符)缺点: