Android Tutorial [B4X] AsyncStreams Tutorial

Status
Not open for further replies.
New video tutorial:


AsyncStreams allows you to read data from an InputStream and write data to an OutputStream without blocking your program. The reading and writing are done with two separate threads.

When new data is available the NewData event is raised with the data.
When you write data to the OutputStream the data is added to an internal queue and then sent in the background.

AsyncStreams is very useful when working with slow streams like network streams or Bluetooth streams.

If you work with such streams using the main thread your program may hang or block while waiting for a value.
The way we tried to deal with it in the Serial tutorial and Network tutorial is with a Timer that checks whether there are bytes waiting in the buffer. However even if there are bytes available there is a chance that not enough are available and then our program will hang or block until those are available.
Using AsyncStreams is usually simpler and safer.

AsyncStreams can work in two modes. Regular mode and "prefix mode". Both work as described above.

The following code demonstrates a simple program that sends text to a connected device or computer:
B4X:
Sub Process_Globals
    Dim AStreams As AsyncStreams
    Dim Server As ServerSocket
    Dim Socket1 As Socket
End Sub
Sub Globals
    Dim EditText1 As EditText
End Sub

Sub Activity_Create(FirstTime As Boolean)
    If FirstTime Then
        Server.Initialize(5500, "Server")
        Server.Listen
        Log("MyIp = " & Server.GetMyIP)
    End If
    EditText1.Initialize("EditText1")
    EditText1.ForceDoneButton = True
    Activity.AddView(EditText1, 10dip, 10dip, 300dip, 60dip)
End Sub

Sub Server_NewConnection (Successful As Boolean, NewSocket As Socket)
    If Successful Then
        ToastMessageShow("Connected", False)
        Socket1 = NewSocket
         'Can only use prefix mode if both sides of the connection implement the prefix protocol!!!
        AStreams.InitializePrefix(Socket1.InputStream, False, Socket1.OutputStream, "AStreams")
    Else
        ToastMessageShow(LastException.Message, True)
    End If
    Server.Listen
End Sub

Sub AStreams_NewData (Buffer() As Byte)
    Dim msg As String
    msg = BytesToString(Buffer, 0, Buffer.Length, "UTF8")
    ToastMessageShow(msg, False)
    Log(msg)
End Sub

Sub AStreams_Error
    ToastMessageShow(LastException.Message, True)
    Log("AStreams_Error")
End Sub

Sub AStreams_Terminated
Log("AStreams_Terminated")
End Sub

'press on the Done button to send text
Sub EditText1_EnterPressed
    If AStreams.IsInitialized = False Then Return
    If EditText1.Text.Length > 0 Then
        Dim buffer() As Byte
        buffer = EditText1.Text.GetBytes("UTF8")
        AStreams.Write(buffer)
        EditText1.SelectAll
        Log("Sending: " & EditText1.Text)
    End If
End Sub

Sub Activity_Pause(UserClosed As Boolean)
    If UserClosed Then
        Log("closing")
        AStreams.Close
        Socket1.Close
    End If
End Sub
Once there is a connection we initialize the AsyncStreams object:
B4X:
AStreams.InitializePrefix(Socket1.InputStream, False, Socket1.OutputStream, "AStreams")
Then when there is new data available we convert the bytes to string and show it:
B4X:
Sub AStreams_NewData (Buffer() As Byte)
    Dim msg As String
    msg = BytesToString(Buffer, 0, Buffer.Length, "UTF8")
    ToastMessageShow(msg, False)
End Sub
When the user enters text in the EditText, the text is being sent:
B4X:
Sub EditText1_EnterPressed
    If AStreams.IsInitialized = False Then Return
    If EditText1.Text.Length > 0 Then
        Dim buffer() As Byte
        buffer = EditText1.Text.GetBytes("UTF8")
        AStreams.Write(buffer)
        EditText1.SelectAll
        Log("Sending: " & EditText1.Text)
    End If
End Sub
Prefix mode

When the object is initialized in prefix mode the data is expected to adhere to the following protocol: every message (bytes array) should be prefixed with the bytes array length (as an Int). So if another device sends us a message made of 100 bytes. The stream is expected to include 4 bytes with a value of 100 and then the 100 bytes.
The NewData event will be raised with the 100 bytes. It will not include the 4 prefix bytes.
When you send data with Write or Write2 the bytes length will be added automatically as the prefix of this message.
If you can work in prefix mode, which is usually only possible if you are implementing both sides, it is highly recommended to do so. Under the cover AsyncStreams uses the message length prefix to make sure that the NewData event is always raised with complete messages. If for example it expects 100 bytes and only 60 bytes arrived, it will wait till the other 40 bytes arrive. In regular mode the event will be raised twice and you will need to handle the two parts of the message.
AsyncStreams also handles the case where 100 bytes are expected and more than 100 bytes arrived (which means that there are several messages).

Note that the WriteStream method that is only available in prefix mode uses an internal protocol which includes error detection.

Errors

The Error event will be raised if there is any error. You can use LastException to find the reason for the error. In most cases you will want to close the connection when an error occurs.

