Android Question Astreams _NewData running later than should?

ByteCounter

Member
Licensed User
I am new to B4A and am trying to communicate with a Serial device using USBSerial v2.4

I am having difficulty clearly understanding event code priorities for timer events and Astreams_NewData.
The priority of B4A's internal messaging queue system is not clearly explained (at least I can't find documentation)

I want to query a USB>Serial printer by sending the relevant ESCPOS command (working, no problems)
But after sending I need to read a status data byte returned as a result of the above command (using Astreams _NewData, working)
I can do all this OK.

I want to implement a timeout waiting for reply in case the printer is not connected so the app doesn't hang waiting on a reply that will never come.

This is what I'm doing. I am not using Starter Service, all code is in Main.
1. Send ESC POS query status command to printer.
2. Set a (global process) flag RX_NewDataFlag = False (this flag is changed to True by Astreams _NewData event to indicate that we received something)
3. Set a (global process) flag RX_Time_Expired = False and Enable RX timer (ticks every 500mS). The Timer is initialized in Sub Activity_Create by RxTimer.Initialize("RxTimer", 500). The idea here is to use the RX timer to determine if we have finished waiting for an expected reply.
4. Loop until the RX_Time_Expired flag changes to True, indicating that timeout has occurred
5. Check RX_NewDataFlag to see if it is True (meaning data was received). If no data then flag would be False so print error message.

And now the relevant code:
RXTimer_Tick:
B4X:
Sub RxTimer_Tick
   'timer ticks 500mS (RxTimer is setup in ActivityCreate)
   Log("RxTimer tick!")
   RxTimerTickCount = RxTimerTickCount + 1
   If RxTimerTickCount = 4 Then
     RX_Time_Expired = True
     RxTimer.Enabled = False   'disable timer
     RxTimerTickCount = 0 ' reset Rx counter for next pass
     Button2.Text = "Query Status" ' change from wait to normal text
   End If
End Sub

Astreams1_NewData:
B4X:
Sub AStreams1_NewData (Buffer() As Byte)
   ' This code handles incoming serial data from USB serial port
   '
   RX_NewDataFlag = True   ' we received new data so set flag so other code knows
   Dim temp As String
 
   Dim y As Int
   Log("NewData Data Length: " & (Buffer.Length))
   For y = 0 To Buffer.Length -1
     temp = Buffer(y)
     temp = Bit.ToHexString(temp)
     If temp.Length = 1 Then temp = "0" & temp
     temp = temp.ToUpperCase
     Log("RECEIVE (Hex): " & temp)
     EditText1.Text = temp
   Next
 
End Sub

escpos_getstatus:
B4X:
Sub escpos_getstatus(function_n)
   ' Query Printer Status by sending DLE EOT command and receiving status byte (has reply wait timeout so will not hang for disconnected printer)
 
   Log("escpos_getstatus - enter")
   RX_NewDataFlag = False ' set "did we receive new data flag" false
   ' send status cmd
   astreams1.Write(Array As Byte(0x10, 0x04, function_n)) ' ESC d data
   Button2.Text = "Waiting"
   Log("escpos_getstatus - exit")
End Sub

Button2_Click:
B4X:
Sub Button2_Click
   ' Sends TRANSMIT REAL TIME STATUS command
   Log("Button2 - about to call escpos_getstatus")
   ' prepare for receive (setup timer)
   RX_Time_Expired = False ' RxTimer_Tick code will set to true when time up
   RxTimer.Enabled = False
   RxTimer.Enabled = True ' disabling then enabling timer ensures that full timing period will begin when enabled (i.e timer reset)
   Log("Button2 - started rx timer")
   escpos_getstatus(1) ' send the command
   Do While RX_Time_Expired = False
     DoEvents   ' how do I wait here until flag set without using DoEvents?
   Loop
   ' what is happening is RX_Time_Expired gets set True by _Tick but RX_NewDataFlag is False since Astreams1_NewData hasn't run yet!
   Log("Button2 - RXNewDataFlag = " & RX_NewDataFlag)
   If RX_NewDataFlag = False Then
     lblStatus.Text = "Printer Error, no reply (check connection)"
     lblStatus.Visible = True
   Else
     lblStatus.Text = "Printer Connected, Status = 0x" & RX_data_byte
     lblStatus.Visible = True 
   End If
