How to unsubscribe after a successful writeCharacteristic with rxandroidble - android

My current code is as shown below. I am wondering how do i unsubscribe (disconnect the ble) after writeCharacteristic? Also, is there a way to reconnect on writeCharacteristic fail?
Subscription subscription = device.establishConnection(false)
.timeout(5000, TimeUnit.MILLISECONDS)
.flatMap(rxBleConnection ->
rxBleConnection.writeCharacteristic(fromString("00005551-0000-1000-8000-008055555fb"), hexToBytes(mData)))
.take(1).retry(2)
.subscribe(
characteristicValue -> {
Log.e(TAG, "write success " + characteristicValue + " " + device.getMacAddress());
// subscribeList.remove(key).unsubscribe();
},
throwable -> {
Log.e(TAG, "write error " + throwable);
// subscribeList.remove(key).unsubscribe();
}
);

I am wondering how do i unsubscribe (disconnect the ble) after connection?
I assume that after connection is after the write because if the connection ended by external factors (like the peripheral being turned off) then the whole flow would end with an error.
If you want to write only a single value and then disconnect then everything you need is to take a single value from the flow after writeCharacteristic() by using .take(1) before the .subscribe().
Also, is there a way to reconnect on writeCharacteristic fail?
First of all, a failure of rxBleConnection.writeCharacteristic() does not automatically close the connection since 1.3.0 version of RxAndroidBle if the error is related to the write itself.
You may be experiencing the connection being closed only because you do not handle errors of the write action. You can make the write to retry two times by using .retry(2) operator.
Be mindful which Observable you try to .retry(). If you are interested in retrying writes only if they fail but the connection is still valid then you should apply .retry() on the RxBleConnection.writeCharacteristic(). On the other hand—if you want to retry the whole flow if any error will occur then you should put the .retry() on the whole flow Observable.
Subscription subscription = device.establishConnection(false)
.flatMap(rxBleConnection ->
rxBleConnection.writeCharacteristic(
fromString("00005551-0000-1000-8000-008055555fb"),
hexToBytes(mData)
)
.retry(2) // retry here will only retry the write if a write characteristic will fail but the connection will still be intact
)
.take(1) // this will unsubscribe the above part of the Observable on the first valid emission from it—so after the write will complete
// .retry(2) if you will put retry here then for whatever reason the above flow [connecting + writing] will fail then a new connection will be established and write performed
.subscribe(
characteristicValue -> {
Log.e(TAG, "write success " + characteristicValue + " " + device.getMacAddress());
},
throwable -> {
Log.e(TAG, "write error " + throwable);
}
);

Just:
subscription.unsubscribe();
If I understand the process correctly, unsubscribing will "bubble up" through the operator stack to the top, thereby disconnecting.

Related

RxAndroidBle 2 : multiple Read then notify on another characteristic