The Terminated event will be raised when the connection is unexpectedly terminated.

Related links:
AsyncStreamsText class offers an alternative to prefix mode when working with streams of text.
New example that uses the Starter service and B4XSerializator: https://www.b4x.com/android/forum/threads/network-asyncstreams-b4xserializator.72149/

Which mode / framework to choose?

There are three modes or frameworks available: AsyncStreams, AsyncStreams in prefix mode and AsyncStreamsText class.

Prefix mode can only be used if both sides of the connection follow the "prefix" protocol. In most cases you can only use this mode when you implement both sides of the connection.


If you are communicating with a non B4X app (and cannot implement the prefix protocol) then you have two options:
- If the data sent and received is text based and each message ends with an end of line character(s) then you should use AsyncStreamsText. For example if you are connecting to an external GPS. Note that it is also possible to modify the class and support different delimiters.
The advantage of using AsyncStreamsText is that it will build the messages correctly. You will not receive partial messages (or multiple messages together).
- In other cases you should use AsyncStreams in regular mode. This means that there is no guarantee that each sent message will be received as a single message. In fact it is more or less guaranteed not to happen. So your code is responsible for correctly collecting and receiving the message.
You can use BytesBuilder class to collect and parse the messages.

Notes

When a new message is available the background thread that is responsible for reading data sends a message to the main thread message queue. This message causes the NewData event to be raised. If you are receiving many messages repeatedly and you show a modal dialog (like Msgbox) then it is possible that the order of events will be changed.
 
Last edited:

Roberto P.

Well-Known Member
Licensed User
Longtime User
Sample working code posted


Please find attached:
- SocketServer.zip (this has java code for a socket that runs on a server, listens to the text that client sends, and echoes it back)

-TestClientSocket.zip (this has B4a code - you can install on your device and debug to test Asyncstreams as well as Socket connections etc)

How to run?:
-unzip the SocketServer.zip. Copy the two files inside this zip into a directory. You need Java installed on this machine. If you do not have Java installed - you can download Java from here
- On commandline - go to the folder which has the above two files
- In that folder: Run on commandline : java SocketServer
- the server will now start and by default listen on port 9092
- if you want to choose a different port - run commandline : java SocketServer <yourchosenportnumber>
- eg: java SocketServer 8081
- IMPORTANT: Make sure that firewall on the machine where you install this server allows connections on the port you have chosen!


flyingbag

Hi Flyingbag
I ask the courtesy to provide the class file of your server instance? thank you very much
 

GaNdAlF89

Active Member
Licensed User
Longtime User
How can I do something like a ping to bluetooth printer? I want to check if printer is on before send it the data to be printed..
 

EduardoElias

Well-Known Member
Licensed User
Longtime User
I am using stream to read a Bluetooth to serial converted connected to a device that measure weight.

It works now.

However the way it is used, it is needed to wait for the weight.

I send a request for the device and it immediately returns with the weight.

Is there a way to wait for the bytes come from the stream? Then the user can move to the next steps.
 

joedarock

Member
Licensed User
Longtime User
Data from my remote USB serial device arrives in messages of 14 bytes. These get broken up by Asyncstreams into a number of NewData events, sometimes containing 1 byte, sometimes 4, sometimes 5, etc and different every time. I reassemble them as they arrive into a single 14 byte message array.

My question is "How does AsyncStreams decide when to raise a NewData event and why are the buffer lengths different every time?". Also, It seems that every alternate time I poll the device, the last byte of the last NewData event is missing. Is there any common explanation for this?

Joe
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
These get broken up by Asyncstreams into a number of NewData events
AsyncStreams doesn't break the messages. AsyncStreams (in non-prefix mode) reads the data from the input stream and then raises the event with the data it was able to read.

Also, It seems that every alternate time I poll the device, the last byte of the last NewData event is missing. Is there any common explanation for this?
No. Can you post your code? Preferably in a new thread.
 

joedarock

Member
Licensed User
Longtime User
AsyncStreams doesn't break the messages. AsyncStreams (in non-prefix mode) reads the data from the input stream and then raises the event with the data it was able to read.


No. Can you post your code? Preferably in a new thread.
If it doesn't btreak up the stream, why is there 4, sometimes 5, events in a 14 byte message stream?
 

joedarock

Member
Licensed User
Longtime User
I'm having a devil of a time using USBSerial and AsyncStreams to make my polling application work and I'd appreciate any suggestions to break through this basic communications problem so I can move on with the value-add code.

I'm polling a low speed (2400 baud) microcontroller-based device using the USBSerial library. The USBSerial part seems to work OK, but I'm having difficulty with the AsyncStreams part. My application uses a 12-byte forward message to the device and a 14-byte return message from the device, all in binary (not ASCII encoded). The sequence to poll a device from start-up looks like this:
1) Send a Reset command
2) Wait for the device to reset and be ready to hear messages
3) Send a 'ping' message to verify the device is alive
4) Get all 14 bytes of the ping message before sending the next message
5) and so on...

