#Region Project Attributes
#MainFormWidth: 600
#MainFormHeight: 600
#End Region
Sub Process_Globals
Private fx As JFX
Private MainForm As Form
Private xui As XUI
Private Button1 As B4XView
Private bc As ByteConverter
Private b(20) As Byte
Private strToReceive As String
Private noStringsReceived As Int
Private bigString As String
Private RxDataComplete As Boolean
End Sub
Sub AppStart (Form1 As Form, Args() As String)
MainForm = Form1
MainForm.RootPane.LoadLayout("Layout1")
MainForm.Show
End Sub
Sub Button1_Click
bigString = ""
RxDataComplete = True
strToReceive = "My new String"
trigger
For k = 0 To 4
Log(DateTime.Time(DateTime.Now))
wait for (trigger) Complete(ret As Boolean)
Log(DateTime.Time(DateTime.Now))
b = bc.StringToBytes((strToReceive & " " & k), "UTF8")
strm_NewData(b)
Next
Log("All done")
End Sub
Private Sub strm_NewData(buffer() As Byte)
strToReceive = bc.StringFromBytes(buffer, "UTF8")
Log(strToReceive)
ProcessData(strToReceive)
End Sub
Private Sub ProcessData(s As String)
Sleep(2000)
noStringsReceived = noStringsReceived + 1
bigString = bigString & s & Chr(10)
Log(bigString)
If noStringsReceived < 5 Then
Log("No Received " & noStringsReceived & " " & DateTime.Time(DateTime.Now))
End If
Sleep(10)
End Sub
Private Sub trigger As ResumableSub
Sleep(1)
Return True
End Sub
That's it, so:..I didn't get that the Wait For sub actually called the sub, ...
Sub Button1_Click
:
'' trigger 'As you say, this shouldn't be here because.....'
For k = 0 To 4
:
wait for (trigger) Complete(ret As Boolean) '....this calls trigger
:
Next
End Sub
I think that's where the AsyncStreamsText class comes in. In your other thread you said you have a sub that is a version of AsyncStreamsText. but you should use the whole AsyncStreamsText class. Use its astreams_NewData sub to buffer the incoming data until you hit the termination character, then send it back to the calling sub.Sadly asyncStreams doesn't have a frame terminator feature so data turns up in chunks with repeated calls to asyncStreams_NewData.
Private Sub astreams_NewData (Buffer() As Byte)
buffer.Append(BytesToString(Buffer, 0, Buffer.Length, charset)) 'buffer is StringBuilder'
Dim s As String = buffer.ToString
Dim endFound As Boolean = s.EndsWith("EndCharacter") 'Or better use regex to make sure you've got a suitable, complete reply.
If endFound Then
CallSubDelayed2(mTarget, mEventName & "_NewText", s) 'callback to AsyncStreamsText_NewText in the calling module'
buffer.Initialize 'clear the buffer
End If
End Sub
Private ast As AsyncStreamsText
Sub Button1_Click
ast.Initialize(Me, "ast", NewSocket.InputStream, NewSocket.OutputStream) 'initialize AsyncStreamsText with the socket streams.
Wait For Reply_Complete 'which is called when AsyncStreamsText passes a complete reply back to ast_NewText in this module
End Sub
'Called from class AsyncStreamsText.astreams_NewData'
Private Sub ast_NewText (Text As String)
'Do whatever validation, processing you want........'
CallSubDelayed(Me, "Reply_Complete") 'Or CallSubDelayed2(...) with a 'success' parameter if you want for example
End Sub
that's where the AsyncStreamsText class comes in
What is the ready for next command prompt?
Is it a string of ascii characters or something else?
It then returns a reply dependent on the command issued. At the end of the reply it sends a <LF>cr>space.
FWIW: I would point out that in serial communications there is a possibility of multiple responses being returned concurrently, you may get more than one full or full-and-partial response in the receive event. Your buffering logic should account for that and maintain the buffer accordingly.OK, I think I know what you're getting at now. A few points....
That's it, so:
But, there's more than one way to use Wait For, which may be where your confusion lies....B4X:Sub Button1_Click : '' trigger 'As you say, this shouldn't be here because.....' For k = 0 To 4 : wait for (trigger) Complete(ret As Boolean) '....this calls trigger : Next End Sub
I think that's where the AsyncStreamsText class comes in. In your other thread you said you have a sub that is a version of AsyncStreamsText. but you should use the whole AsyncStreamsText class. Use its astreams_NewData sub to buffer the incoming data until you hit the termination character, then send it back to the calling sub.
So, in the AsyncStreamsText.astreams_NewData sub, you'll have something like:
B4X:Private Sub astreams_NewData (Buffer() As Byte) buffer.Append(BytesToString(Buffer, 0, Buffer.Length, charset)) 'buffer is StringBuilder' Dim s As String = buffer.ToString Dim endFound As Boolean = buffers.EndsWith("EndCharacter") 'Or better use regex to make sure you've got a suitable, complete reply. If endFound Then CallSubDelayed2(mTarget, mEventName & "_NewText", s) 'callback to AsyncStreamsText_NewText in the calling module' buffer.Initialize 'clear the buffer End If End Sub
Then in the module that calls AsyncStreamsText something like:
B4X:Private ast As AsyncStreamsText Sub Button1_Click ast.Initialize(Me, "ast", NewSocket.InputStream, NewSocket.OutputStream) 'initialize AsyncStreamsText with the socket streams. Wait For Reply_Complete 'which is called when AsyncStreamsText passes a complete reply back to ast_NewText in this module End Sub 'Called from class AsyncStreamsText.astreams_NewData' Private Sub ast_NewText (Text As String) 'Do whatever validation, processing you want........' CallSubDelayed(Me, "Reply_Complete") 'Or CallSubDelayed2(...) with a 'success' parameter if you want for example End Sub
OK, then in the AsyncStreamsText.astreams_NewData sub couldn't @rgarnett1955 do something like:It is a string of 5 ASCII characters: linefeed 'c' 'r' '>' space
Private Sub astreams_NewData (Buffer() As Byte)
buffer.Append(BytesToString(Buffer, 0, Buffer.Length, charset)) 'buffer is StringBuilder'
Dim s As String = buffer.ToString
Dim endFound As Boolean = s.EndsWith("EndCharacter") 'Or better, use regex to make sure you've got a suitable, complete reply.
If endFound Then
CallSubDelayed2(mTarget, mEventName & "_NewText", s) 'callback to AsyncStreamsText_NewText in the calling module'
Else If s.EndsWith($"${Chr(10)}cr> "$) Then 'again regex or s.Contains might be better
CallSubDelayed(mTarget, mEventName & "_ReadyForNextCommand") 'callback to AsyncStreamsText_ReadyForNextCommand in the calling module'
buffer.Initialize 'clear the buffer
End If
End Sub
All the partial bits should arrive in the correct order though, right?you may get more than one full or full-and-partial response in the receive event
One time at band camp... No, seriously, I ran into a problem with a specific series of Dell computers that had an odd UART implementation. Other PC's were fine, just that one series had an issue communicating with a serial control device.All the partial bits should arrive in the correct order though, right?
So if we're buffering until we see the 'EndCharacter' &/or the 'ready for next command prompt' then we can be sure that what we have in the buffer contains everything we need, in the right order? (even if we have to apply some regex matching to remove surplus characters)
Message 01X
Message 02X
Message 01XMe
ssage 02X
What is the ready for next command prompt?
Is it a string of ascii characters or something else?
I may be missing somthing here but if it's "cr> ", then why can't @rgarnett1955 just pick it out of the incoming stream in the AsyncStreamsText.astreams_NewData sub using regex or bH
?
ch> scan 320M 330M 21 3 <== Text input Tx to TSA
320000000 -1.178125e+02 0.000000000 <== Data returned Rx From TSA
320500000 -1.163125e+02 0.000000000 .
321000000 -1.168125e+02 0.000000000 .
321500000 -1.153125e+02 0.000000000 .
322000000 -1.163125e+02 0.000000000 .
322500000 -1.168125e+02 0.000000000 .
323000000 -1.138125e+02 0.000000000 .
323500000 -1.158125e+02 0.000000000 .
324000000 -1.178125e+02 0.000000000 .
324500000 -1.188125e+02 0.000000000 .
325000000 -1.158125e+02 0.000000000 .
325500000 -1.168125e+02 0.000000000 .
326000000 -1.203125e+02 0.000000000 .
326500000 -1.178125e+02 0.000000000 .
327000000 -1.188125e+02 0.000000000 .
327500000 -1.148124e+02 0.000000000 .
328000000 -1.153125e+02 0.000000000 .
328500000 -1.143125e+02 0.000000000 .
329000000 -1.153125e+02 0.000000000 .
329500000 -1.158125e+02 0.000000000 .
330000000 -1.163125e+02 0.000000000 < Up to a maximum of 260 lines
ch> <== TSA Prompt
the question isn't how to detect the end of a receive stream it is how do I stop a loop that sends commands to the TSA from incrementing on until I the eof from the NewData sub has been received.
I'm perfectly willing to believe that I can't give you what you're after, but not that B4X can'tIf jSerial and Asynchstreams had the facility to detect an eof with a timeout such that the new data sub didn't fire until the eof or timeout then it would be simple, but it does not have this facility.
Sub Process_Globals
Private ast As AsyncStreamsText
Private ListOfCommands as List
End Sub
Sub Button1_Click
ast.Initialize(Me, "ast", socket.InputStream, socket.OutputStream)
For k = 0 To ListOfCommands.Size-1
Dim command as string = ListOfCommands.Get(k)
Dim cancel(1) As Boolean
Timeout(cancel, 2, "Reply_Complete")
ast.Write(command)
Wait For Reply_Complete
If cancel(0) Then 'command timed out.
Log("Time out: No valid reply received.")
Exit 'Exit the loop unless you want the next command send regardless'
Else
cancel(0) = True 'cancels the timeout
Log("Valid Reply Received. Continue to next command")
End If
Next
End Sub
'Text received here has been validated in AsyncStreamsText as a complete valid reply with the eof characters'
Private Sub ast_NewText (Text As String)
Log(Text)
CallSubDelayed(Me, "Reply_Complete")
End Sub
'https://www.b4x.com/android/forum/threads/b4x-b4xlib-waitforwithtimeout-timeouthandler.110703/post-690966'
Sub Timeout(Cancel() As Boolean, Duration As Int, EventName As String)
Do While Duration > 0
'lblCountdown.Text = Duration
Sleep(1000)
If Cancel(0) Then Return
Duration = Duration - 1
Loop
Cancel(0) = True
CallSubDelayed(Me, EventName)
End Sub
#Event: NewText (Text As String)
#Event: Terminated
'version: 1.00
'Class module
Sub Class_Globals
Private mTarget As Object
Private mEventName As String
Private astreams As AsyncStreams
Private sb As StringBuilder
End Sub
Public Sub Initialize (TargetModule As Object, EventName As String, In As InputStream, out As OutputStream)
mTarget = TargetModule
mEventName = EventName
astreams.Initialize(In, out, "astreams")
sb.Initialize
End Sub
'Sends the text. Note that this method does not add end of line characters.
Public Sub Write(Text As String)
astreams.Write(Text.GetBytes("ASCII"))
End Sub
Private Sub astreams_NewData (Buffer() As Byte)
sb.Append(BytesToString(Buffer, 0, Buffer.Length, "ASCII"))
Dim s As String = sb.ToString
Dim endFound As Boolean = s.EndsWith("ch> ") 'Or use regex to make sure you've got a suitable, complete reply as far as possible.
If endFound Then
CallSubDelayed2(mTarget, mEventName & "_NewText", s) 'callback to ast_NewText in the calling module'
sb.Initialize 'clear the buffer ready for the next command's reply
End If
End Sub
Private Sub astreams_Terminated
CallSubDelayed(mTarget, mEventName & "_Terminated")
End Sub
Private Sub astreams_Error
astreams.Close
CallSubDelayed(mTarget, mEventName & "_Terminated")
End Sub
Public Sub Close
astreams.Close
End Sub
the question isn't how to detect the end of a receive stream it is how do I stop a loop that sends commands to the TSA from incrementing on until I the eof from the NewData sub has been received.
Sub Class_Globals
Private Root As B4XView
Private xui As XUI
Private TimeoutTimer As Timer 'Required for this solution
Private buffer As StringBuilder 'Required for this solution
Private testData As List 'For test data
Private listPtr As Int 'For test data
End Sub
Public Sub Initialize
' B4XPages.GetManager.LogEvents = True
TimeoutTimer.Initialize("Timeout", 2000) 'Required - a large value for testing purposes
buffer.Initialize 'Required
'For setting up test data
testData.Initialize
testData.Add($"320000000 -1.178125e+02 0.000000000${CRLF}"$)
testData.Add($"320500000 -1.163125e+02 0.000000000${CRLF}"$)
testData.Add($"ch> "$)
End Sub
'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
Root = Root1
Root.LoadLayout("MainPage")
End Sub
'You can see the list of page related events in the B4XPagesManager object. The event name is B4XPage.
'For emulating receivign test data
Private Sub Button1_Click
Log($"Button1_Click: Sending ${testData.Get(listPtr)}"$)
strm_NewData(testData.Get(listPtr).As(String).GetBytes("UTF8"))
listPtr = listPtr + 1
If listPtr >= testData.Size Then listPtr = 0
End Sub
'Required
Private Sub Timeout_Tick
TimeoutTimer.Enabled = False
Log($"Capturing data timed out. Received ${buffer.Length} bytes"$)
If buffer.Length > 0 Then Log($"Data received: ${buffer.ToString}"$)
CallSubDelayed2(Me, "TSA_NewData", "")
buffer.Initialize ' Reset Buffer
End Sub
'Required
Private Sub strm_NewData(data() As Byte)
If TimeoutTimer.Enabled Then
Dim chunk As String = BytesToString(data, 0, data.Length, "UTF8")
buffer.Append(chunk)
If chunk.ToLowerCase.EndsWith("ch> ") Then
TimeoutTimer.Enabled = False
CallSubDelayed2(Me, "TSA_NewData", buffer.ToString)
buffer.Initialize ' Reset Buffer
End If
Else
Log($"Not expecting any new data, discarding: ${BytesToString(data,0,data.Length, "UTF8")}"$) '
End If
End Sub
'For emulating the Wait For of TSA_NewData
Private Sub Button2_Click
If TimeoutTimer.Enabled = False Then
Log("Waiting for data...")
TimeoutTimer.Enabled = True 'Enabling the timer goes hand in hand with
Wait For TSA_NewData(data As String) 'Waiting for the event
Log("TSA_NewData:")
If data <> "" Then
Log($"${TAB}Raw/unparsed data received: ${CRLF}${data}"$)
Else
Log($"${TAB}Timout occurred"$)
End If
Else
Log("Already waiting...")
End If
End Sub
The above also works in a loop. Change Button2_Click toExcept that it doesn't work; the loop just ignores the Wait For.
So I guess the question is how do I implement a semaphore in B4x.
'For emulating the Wait For of TSA_NewData
Private Sub Button2_Click
If TimeoutTimer.Enabled = False Then
Dim loopCount As Int = 4
Log($"Waiting for data ${loopCount} times"$)
For x = 0 To loopCount - 1
Log($"Wait #${x+1}"$)
TimeoutTimer.Enabled = True 'Enabling the timer goes hand in hand with
Wait For TSA_NewData(data As String) 'Waiting for the event
Log("TSA_NewData:")
If data <> "" Then
Log($"${TAB}Raw/unparsed data received: ${CRLF}${data}"$)
Else
Log($"${TAB}Timout occurred"$)
End If
Next
Else
Log("Already waiting...")
End If
End Sub
the TSA has received some data and has replied with its prompt "cr> "
...
At the end of the reply it sends a <LF>cr>space.
TSA TerraTerm transaction:? ch> scan 320M 330M 21 3 <== Text input Tx to TSA 320000000 -1.178125e+02 0.000000000 <== Data returned Rx From TSA 320500000 -1.163125e+02 0.000000000 . 321000000 -1.168125e+02 0.000000000 . ... 329000000 -1.153125e+02 0.000000000 . 329500000 -1.158125e+02 0.000000000 . 330000000 -1.163125e+02 0.000000000 < Up to a maximum of 260 lines ch> <== TSA Prompt
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?