I'm using RxAndroidBle 2 to watch a characteristic for CSC but I cannot read some characteristics before. It's also the first time I'm using ReactiveX so I have to deal with flatMap, flatMapSingle, etc.
Here's my code to register a handler for watching CSC Measure:
connectionSubscription = device.establishConnection(false)
// Register for notifications on CSC Measure
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(Constants.CSC_MEASURE))
.doOnNext(notificationObservable -> {
// Notification has been set up
Log.d(TAG, "doOnNext = " + notificationObservable.toString());
})
.flatMap(notificationObservable -> {
Log.d(TAG, "flatMap = " + notificationObservable);
return notificationObservable;
}) // <-- Notification has been set up, now observe value changes.
.subscribe(
this::onCharacteristicChanged,
throwable -> {
if (throwable instanceof BleDisconnectedException) {
Log.e(TAG, "getCanonicalName = " + throwable.getClass().getCanonicalName());
}
}
);
And then the code for extracting the value:
private void onCharacteristicChanged(byte[] bytes) {
Integer f = ValueInterpreter.getIntValue(bytes, ValueInterpreter.FORMAT_UINT8, 0);
Log.d(TAG, "flags " + f);
switch (f) {
case 0: // 0x00000000 is not authorized
Log.w(TAG, "flags cannot be properly detected for this CSC device");
break;
case 1:
// sensor is in speed mode (mounted on the rear wheel)
// Cumulative Wheel Revolutions
Integer sumWheelRevs = ValueInterpreter.getIntValue(bytes, ValueInterpreter.FORMAT_UINT32, 1);
// Last Wheel event time
Integer lastWheelEvent = ValueInterpreter.getIntValue(bytes, ValueInterpreter.FORMAT_UINT16, 5);
Log.d(TAG, "Last wheel event detected at " + lastWheelEvent + ", wheel revs = " + sumWheelRevs);
break;
case 2:
// sensor is in cadence mode (mounted on the crank)
// Last Crank Event Time
// Cumulative Crank Revolutions
break;
}
}
I have another piece of working code to read ONE characteristic, but how can I process a list of characteristics?
.flatMapSingle(rxBleConnection -> rxBleConnection.readCharacteristic(Constants.DEVICE_HARDWARE))
.subscribe(
characteristicValue -> {
String deviceHardware = ValueInterpreter.getStringValue(characteristicValue, 0);
Log.d(TAG, "characteristicValue deviceHardware = " + deviceHardware);
},
throwable -> {
// Handle an error here.
}
)
Constants are defined like this:
public static final UUID CSC_MEASURE = UUID.fromString("00002a5b-0000-1000-8000-00805f9b34fb");
I tried to integrate the answer provided in here but the code doesn't compile anymore. Moreover, the code should combine up to 12 characteristics (simple map of UUID to Int/String/Boolean). I used to have a working code by creating a subclass of BluetoothGattCallback but my code was getting more and more difficult to maintain with standard Android Bluetooth classes.
I tried to integrate the answer provided in here but the code doesn't compile anymore.
I have updated the post you mentioned to match RxAndroidBle based on RxJava2. It should compile now.
I have another piece of working code to read ONE characteristic, but how can I process a list of characteristics?
...
Moreover, the code should combine up to 12 characteristics (simple map of UUID to Int/String/Boolean).
The mentioned post does solve a case where there are 4 characteristics. In case of 12 (or a variable number) there is a Single#zipArray function.
Single.zipArray(
/* Object[] */ results -> YourWrapperObject(results),
rxBleConnection.readCharacteristic(Constants.DEVICE_HARDWARE),
rxBleConnection.readCharacteristic(Constants.DEVICE_HARDWARE1),
rxBleConnection.readCharacteristic(Constants.DEVICE_HARDWARE2),
// ...
rxBleConnection.readCharacteristic(Constants.DEVICE_HARDWARE11)
)

TcpClient.ConnectAsync and TcpClient.BeginConnect always returning true in Xamarin.Forms Android Application

I have made an application in my Xamarin.Forms project where I can connect my Android phone to my Computer using a TCP connection. I have found while using both TcpClient.ConnectAsync and TcpClient.BeginConnect, they both return that client.Connected is true even though the port isn't open. I have verified this because I tried random IPs and random ports and it still says connection was successful.
When using TcpClient.ConnectAsync, it doesn't return true unless I press the button that runs the code under Button_Clicked 2 times, but when using TcpClient.BeginConnect, client.Connected always returns true. I know for a fact that the client isn't connected because I have a detection system that kicks the user to the reconnect page when the connection is lost.
The code I have for my TCPClient in MainPage.xaml.cs:
TcpClient client = new TcpClient();
private async void Button_Clicked(object sender, EventArgs e)
{
await client.ConnectAsync(ipAddress.Text, Convert.ToInt32(Port.Text));
if (client.Connected)
{
await DisplayAlert("Connected", "The client has successfully connected", "OK");
}
else
{
await DisplayAlert("Connection Unsuccessful", "The client couldn't connect!", "OK");
}
}
I have also tried using TcpClient.BeginConnect from How to set the timeout for a TcpClient?:
TcpClient client = new TcpClient();
private async void Button_Clicked(object sender, EventArgs e)
{
var result = client.BeginConnect(ipAddress.Text, Convert.ToInt32(Port.Text), null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1));
if (success)
{
await DisplayAlert("Connected", "The client has successfully connected", "OK");
}
else
{
await DisplayAlert("Connection Unsuccessful", "The client couldn't connect!", "OK");
}
}
I tried looking up the issue and the only thing I found was: TcpClient.Connected returns true yet client is not connected, what can I use instead? but, this link is stating that the client.Connected bool remains true after disconnection, while my problem is that it says the client connects even even though the client never gets a true connection to the server.
The project is currently using .NET Standard 2.0
I have found out the reason it would return client.Connected is true is because running the same ConnectAsync/BeginConnect method twice while the client is still trying to connect and hasn't yet timed out will cause the client.Connected value to be true for some reason.
The only way to fix this it to wait for the timeout to complete, or if the timeout is too long, to dispose the client and create a new one.