I'm OK up to somewhere in step 4. If I enter any sort of DO loop to wait for all the data to be available, the NewData event doesn't get raised anymore, so I don't know when to proceed with the next command. A code example is attached.

Any help greatly appreciated.

Joe
 

Attachments

  • Polling debug code.zip
    7.8 KB · Views: 750

Erel

B4X founder
Staff member
Licensed User
Longtime User
If it doesn't btreak up the stream, why is there 4, sometimes 5, events in a 14 byte message stream?
It is not AsyncStreams the breaks the messages. This is how network communication works.

Don't try to do any loop. It will not work.
The solution is to collect the bytes in a List or array until you have a full message and then work with the message.

You can see an example that works with text and looks for an end of line character: http://www.b4x.com/android/forum/threads/27002/#content

Your case is simpler as you know the length of each message.
 

joedarock

Member
Licensed User
Longtime User
OK, I got through the previous problem with a workaround similar to how it's done in AsyncStreamsText (I think that's what it's called). In my NewData event, I count up the received bytes as they arrive , stuffing them into an array, until all the expected bytes in the message are received. It's not a perfect solution for other reasons, but it will work for this application, at least for now.

New problem: If I leave the device app running with B4A in the wireless debug mode and walk away for some period of time (minutes?), then when I return, the NewData event doesn't fire anymore. Everything else in the app (UI button events, AsyncStream writes, etc) work as normal, but no NewData. If I press the "Restart Program" icon (F11) in the B4A gui, the program restarts and works normally.

Suggestions?
Joe
 

joedarock

Member
Licensed User
Longtime User
It's not a 'network" connection, it's a USB serial connection - a 2 foot piece of wire directly to a single device. In all my years developing low speed asynchronous serial data links, even via USB serial adaptors, I've never had this problem or had to resort to the complex and obtuse workarounds required to make this work reliably. Even VB, which runs on top of a lot of OS layers, is much more intuitive, easier and reliable in this respect. Isn't there a simpler method to handle simple USB serial comms or are these difficulties implicit to Android and will always be there?
 

joedarock

Member
Licensed User
Longtime User
How are you interacting with the USB?
With USBSerial library?

OK, I finally broke through and got my thinking process wrapped around this problem. My system is working smoothly and reliably via the USB Serial library and Asyncstreams now. Sorry for any frustration I might have vented here.

For others like me who are accustomed to working with small micro-controllers and small or no operating systems, serial communications on Android requires a different way of thinking. Instead of sending a command and trying to 'waste time' in some sort of loop until the reply comes in, you really have to think in terms of event-driven systems. What I did was basically to build a little 'state-machine' inside the NewData event with Case statements. Each message that I transmit is labeled via global variables with a name and a message type that includes expected number of return bytes and the name of the Case statement that should process it. Each CASE statement concludes with a call to the next message to be transmitted. When the first message in a multi-message transaction is sent, it's response, which comes in multiple NewData events of varying buffer length, is processed and assembled by NewData into a return message array until all the expected bytes arrive. Once a complete and verified return message is received, it triggers the next forward message in the state machine sequence and a new set of returned bytes get built into a new returned message. This process continues until a whole message transaction is complete. There are no 'Waits' or "Do" loops.

Thanks Erel for your patience with me. I think I get it now.

Joe
 

westingenieria

Active Member
Licensed User
Longtime User
Sample working code posted

Hi,
As a beginner I know that it is difficult to wrap your head around Sockets, Server Sockets and Asyncstreams...

Here is sample code that can help you to debug and understand better - it does TCP/IP Socket communication with a server using B4A android app

Please find attached:
- SocketServer.zip (this has java code for a socket that runs on a server, listens to the text that client sends, and echoes it back)

-TestClientSocket.zip (this has B4a code - you can install on your device and debug to test Asyncstreams as well as Socket connections etc)

How to run?:
-unzip the SocketServer.zip. Copy the two files inside this zip into a directory. You need Java installed on this machine. If you do not have Java installed - you can download Java from here
- On commandline - go to the folder which has the above two files
- In that folder: Run on commandline : java SocketServer
- the server will now start and by default listen on port 9092
- if you want to choose a different port - run commandline : java SocketServer <yourchosenportnumber>
- eg: java SocketServer 8081
- IMPORTANT: Make sure that firewall on the machine where you install this server allows connections on the port you have chosen!

-Now unzip the TestClientSocket.zip
- Open the b4a code - make sure that you set the "ip" and "port" fields in sub globals to match the ip of the machine running the above Socketserver and the port that it is listening on
- Install this on a real device (not emulator)
- Connect to server - type the text in lower text box - you should see the echo from the server
- Debug and understand Connect, AsyncStreams Read and Write etc ...

I spent a lot of time trying to understand this - eventually it was very simple. Hope this helps the beginners to get started quickly - just like others out here helped me :)

Enjoy
flyingbag

Attach the code modified little bit. With bookmarks and questions. Help me please.

thanks
 

Attachments

  • TestClientSocket WEST.zip
    352.1 KB · Views: 1,272
Status
Not open for further replies.
Top