End Sub

I am having trouble with this because it seems that the sequence of events that occur is wrong:

From the Log: (I am running in release mode on some cheap 7" tablet, with B4A bridge & #BridgeLogger: True for logging)
B4X:
Button2 - about to call escpos_getstatus
Button2 - started rx timer
escpos_getstatus - enter
escpos_getstatus - exit
RxTimer tick!
RxTimer tick!
RxTimer tick!
RxTimer tick!
Button2 - RXNewDataFlag = false
NewData Data Length: 1                    <<< AStreams1_NewData now runs!
RECEIVE (Hex): 16
Astreams _NewData is running AFTER I thought it would, so that the RX_NewDataFlag check in Button2_Click fails and "Printer Error" is displayed.
It seems that _NewData runs AFTER Button2_Click code is finished?!
If the _Tick code runs as expected why not _NewData?

Further questions:

1. When does a Timer _Tick or Astreams _NewData event actually run? Anywhere in the middle of another sub's code or ONLY AFTER the sub code has finished?
(if you are familiar with embedded programming like Arduino you know that an Interrupt can occur anytime and the interrupt code will run after each instruction is complete). So I was hoping that B4A would be the same way. However from looking at Log messages it appears that event code only executes after the currently executed sub or event code is done - is this true?

2. Does Astreams _NewData event have the same priority as other events (e.g. Timer _Tick)? The problem is the _NewData event code is running later than it should so the RX_NewDataFlag doesn't get changed to True before the check.
Why is _NewData not running when it should?
How can I make the _NewData a higher priority event so it occurs sooner? (Do I need to move _NewData to starter service? Will it have higher priority)

3. How the the internal B4A messaging system work? Is there anything like DoEvents to get the _NewData code to run sooner?

4. Is there a B4A wiki? (apart from the tutorials/beginner's guide/user's guide). While the forum is helpful a properly organized wiki would be wonderful to have.

Apologies if I have missed something here. I have spent 2 days on this and am a little frustrated!
Thanks for reading, hope someone can help!
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
1. Don't use DoEvents. It will not solve any problem and only cause new problems.
2. Don't try to hold the main thread. It will not work. In the future there will be support for resumable subs that will make such tasks simpler.

Anywhere in the middle of another sub's code or ONLY AFTER the sub code has finished?
Only after the current code completes and the main thread is free to process the message queue.
All events have the same priority.

There was a wiki in the past however it was not maintained and eventually was removed. Use the forum search field. It works and will return many relevant discussions.
 
Upvote 0

ByteCounter

Member
Licensed User
1. Don't use DoEvents. It will not solve any problem and only cause new problems.
2. Don't try to hold the main thread. It will not work. In the future there will be support for resumable subs that will make such tasks simpler.

@Erel
1. By "don't try to hold the main thread" do you mean "No Do... Loop"? If so, how do you suggest handling a case where main code (Button2_Click) has to wait on a flag that is changed by Timer _Tick or Astreams _NewData ?

2. Without using DoEvents in the Button2_Click loop the app hangs but with it the loop works as expected.
i.e.
B4X:
Do While RX_Time_Expired = False
     'DoEvents   ' loop hangs with DoEvents commented out but works with DoEvents in loop? 
Loop
Why does it work with DoEvents (in the loop) if it is "bad" to use?

3. Why does Astreams _NewData event trigger AFTER the Button2_Click code? (see logs above) The timer tick event triggers as expected but _NewData doesn't fire when it should (considering it only receives a single byte at 19200bps and I wait 2 seconds for _NewData to change RX_NewDataFlag before checking it). The NewData event runs after Button2_Click finishes. Any ideas?

Thanks for your help
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
By "don't try to hold the main thread" do you mean "No Do... Loop"? If so, how do you suggest handling a case where main code (Button2_Click) has to wait on a flag that is changed by Timer _Tick or Astreams _NewData ?
Until resumable subs will be available, you cannot wait for an event to happen. You need to continue the program flow from AStream_NewData.

Without using DoEvents in the Button2_Click loop the app hangs but with it the loop works as expected.
As I wrote, you should not try to hold the main thread in such loops. It will not work. Even if it looks like it works in a small example, it will later fail.

Why does Astreams _NewData event trigger AFTER the Button2_Click code?
See my previous post: Only after the current code completes and the main thread is free to process the message queue.
 
Upvote 0

SteveTerrell

Active Member
Licensed User
Longtime User
I am new to B4A and am trying to communicate with a Serial device using USBSerial v2.4

I am having difficulty clearly understanding event code priorities for timer events and Astreams_NewData.
The priority of B4A's internal messaging queue system is not clearly explained (at least I can't find documentation)

