I am building an Android application that has specific requirements regarding Bluetooth Low Energy.
I need to write to a Write-only characteristic and receive responses on a separate notification characteristic, and I need to do it in many, many activities. Is there a Rx way to send a request on the 1st characteristic, wait for the answer on the second one, then proceed to another request?
Also, to share my instance of RxAndroidBle I thought about doing some sort of BleManager Singleton where I would expose the Observables, so I can easily subscribe to them in my Presenter. I just want to avoid having to copy the connection logic for each activity and have (ideally) a persistant connection. This way I could only expose the connectionObservable and subscribe to it, so I can easily send Write Requests and get Notifications, but I am sure there is a better way to do it.
This is what I have for now:
@Singleton
public class BleManager {
private PublishSubject<Void> disconnectTriggerSubject = PublishSubject.create();
private Observable<RxBleConnection> connectionObservable;
private boolean isConnected;
private final UUID CTRL_FROM_BRIDGE_UUID = UUID.fromString("someUUID");
private final UUID BLE_WRITE_CHARACTERISTIC_UUID = UUID.fromString("someOtherUUID");
private final RxBleClient bleClient;
private String mMacAddress;
private final Context context;
private RxBleDevice bleDevice;
@Inject
public BleManager(Context context, RxBleClient client) {
Timber.d("Constructing BleManager and injecting members");
this.context = context;
this.bleClient = client;
}
public void setMacAddress(String mMacAddress) {
this.mMacAddress = mMacAddress;
// Set the associated device on MacAddress change
bleDevice = bleClient.getBleDevice(this.mMacAddress);
}
public String getMacAddress() {
return mMacAddress;
}
public RxBleDevice getBleDevice() {
Preconditions.checkNotNull(mMacAddress);
return bleClient.getBleDevice(mMacAddress);
}
public Observable<RxBleScanResult> getScanSubscription() {
Preconditions.checkNotNull(context);
Preconditions.checkNotNull(bleClient);
return bleClient.scanBleDevices().distinct();
}
public Observable<RxBleConnection> getConnectionSubscription() {
Preconditions.checkNotNull(context);
Preconditions.checkNotNull(bleDevice);
if (connectionObservable == null) {
connectionObservable = bleDevice.establishConnection(context, false)
.takeUntil(disconnectTriggerSubject)
.observeOn(AndroidSchedulers.mainThread())
.doOnUnsubscribe(this::clearSubscription)
.compose(new ConnectionSharingAdapter());
}
return connectionObservable;
}
public Observable<byte[]> setupListeners() {
return connectionObservable.flatMap(rxBleConnection -> rxBleConnection.setupNotification(CTRL_FROM_BRIDGE_UUID))
.doOnNext(notificationObservable -> Timber.d("Notification Setup"))
.flatMap(notificationObservable -> notificationObservable)
.observeOn(AndroidSchedulers.mainThread());
}
private void triggerDisconnect() {
disconnectTriggerSubject.onNext(null);
}
public Observable<byte[]> writeBytes(byte[] bytes) {
return connectionObservable.flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(
BLE_WRITE_CHARACTERISTIC_UUID,
bytes)).observeOn(AndroidSchedulers.mainThread());
}
private boolean isConnected() {
return bleDevice.getConnectionState() == RxBleConnection.RxBleConnectionState.CONNECTED;
}
/**
* Will update the UI with the current state of the Ble Connection
*/
private void registerConnectionStateChange() {
bleDevice.observeConnectionStateChanges().observeOn(AndroidSchedulers.mainThread()).subscribe(connectionState -> {
isConnected = connectionState.equals(RxBleConnection.RxBleConnectionState.CONNECTED);
});
}
private void clearSubscription() {
connectionObservable = null;
}
}
I have thought a bit about your use-case. With sharing the same connection you're introducing states to your application which needs a bit of state handling and therefore it is impossible (or at least I have no idea how) to be purely reactive.
I have focused on establishing a connection and performing a write-notify transmission to a BLE device that is serialized.
This is an approach that I think is good as the whole flow is described in one place and therefore easier to understand. The part of communication that is stateful (writing request and waiting for response) is serialized and it has the possibility to persist the connection till the
disconnect()
call.The downside is that the transmission relies on the side effects of different flow and calling the
writeData()
before the connection is established and notification setup will never complete the returned Observable though it shouldn't be a problem to add handling for this scenario with a state check.Best Regards