Why do multiple parallel invocations of NetworkManager.addToQueueAndWait interfere with each other?

Multiple invocations to the jsonRequest() method from different Threads (Timer-1 and EDT) do interfere with each other and even one call returns the result of a previous invocation
My CodeNameOne application uses a background Thread (Timer-1) to retrieve and display data from a REST service every second and it allows the user to issue commands that also issue REST calls from the EDT thread.
private Map<String, Object> jsonRequest(String url, String body, String cmd, int timeoutMs) {
long startTs = System.currentTimeMillis();
try {
request = new ConnectionRequest();
request.setReadResponseForErrors(true);
// request.setTimeout(timeoutMs);
// Shai: Timeout in Codename One is currently limited to connection timeout and
// doesn't apply to read timeout so once a connection is made it will last
request.setHttpMethod(cmd);
request.setPost(cmd.equalsIgnoreCase("POST") || cmd.equalsIgnoreCase("PUT") || cmd.equalsIgnoreCase("PATCH"));
if (body != null) {
request.addRequestHeader("Accept", "application/json");
request.setContentType("application/json");
request.setRequestBody(body);
request.setWriteRequest(true);
}
request.setUrl(url);
NetworkManager.getInstance().addToQueueAndWait(request);
long duration = System.currentTimeMillis() - startTs;
Log.p(cmd + ": " + url + " " + duration + " ms");
if (request.getResponseCode() >= 400 || request.getResponseData() == null) {
Log.p("responseCode=" + request.getResponseCode() + " responseData=" + request.getResponseData());
return null;
}
Log.p(cmd + ": " + url + " " + new String(request.getResponseData()));
Map<String, Object> result = new JSONParser().parseJSON(new InputStreamReader(new ByteArrayInputStream(request.getResponseData()), "UTF-8"));
return result;
} catch (Exception e) {
problemHandler.handle(cmd, url, e);
}
return null;
}
Actually result of multiple invocations get mixed up.
I would expect that each call to addToQueueAndWait() waits for the right result and returns just when the result is there.
I observed this problem to happen much more often on Android than on iOS or the simulator
I doubt that's what you are seeing. I see that request is defined in the class level as a variable so I'm guessing you are seeing a typical race condition where the request variable gets replaced while one is sent and by the time you reach the parsing it's a different object.
There is no need to use a thread for polling as networking already runs on a separate thread (or more this is usually determined in the init(Object) method).
I would suggest using a timer for a single invocation that's invoked after the response finishes.
A better approach would be websockets though: https://www.codenameone.com/blog/introducing-codename-one-websocket-support.html
With websockets the server can push out an update notification. This will save you the need to constantly poll the server. It saves on device battery life and resources on server/device.

How to perform "RXJava 2 queue jobs" until value changes?

I'm using RXJava2 in my Android app, and I have a somewhat peculiar scenario.
I want to perform unknown amount of jobs (determined by the user), but I want them to start only after a certain value has changed.
My specific requirement is to use a Socket for server communication,
and the flow is the following:
User requests some data - data is retrieved by sending data to the socket and wait for the response.
The module that communicates with the server should open a Socket connection, and only after the connection established, it may fetch the requested data.
While Socket attempt to connect, the user may request some more data.
After the connection established successfully the module should perform all the requests sent by the user while connection process was in progress.
The module also should publish the results that came for each data sent to the socket.
How can this be accomplished using RXJava2?
You could use an UnicastSubject for the queue part and do some flatMap-ping once the connection is established:
UnicastSubject<String> userRequests = new UnicastSubject.create();
Single.fromCallable(() -> new Socket("server", port))
.subscribeOn(Schedulers.io())
.flatMapObservable(socket -> {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte[] responseBuffer = new byte[4096];
return userRequests
.observeOn(Schedulers.io())
.map(request -> {
out.write(request.getBytes());
int n = in.read(responseBuffer);
if (n >= 0) {
return new String(responseBuffer, 0, n);
}
throw new IOException("Socket closed while waiting for response");
})
.doFinally(() -> socket.close());
});
Since you are working on the Socket level, it is your responsibility to work out the proper encoding of the requests to be written and the proper decoding of the response to be read (i.e., how long (in bytes) the response is to a particular request).
I believe you would need FlowableTransformers.valve() for this, from RxJava2Extensions.
It should work something like this
PublishSubject<String> jobs = PublishSubject.create().toSerialized();
BehaviorSubject<Boolean> isConnected = BehaviorSubject.createDefault(false);
CompositeDisposable disposables = new CompositeDisposable();
public void connect() {
disposables.add(socketService.subscribe((success) -> {
isConnected = true;
}));
}
public void addJob(String job) {
jobs.onNext(job);
}
public void executeQueuedTasks() {
disposables.add(jobs
.toFlowable(BackpressureStrategy.BUFFER)
.compose(FlowableTransformers.valve(isConnected))
.subscribeWith(new DisposableObserver<>() {
...
})
);
}
public void destroy() {
disposables.clear();
}
}
But the UnicastSubject sample above is more likely to work, I wrote this off the top of my head.