I want to query a USB>Serial printer by sending the relevant ESCPOS command (working, no problems)
But after sending I need to read a status data byte returned as a result of the above command (using Astreams _NewData, working)
I can do all this OK.

I want to implement a timeout waiting for reply in case the printer is not connected so the app doesn't hang waiting on a reply that will never come.

This is what I'm doing. I am not using Starter Service, all code is in Main.
1. Send ESC POS query status command to printer.
2. Set a (global process) flag RX_NewDataFlag = False (this flag is changed to True by Astreams _NewData event to indicate that we received something)
3. Set a (global process) flag RX_Time_Expired = False and Enable RX timer (ticks every 500mS). The Timer is initialized in Sub Activity_Create by RxTimer.Initialize("RxTimer", 500). The idea here is to use the RX timer to determine if we have finished waiting for an expected reply.
4. Loop until the RX_Time_Expired flag changes to True, indicating that timeout has occurred
5. Check RX_NewDataFlag to see if it is True (meaning data was received). If no data then flag would be False so print error message.

And now the relevant code:
RXTimer_Tick:
B4X:
Sub RxTimer_Tick
   'timer ticks 500mS (RxTimer is setup in ActivityCreate)
   Log("RxTimer tick!")
   RxTimerTickCount = RxTimerTickCount + 1
   If RxTimerTickCount = 4 Then
     RX_Time_Expired = True
     RxTimer.Enabled = False   'disable timer
     RxTimerTickCount = 0 ' reset Rx counter for next pass
     Button2.Text = "Query Status" ' change from wait to normal text
   End If
End Sub

Astreams1_NewData:
B4X:
Sub AStreams1_NewData (Buffer() As Byte)
   ' This code handles incoming serial data from USB serial port
   '
   RX_NewDataFlag = True   ' we received new data so set flag so other code knows
   Dim temp As String

   Dim y As Int
   Log("NewData Data Length: " & (Buffer.Length))
   For y = 0 To Buffer.Length -1
     temp = Buffer(y)
     temp = Bit.ToHexString(temp)
     If temp.Length = 1 Then temp = "0" & temp
     temp = temp.ToUpperCase
     Log("RECEIVE (Hex): " & temp)
     EditText1.Text = temp
   Next

End Sub

escpos_getstatus:
B4X:
Sub escpos_getstatus(function_n)
   ' Query Printer Status by sending DLE EOT command and receiving status byte (has reply wait timeout so will not hang for disconnected printer)

   Log("escpos_getstatus - enter")
   RX_NewDataFlag = False ' set "did we receive new data flag" false
   ' send status cmd
   astreams1.Write(Array As Byte(0x10, 0x04, function_n)) ' ESC d data
   Button2.Text = "Waiting"
   Log("escpos_getstatus - exit")
End Sub

Button2_Click:
B4X:
Sub Button2_Click
   ' Sends TRANSMIT REAL TIME STATUS command
   Log("Button2 - about to call escpos_getstatus")
   ' prepare for receive (setup timer)
   RX_Time_Expired = False ' RxTimer_Tick code will set to true when time up
   RxTimer.Enabled = False
   RxTimer.Enabled = True ' disabling then enabling timer ensures that full timing period will begin when enabled (i.e timer reset)
   Log("Button2 - started rx timer")
   escpos_getstatus(1) ' send the command
   Do While RX_Time_Expired = False
     DoEvents   ' how do I wait here until flag set without using DoEvents?
   Loop
   ' what is happening is RX_Time_Expired gets set True by _Tick but RX_NewDataFlag is False since Astreams1_NewData hasn't run yet!
   Log("Button2 - RXNewDataFlag = " & RX_NewDataFlag)
   If RX_NewDataFlag = False Then
     lblStatus.Text = "Printer Error, no reply (check connection)"
     lblStatus.Visible = True
   Else
     lblStatus.Text = "Printer Connected, Status = 0x" & RX_data_byte
     lblStatus.Visible = True
   End If
