serial communication in a sequential way

  1. 3 years ago
    Edited 3 years ago

    Hi,

    I'm working on an embedded device that requires using AT commands to do settings and communicate with the board. So I hope to send several commands one by one and base on the response to determine what the next command will be sent out. For example,

    send out "+++" to start AT command mode, if the feedback is "OK" then keep going, if the feedback is "(null) or something else" then resend "+++".

    I did find a discussion here, https://forum.xojo.com/12492-need-a-simple-serial-terminal
    but it seems it's too long ago and I can't see the example by either using data available event or timer with serial.poll methods.

    I also find something here, https://forum.xojo.com/16761-serial-rs232/0
    @Jon O provides a do...loop

    Serial1.Write(SomeCommand)
    
    Do
       Serial1.Poll
    Loop Until Serial1.LookAhead.Instr(Some_Response_I_Want) <> 0
    
    Dim ReadString as String = Serial1.ReadAll

    I tried but for some reason, I always get in the infinite loop and the GUI just hang.

    I understand it's not like in the embedded C programming, you can set a timer and send protocol one by one if something wrong just resends or throws out an interrupt or similar. So what's the best way in Xojo to do the similar job?

    If anyone could help I would appreciate that.

    @BO C Thank you for your response. I guess I do not quite understand the state engine part. It seems you have done this successfully and smoothly. Could you give me some sudo-code example?

    Sure. Here's some actual code that I use to parse data received from a TCPSocket. It is similar to RS-232. When the DataAvailable event of the socket fires, I call a timer and this code below is executed in the timer action event. Greg O'Lone suggested using the timer.

    I have this code in the DataAvailable Event of the socket:

    MySockData = MySockData+t.ReadAll(Encodings.ASCII)
    DataAvailableTimer.Mode = Timer.ModeSingle

    MySockData is a proper of my object.

    Now here is some of the code from the timer. Basically I dump a bunch of commands to a device to get its status, information, etc. Here is how I analyze it. It will be meaningless to you in large part, but I'm showing you how you can basically take the data you have received and parse it or look for specific information. Then once you find the information, you act on it. That's the purpose of a state machine.

    Dim rx As New RegEx
    Dim rxOptions As RegExOptions = rx.Options
    rxOptions.LineEndType = 4
    Dim match As RegExMatch
    Dim matchPosition as Integer
    
    
    // Code to look for firmware version
    
    rx.SearchPattern = "(?msi-U)^([A-Z]\x20?(?:\d+\.){1,2}\d*\x20?[A-Za-z]*.*)\R+</pre>"
    
    match = rx.Search(MySockData)
    
    If match <> Nil Then
      FirmwareVersion = match.SubExpressionString(1)
      Dim fw() As String = FirmwareVersion.Split(EndOfLine)
      FirmwareVersion = fw(0)
    End If
    
    //
    // Code to look for RS232 data in the parameter dump
    
    rx.SearchPattern = "(?msi-U)(soip_type2=[yn])[\r\n]+(soip_guest_on=[yn])"
    
    match = rx.Search(MySockData)
    
    If match <> Nil Then
      Try
        mGuestMode = If (match.SubExpressionString(2).Right(1) = "y", True, False)
        mType2 = If (match.SubExpressionString(1).Right(1) = "y", True, False)
        mGotGuestMode = True
        'system.debuglog"Got RS232 Mode in Param Dump for Device "+Me.DeviceName
        RaiseEvent RS232ModeUpdate
        NotificationCenter.send("RS232ModeUpdated",RS232Mode,Self)
      Catch
        'RS232Mode = ""
      End Try
    End If
    
    // Code to look for baud rate in the parameter dump
    
    rx.SearchPattern = "(?msi-U)s0_baudrate=([0-9]+)-([5678][ENO][12])"
    
    match = rx.Search(MySockData)
    
    If match <> Nil Then  // We have RS232Settings
      
      Try
        RS232Values(0) = match.SubExpressionString(1)
        RS232Values(1) = match.SubExpressionString(2).Uppercase
        RaiseEvent RS232SettingsUpdate
        NotificationCenter.Send("RS232SettingsUpdated",RS232Values,Self)
      Catch
        RS232Values(0) = ""
        RS232Values(1) = ""
      End Try
    End If
    
    // Code to look for the web name
    
    rx.SearchPattern = "(?msi-U)webname=(.*)"
    
    match = rx.Search(MySockData)
    
    If match <> Nil Then
      Webname = match.SubExpressionString(1)
    End If
    
    
    // Code to look for the subnet mask
    
    rx.SearchPattern ="(?msi-U)netmask=((?(DEFINE)(?'octet'25[0-5]|2[0-4]\d|[01]?\d{1,2}))\b(?'octet1'(?&octet))\.(?'octet2'(?&octet))\.(?'octet" + _
    "3'(?&octet))\.(?'octet4'(?&octet)))\b"
    
    match = rx.Search(MySockData)
    
    If match <> Nil Then
      SubnetMask = match.SubExpressionString(1)
      'system.debuglog "Got SubnetMask for "+me.DeviceName
    End If
  2. Derk J

    18 Sep 2017 Testers, Xojo Pro
    Edited 3 years ago

    Do
    Loop until ?entervalue?

    You loop always, serial is event based. No need to poll, use the DataAvailable event.

    Or check for the value inside the loop and use a boolean flag after Until

  3. @Derk J Do
    Loop until ?entervalue?

    You loop always, serial is event based. No need to poll, use the DataAvailable event.

    Or check for the value inside the loop and use a boolean flag after Until

    I need to write one thing wait and then parse the response and based on that, execute other code, etc. That's the AT commands I try to execute. A typical process is following,

    sender: +++
    receiver: OK //the AT mode Starts
    sender: ATOA //what's your operation provider
    receiver: VZW //it's verizon wireless
    sender: ATDL###.###.###.### // change the destination port to ###.###.###.###
    receiver: OK
    sender: ATWR //write the register
    receiver: OK
    sender: ATAC //apply changes
    receiver: OK
    sender: ATCN // exit AT mode
    receiver: OK

    I need to base on the feedback from the target board to decide what code I should execute or run. And from time-wise, these responses should take no more than 500 ms. In that case, the AT mode will end automatically or we should consider it's timeout.
    I know, usually, the serial is event-based, you can have a data available event handler to handle the response, but in my case how to do that and more specifically, how can I debug the code with this event? Can you give an example or point me to it?

  4. Mathias M

    is not verified 19 Sep 2017 Testers Bruges, Belgium

    I don't really understand the problem. Can't you just use an IF condition in the DataAvailable event?

    If receivedText = "OK" Then
    Send("ATOA"
    Else If ..
    
    End If

    Or do I miss something?

  5. Wayne G

    19 Sep 2017 Testers, Xojo Pro, MVP Auckland, New Zealand

    Seems to me you need a state engine.

    Public Sub sendCommand()
      Select Case State
      Case 0
        me.Write("+++")
      Case 1
        me.Write("ATOA")
      Case 2
        me.Write("ATDL" + Port)
      Case 3
        me.Write("ATWR")
      Case 4
        me.Write("ATAC")
      Case 5
        me.Write("ATCN")
      End Select
      
      me.Write(EndOfLine)
      State = State + 1
      
    End Sub
    
    Sub DataAvailable() Handles DataAvailable
      Dim s As String = me.ReadAll
      
      If s.Left(2) = "OK" Or s.Left(3) + "VZW" Then
        sendCommand()
      End If
      
    End Sub
    

    Where State & Port are properties of a sub classed Serial Socket.

  6. John H

    19 Sep 2017 Planet earth

    You might have a look on Roger Meier's Freeware CoolTerm serial port terminal written in Xojo http://freeware.the-meiers.org/

  7. Jon O

    19 Sep 2017 Testers, Xojo Pro Chicago Area USA

    I have struggled mightily trying to get my head around how to best handle synchronous communications in Xojo. I had been using the method you attribute to me earlier. But that just would not always be smooth and you could get into an infinite loop like you have had.

    The thing I have discovered is to let the events do their thing. Write to the socket/port and then analyze the data received and take action after that. Wayne is right when he says to use a state engine.

    I've even gone a step further and am now just writing all my desired commands to the port and then in the DataAvailable event using a series of RegEx functions to select the data that I want to process. That works quite well actually.

    But you need to know what to send based on what was received. So the state engine is the way to go. Analyze what you have received. Branch to code based on that and then write the new command.

    Just let the events take care of themselves. It's so much easier than trying to shoehorn an event driven asynchronous object into being a synchronous object.

  8. @Jon O I have struggled mightily trying to get my head around how to best handle synchronous communications in Xojo. I had been using the method you attribute to me earlier. But that just would not always be smooth and you could get into an infinite loop like you have had.

    The thing I have discovered is to let the events do their thing. Write to the socket/port and then analyze the data received and take action after that. Wayne is right when he says to use a state engine.

    I've even gone a step further and am now just writing all my desired commands to the port and then in the DataAvailable event using a series of RegEx functions to select the data that I want to process. That works quite well actually.

    But you need to know what to send based on what was received. So the state engine is the way to go. Analyze what you have received. Branch to code based on that and then write the new command.

    Just let the events take care of themselves. It's so much easier than trying to shoehorn an event driven asynchronous object into being a synchronous object.

    Thank you for your response. I guess I do not quite understand the state engine part. It seems you have done this successfully and smoothly. Could you give me some sudo-code example?

  9. @Wayne G Seems to me you need a state engine.

    Public Sub sendCommand()
      Select Case State
      Case 0
        me.Write("+++")
      Case 1
        me.Write("ATOA")
      Case 2
        me.Write("ATDL" + Port)
      Case 3
        me.Write("ATWR")
      Case 4
        me.Write("ATAC")
      Case 5
        me.Write("ATCN")
      End Select
      
      me.Write(EndOfLine)
      State = State + 1
      
    End Sub
    
    Sub DataAvailable() Handles DataAvailable
      Dim s As String = me.ReadAll
      
      If s.Left(2) = "OK" Or s.Left(3) + "VZW" Then
        sendCommand()
      End If
      
    End Sub
    

    Where State & Port are properties of a sub classed Serial Socket.

    I don't understand the state engine part you mentioned in your reply. is "sendCommand()" a method under serial class or something else? To do this, I have to subclass a serial socket and define a custom method of send command and define the properties of port and state, right? It's the first time I try to sub classed a xojo default class.

  10. Jon O

    23 Oct 2017 Testers, Xojo Pro Answer Chicago Area USA
    Edited 3 years ago

    @BO C Thank you for your response. I guess I do not quite understand the state engine part. It seems you have done this successfully and smoothly. Could you give me some sudo-code example?

    Sure. Here's some actual code that I use to parse data received from a TCPSocket. It is similar to RS-232. When the DataAvailable event of the socket fires, I call a timer and this code below is executed in the timer action event. Greg O'Lone suggested using the timer.

    I have this code in the DataAvailable Event of the socket:

    MySockData = MySockData+t.ReadAll(Encodings.ASCII)
    DataAvailableTimer.Mode = Timer.ModeSingle

    MySockData is a proper of my object.

    Now here is some of the code from the timer. Basically I dump a bunch of commands to a device to get its status, information, etc. Here is how I analyze it. It will be meaningless to you in large part, but I'm showing you how you can basically take the data you have received and parse it or look for specific information. Then once you find the information, you act on it. That's the purpose of a state machine.

    Dim rx As New RegEx
    Dim rxOptions As RegExOptions = rx.Options
    rxOptions.LineEndType = 4
    Dim match As RegExMatch
    Dim matchPosition as Integer
    
    
    // Code to look for firmware version
    
    rx.SearchPattern = "(?msi-U)^([A-Z]\x20?(?:\d+\.){1,2}\d*\x20?[A-Za-z]*.*)\R+</pre>"
    
    match = rx.Search(MySockData)
    
    If match <> Nil Then
      FirmwareVersion = match.SubExpressionString(1)
      Dim fw() As String = FirmwareVersion.Split(EndOfLine)
      FirmwareVersion = fw(0)
    End If
    
    //
    // Code to look for RS232 data in the parameter dump
    
    rx.SearchPattern = "(?msi-U)(soip_type2=[yn])[\r\n]+(soip_guest_on=[yn])"
    
    match = rx.Search(MySockData)
    
    If match <> Nil Then
      Try
        mGuestMode = If (match.SubExpressionString(2).Right(1) = "y", True, False)
        mType2 = If (match.SubExpressionString(1).Right(1) = "y", True, False)
        mGotGuestMode = True
        'system.debuglog"Got RS232 Mode in Param Dump for Device "+Me.DeviceName
        RaiseEvent RS232ModeUpdate
        NotificationCenter.send("RS232ModeUpdated",RS232Mode,Self)
      Catch
        'RS232Mode = ""
      End Try
    End If
    
    // Code to look for baud rate in the parameter dump
    
    rx.SearchPattern = "(?msi-U)s0_baudrate=([0-9]+)-([5678][ENO][12])"
    
    match = rx.Search(MySockData)
    
    If match <> Nil Then  // We have RS232Settings
      
      Try
        RS232Values(0) = match.SubExpressionString(1)
        RS232Values(1) = match.SubExpressionString(2).Uppercase
        RaiseEvent RS232SettingsUpdate
        NotificationCenter.Send("RS232SettingsUpdated",RS232Values,Self)
      Catch
        RS232Values(0) = ""
        RS232Values(1) = ""
      End Try
    End If
    
    // Code to look for the web name
    
    rx.SearchPattern = "(?msi-U)webname=(.*)"
    
    match = rx.Search(MySockData)
    
    If match <> Nil Then
      Webname = match.SubExpressionString(1)
    End If
    
    
    // Code to look for the subnet mask
    
    rx.SearchPattern ="(?msi-U)netmask=((?(DEFINE)(?'octet'25[0-5]|2[0-4]\d|[01]?\d{1,2}))\b(?'octet1'(?&octet))\.(?'octet2'(?&octet))\.(?'octet" + _
    "3'(?&octet))\.(?'octet4'(?&octet)))\b"
    
    match = rx.Search(MySockData)
    
    If match <> Nil Then
      SubnetMask = match.SubExpressionString(1)
      'system.debuglog "Got SubnetMask for "+me.DeviceName
    End If
  11. Jon O

    23 Oct 2017 Testers, Xojo Pro Chicago Area USA

    @BO C I don't understand the state engine part you mentioned in your reply. is "sendCommand()" a method under serial class or something else? To do this, I have to subclass a serial socket and define a custom method of send command and define the properties of port and state, right? It's the first time I try to sub classed a xojo default class.

    SendCommand is the method that Wayne created as part of the customer subclass. It analyzes the received data and takes appropriate action. That's a state machine. Look at the data that has come in. If it matches the desired criteria then act on it. If not, ignore it and wait for the next chunk of data, etc.

  12. Tim H

    23 Oct 2017 Portland, OR USA

    Technically, that's not a state machine. You're acting on the data you received alone. A state machine acts on the data you most recently sent. It says, "I'm expecting such and such in response, what did I actually get?" It evaluates the response based on some expected value, based on the "state" of the conversation.

    For example, if I'm talking to a modem, the first thing I send is "AT", and I expect in response "OK". If I get anything else, there is a problem. However, "OK" could be a valid transmission, so I don't know based just on the data received what's going on. So a State Machine says, Set the current state to "Initiating Conversation" and then send an AT command. When the data comes back, I have to first check the state of the conversation before I can evaluate the data received.

    select case State
    
    case "Initiate Conversation"
        if data = "OK" then
            State = "Conversation in Progress"
            me.Write "Hello There"
        else
           State = "Error"
        end
    
    case "Conversation in Progress"
        if data = "OK" then
            me.Write "I'm glad you agree!"
        end
    
    end case
  13. Jon O

    23 Oct 2017 Testers, Xojo Pro Chicago Area USA

    @Tim H Technically, that's not a state machine. You're acting on the data you received alone. A state machine acts on the data you most recently sent. It says, "I'm expecting such and such in response, what did I actually get?" It evaluates the response based on some expected value, based on the "state" of the conversation.

    For example, if I'm talking to a modem, the first thing I send is "AT", and I expect in response "OK". If I get anything else, there is a problem. However, "OK" could be a valid transmission, so I don't know based just on the data received what's going on. So a State Machine says, Set the current state to "Initiating Conversation" and then send an AT command. When the data comes back, I have to first check the state of the conversation before I can evaluate the data received.
    select case State case "Initiate Conversation" if data = "OK" then State = "Conversation in Progress" me.Write "Hello There" else State = "Error" end case "Conversation in Progress" if data = "OK" then me.Write "I'm glad you agree!" end end case

    True. However, in my case I had a state machine but it's actually faster to evaluate whatever data comes into the socket. For example if you send AT, the first data received event may only get O and not OK. So then you have to wait another cycle for the full response. Then send your next command etc.

    Instead, what I do is send all my commands at one time and let all the TCP buffers figure it out. I can then process multiple chunks of data in one shot and not wait until a specific chunk is complete before issuing the next command and sending it out.

    Sometimes, you may need a state machine if you really do need to process and do one thing in one case and another thing in an opposite case. But I bet many times, that's not needed. So instead just send what you need to send and let the data come back and process it as it comes in. Once I allowed myself to do that, my world got much easier...

  14. Thank you all for the help. @Jon O @Tim H and @Wayne G

    I finally like Jon did, put a timer inside of DataAvailable event and put the state engine code into the timer. Now it works quite smoothly as I expected. Tim has a very good point to my case, I will modify my code after I figure out the state transition sequence.

    Again, thank you very much!

or Sign Up to reply!