Android USB host interrupt transfer: how to TX and RX at any time

I'm trying to communicate with a USB device that uses interrupt transfer for communications.
it is not a polled device, either side may send at any time. All the examples I find seem to be poll-response where you send data first, wait for the send to complete, then wait for the response, process it and then go back to sending data again.
My code is modeled after the following which I found here on stackoverflow (I'm showing the original I based it on because my own code has a lot more going on and is less compact)
boolean retval = request.queue(buffer, 1);
if (mConnection.requestWait() == request) {
// wait for confirmation (request was sent)
UsbRequest inRequest = new UsbRequest();
// URB for the incoming data
inRequest.initialize(mConnection, mEndpointIn);
// the direction is dictated by this initialisation to the incoming endpoint.
if(inRequest.queue(buffer, bufferMaxLength) == true){
mConnection.requestWait();
// wait for this request to be completed
// at this point buffer contains the data received
}
}
The second requestWait() will block until something arrives, so I can't do another TX operation until I receive something! What am I missing?
You said: "The second requestWait() will block until something arrives, so I can't do another TX operation until I receive something"
Having written code of my own also based on the example you show, I think I understand where you are confused: The second requestWait() will return for any USB operation, not just the one that preceeded it. (from the Android API documentation "Note that this may return requests queued on multiple UsbEndpoints")
So if you queue a Send request even while you are waiting, your "receive waitRequest" will return, but for the Send endpoint. You should always check the endpoint of the result of waitRequest, or compare it to the initial request itself. If it matches inRequest, then it's actually the receive operation you were blocking on. If it doesn't match, compare it to your Send request (or in my example code below, I simply assume that it's a send response and ignore it)
You will need to queue send and receive requests from different methods or threads however rather than in the same loop as is implied by the code you supplied.
Here is the code from my own project (be aware that my app is running into heap corruptions, so the code below may not be perfect, but it does allow me to send even while a receive operation is pending)
So here is my receive loop, you'll see the similarities with your code:
while(mUsbDevice != null ) {
if (inRequest.queue(buffer, BUFFER_SIZE) == true) {
// (mUsbConnection.requestWait() is blocking
if (mUsbConnection.requestWait() == inRequest){
// this is an actual receive
// do receive processing here (send to conusmer)
} else{
Log.d(TAG, "mConnection.requestWait() returned for a different request (likely a send operation)");
}
} else {
Log.e(TAG, "failed to queue USB request");
}
buffer.clear();
}
I do the sending form another thread which uses messages to queue incoming send requests:
mHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 1) { // 1 means send a 64 bytes array in msg.obj
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
final byte[] array = (byte[]) msg.obj;
buffer.clear();
buffer.put( array );
UsbRequest outRequest = new UsbRequest();
outRequest.initialize(mUsbConnection, mUsbEndpointOut);
outRequest.queue(buffer, BUFFER_SIZE);
Log.d(L.TAG, "Queueing request:"+outRequest);
// don't do a mConnection.requestWait() here, ReceiveThread is already listening
} else if (msg.what == 2) { // 2 means exit
Log.d(L.TAG, "SenderThread::handleMessage(): terminate");
Looper.myLooper().quit();
} else {
Log.e(L.TAG, "SenderThread::handleMessage(): unknow message type: " + msg.what);
}
}
};

Categories

Resources