End Sub

I am having trouble with this because it seems that the sequence of events that occur is wrong:

From the Log: (I am running in release mode on some cheap 7" tablet, with B4A bridge & #BridgeLogger: True for logging)
B4X:
Button2 - about to call escpos_getstatus
Button2 - started rx timer
escpos_getstatus - enter
escpos_getstatus - exit
RxTimer tick!
RxTimer tick!
RxTimer tick!
RxTimer tick!
Button2 - RXNewDataFlag = false
NewData Data Length: 1                    <<< AStreams1_NewData now runs!
RECEIVE (Hex): 16
Astreams _NewData is running AFTER I thought it would, so that the RX_NewDataFlag check in Button2_Click fails and "Printer Error" is displayed.
It seems that _NewData runs AFTER Button2_Click code is finished?!
If the _Tick code runs as expected why not _NewData?

Further questions:

1. When does a Timer _Tick or Astreams _NewData event actually run? Anywhere in the middle of another sub's code or ONLY AFTER the sub code has finished?
(if you are familiar with embedded programming like Arduino you know that an Interrupt can occur anytime and the interrupt code will run after each instruction is complete). So I was hoping that B4A would be the same way. However from looking at Log messages it appears that event code only executes after the currently executed sub or event code is done - is this true?

2. Does Astreams _NewData event have the same priority as other events (e.g. Timer _Tick)? The problem is the _NewData event code is running later than it should so the RX_NewDataFlag doesn't get changed to True before the check.
Why is _NewData not running when it should?
How can I make the _NewData a higher priority event so it occurs sooner? (Do I need to move _NewData to starter service? Will it have higher priority)

3. How the the internal B4A messaging system work? Is there anything like DoEvents to get the _NewData code to run sooner?

4. Is there a B4A wiki? (apart from the tutorials/beginner's guide/user's guide). While the forum is helpful a properly organized wiki would be wonderful to have.

Apologies if I have missed something here. I have spent 2 days on this and am a little frustrated!
Thanks for reading, hope someone can help!




If you comment out all of the timer code in Button2_Click (i.e. just have Log("Button2 - started rx timer") and escpos_getstatus(1) ' send the command ) then exit that sub.
Then run the app and time between the log entries ->Log("Button2... and ->Log("RECEIVE you can see how long it takes for the reply to be received?
You can always add DateTime.Time(DateTime.Now) to the log line to timestamp the log entry.
It will probably be difficult to be faster than that time.

I would suggest that

B4X:
Log("Button2 - RXNewDataFlag = " & RX_NewDataFlag)
   If RX_NewDataFlag = False Then
     lblStatus.Text = "Printer Error, no reply (check connection)"
     lblStatus.Visible = True
   Else
     lblStatus.Text = "Printer Connected, Status = 0x" & RX_data_byte
     lblStatus.Visible = True
   End If

is better placed in the timer tick code where you set RX_Time_Expired.


Then you can remove all of

B4X:
Do While RX_Time_Expired = False
     DoEvents   ' how do I wait here until flag set without using DoEvents?
   Loop
   ' what is happening is RX_Time_Expired gets set True by _Tick but RX_NewDataFlag is False since Astreams1_NewData hasn't run yet!
   Log("Button2 - RXNewDataFlag = " & RX_NewDataFlag)
   If RX_NewDataFlag = False Then
     lblStatus.Text = "Printer Error, no reply (check connection)"
     lblStatus.Visible = True
   Else
     lblStatus.Text = "Printer Connected, Status = 0x" & RX_data_byte
     lblStatus.Visible = True
   End If

from Button2_Click.

The code will then start to look more event driven and probably work better.
 
Upvote 0
Top