Shop OBEX P1 Docs P2 Docs Learn Events
Parsing ASCII data into an array — Parallax Forums

Parsing ASCII data into an array

I am using the P2 to create a multi interface remote control terminal. It communicates with an ammeter using RS-232 that uses ASCII SCPI commands. I'm using a 1V 1kohm test just so I have some accurate data to work with. The flow is quite simple; The code sends in CONF:CURR:DC and READ? and the ammeter will send back +9.99538E-04. Except these are hex ASCII chars and I can't seem to parse out and format the number so I can use it for math and follow on communication. Additionally, I am using a Nextion NX8048P070_011 as the HMI for this. I need to be able to update either a text box or Xfloat with it for the user. This is done with an exact string statement like Current.val=####. The terminator for the RS-232 is a CR and for the nextion is an $FF. I am using a modified for my application version of jm_serial_bridge, which uses jm_fullserialduplex and jm_nstr. Using

I can send the entirety of the data to the PST and see the +9.99538E-04, I put a mask on the receive to get rid of the + and . , but I am at a complete loss for iterating the numbers into the desired array such as 9995.

I am just kind of at a deadend and I don't see the way forward. I've been in the P2 language documentation and on the OBEX looking for something that could point to a method or object that will handle this data. The code doesn't have a lot of attempts that I've tried already such as iterating a temp variable d[i] in an attempt to store each byte because those attempts got me further from what I am trying to do. Any help or direction is greatly appreciated.

Comments

  • If I understand correctly, you want to convert an ASCII string containing a floating point value into a floating-point register value?
    If so, give this a try:

    PUB Main() : result 
      result := str2float(string("+1.234567E-08"))
      debug(fdec(result))
    
    PUB str2float(p_str) : result | m_sign, mantissa, e_sign, exponent, i           '' convert a floating-point ASCII string into a floating point value
      mantissa := exponent := i := 0
      m_sign := byte[p_str++]                                                       ' capture sign
      repeat until byte[p_str] == "E"                                               ' parse mantissa up to "E"
        case byte[p_str]                                                            ' convert mantissa from ASCII to value
          "0".."9":                                                                 '  ignore "."
            mantissa := mantissa * 10 + byte[p_str] - "0"
            i++                                                                     ' count number of digits
        p_str++
      result := float(mantissa)                                                     ' convert mantissa to float
    
      repeat i-1                                                                    ' strip exponent from float by dividing it out
        result := result /. 10.0                                                    '  (1.23E2 becomes 1.23E0)
    
      p_str++                                                                       ' advance pointer past "E"
      e_sign := byte[p_str++]                                                       ' capture exponent sign
      repeat until byte[p_str] == $00                                               ' parse exponent up to null
        case byte[p_str]                                                            ' convert exponent from ASCII to value
          "0".."9":                                                                 '  ignore line terminators ($0D, $FF)
            exponent := exponent * 10 + byte[p_str] - "0"                          
        p_str++
    
      if e_sign == "-"                                                              ' if exponent is negative
        repeat exponent                                                             ' divide result by 10 by exponent
          result := result /. 10.0
      else                                                                          ' otherwise multiply
        repeat exponent
          result := result *. 10.0
    
      if m_sign == "-"                                                              ' negate result if sign negative
        result := -. result
    
  • Chris,

    Thank you very much for your reply. I think if I am able to get the data into an actual string first it would work, but the way it parses it in is in characters. I can use fstr1 and string to emit the characters as a string_ i.e. term.fstr1(string("%0c"),c)_ , but that method can't seem to be set to a variable. it will only just emit a string. I have also thought about having an empty array and adding the characters that come in from the mask but a += or iterating the index [i] doesn't seem to produce any readable results. If you look at where the method pulls in the receive buffer it just immediately transmits it to the pst. I think I may need a new method of transmitting in the characters so they can be formatted correctly for both math applications and for sending to the nextion. the nextion requires it in this syntax _term.fstr2(string("Current.val=%0d"), result, $FF) _

  • JonnyMacJonnyMac Posts: 8,988
    edited 2024-01-31 19:22

    You should extract the value string starting with the sign and ending after the mantissa magnitude. Convert it to an integer by multiply as appropriate for the number of digits you need after the decimal point. Send that to the Nextion Xfloat object and set the vvs1 parameter for the number of digits you want after the decimal point.

  • JonnyMacJonnyMac Posts: 8,988
    edited 2024-01-31 19:38

    I went back to your original post and updated my suggestion with your value. Again, I'm assuming you're using an Xfloat with 3 decimal places.

  • I think there is just something inherently different with these bytes coming in. They are ASCII chars I know that. If I print them in hex they are the ASCII hex number for the character being shown. The str2float function works great when sending in a test value, but to have it actually interpret this RS-232 data, it just destroys it. The following is having it just tx it to the pst as is, then using various ways like a mask or the str2float function but it always produces gobbledy gook.

    UNADULTERATED TRANSMIT: ASCII STRING

    PRINT AS HEX TO PROVE IT IS ASCII CHARS

    USING A MASK TO ONLY ALLOW 0-9, WILL ONLY PRINT ONE CHAR AT A TIME

    ATTEMPTING STR2FLOAT

    USING ROUND AND STR2FLOAT

  • JonnyMacJonnyMac Posts: 8,988
    edited 2024-02-01 20:26

    PRINT AS HEX TO PROVE IT IS ASCII CHARS

    That doesn't prove anything. You could have used BIN to print the incoming character -- that doesn't change the value of the character received.

    Note how the user input is buffered with get_user() and only sent to the device when the user presses Enter? You should probably do the same thing with the response. Here's an idea that puts the response in the buffer of your choosing and returns the length of the response.

    pub get_value(p_buf) : len | c
    
    '' Get value input from device
    '' -- input terminated by timeout or illegal character
    
      byte[p_buf][0] := 0
    
      repeat 
        c := amm.rxtime(TOMS)
        case c
          "+", "-"      : byte[p_buf][len++] := c
          "0".."9", "." : byte[p_buf][len++] := c
          "E", "e"      : byte[p_buf][len++] := c
          other         : quit
    
      byte[p_buf][len] := 0
    

    You'll have to create a buffer (array of bytes) to hold the response and then pass a pointer (@) to it. You can check the buffer to see if it's probably a number with

    pub is_number(p_buf) : result | c
    
      if (lookdown(byte[p_buf][0] : "+-0123456789"))
        return true
    

    This only checks the first character, but if you used get_value() the buffer shouldn't have any illegal characters. Now you can use the code above to send the reading to the Nextion. BTW, the Nextion uses three $FF characters to terminate a command string -- not one as you're doing.

    I am using a modified for my application version of jm_serial_bridge

    Yeah... about that. If you modify one of my programs and repost it, take my name off of it first (including the jm_ prefix on the name). I don't want credit for your hard work, nor criticism for your mistakes. Thanks for understanding. I get paid to write Propeller code, and I actually had a client question me about something someone posted in these forums.

  • AribaAriba Posts: 2,685

    Here is Chris parsing code with direct reading from serial input:
    ( I have not tested it, not even tried to compile it)

    PUB parseFloat() : result | m_sign, mantissa, e_sign, exponent, i,c             '' convert serial ASCII Input into a floating point value
      mantissa := exponent := i := 0
      m_sign := amm.rx()                                                            ' capture sign
      repeat
        c := amm.rx()
        case c                                                                      ' convert mantissa from ASCII to value
          "0".."9":                                                                 '  ignore "."
            mantissa := mantissa * 10 + c - "0"
            i++
      until c == "E"
      result := float(mantissa)                                                     ' convert mantissa to float
    
      repeat i-1                                                                    ' strip exponent from float by dividing it out
        result := result /. 10.0                                                    '  (1.23E2 becomes 1.23E0)
    
      e_sign := amm.rx()                                                            ' capture exponent sign
      repeat
        c := amm.rx()
        case c                                                                      ' convert exponent from ASCII to value
          "0".."9":
            exponent := exponent * 10 + c - "0"                          
          other:                                                                    ' parse exponent until no num
            quit
    
      if e_sign == "-"                                                              ' if exponent is negative
        repeat exponent                                                             ' divide result by 10 by exponent
          result := result /. 10.0
      else                                                                          ' otherwise multiply
        repeat exponent
          result := result *. 10.0
    
      if m_sign == "-"                                                              ' negate result if sign negative
        result := -. result                                                         ' result is a float
    

    Andy

  • This is giving me a lot of new avenues to try. I haven't gotten anything functional yet but I at least am not at a dead end. I would make me feel better about myself if I was able to figure it out with your kind direction, so thank you very, very much. I am truly sorry about the modified code still having the jm_ prefix. I removed the header but any nomenclature like that won't happen again. I just try to start with known good code that I can read through and watch the output, and then try to adjust it to do something different to solidify that I know what I'm doing with the syntax. I think a major thing I'm struggling with with the SPIN2 code is figuring out the syntax of arrays and indexing and even returning variables from methods. I've read the thread about how SPIN was able to use a return "result", but that doesn't work in SPIN2. The variable has to be declared for the return, but I'm not sure what that means. I think it has something to do with the : and | that come after the method name, but they seemed synonymous to me when I've used them.

  • JonnyMacJonnyMac Posts: 8,988
    edited 2024-02-01 20:40

    Thanks for understanding about the naming thing -- again, I don't want to get credit for your good work.

    Arrays in Spin -- as with any other language -- are a contiguous collection of same-sized elements. Spin is particularly flexible because it views the entire RAM as an array; simply point to an address and read one (byte), two (word), or four (long) bytes. In the P1 words and longs have address alignment requirements, but not in the P2.

    A string is simply an array of bytes that is terminated with zero. There is no string type in Spin (1 or 2). To deal with a string you are dealing with an array of bytes. For built-in functions like strsize() the terminating 0 is what counts.

    In the serial bridge code get_user() method you'll see this code:

      bytefill(@cmdbuf, 0, BUF_SIZE)                                ' clear the command buffer
    

    This is filling the byte array called cmdbuf with 0s -- this lets us write any number of characters (up to BUF_SIZE-1) and it will work with string functions. The @ symbol provides the hub address of the array. In my code if you see a variable prefixed with p_, this is a pointer (address of something) that was provided with @.

    Here's an improved (?) version of get_value()

    pub get_value(p_buf, buflen) : len | c
    
    '' Get value input from device
    '' -- input terminated by timeout or illegal character
    
      if (term.available() == 0)                                    ' anything in serial rx buffer?
        return                                                      '  if no, abort (len == 0)
    
      bytefill(p_buf, 0, buflen)                                    ' clear buffer
    
      repeat 
        c := term.rxtime(TOMS)                                      ' wait for character
        if (lookdown(c : "+0.1234567Ee-89"))                        ' valie E notation char?
          byte[p_buf][len++] := c                                   '  yes, add to buffer
          if (len == buflen-1)                                      '  are we at end of buffer
            return                                                  '   yes, return
        else
          return                                                    ' return on timeout/bad char
    

    In this version you pass a pointer to the input buffer and its length. If there are bytes waiting in the recieve UART the code will clear the recieve buffer (which is why we need to know its length) and then receive characters that are legal for an E notation value. If we get a timeout or invalid character, we will return. There is a safety step that checks for the length of the input to prevent overrunning the input buffer and corrupting other variables (I have a lot of hardcore IT friends and "buffer overflow" is a first-level attack when hacking systems).

    I would suggest you keep your code very modular. Receive your command in a buffer and then send it (but not inside the receive code). Take in your response and deal with it outside the response code receive method. I find keep methods small and atomic let me isolate problems more quickly.

    Spin 1 always returns a value from a function, even if no return is specified. Internally, it is called result. In Spin2, there is no automatic return, and we can return multiple values. Examples

    pub method0(params)
      ' code
    

    This Spin2 method returns NO values.

    pub method1(params) : result1
      result1 := something
    

    This method returns a single long. Internally, it is called result1. As with Spin1, the result variable(s) are initialized to 0 when the method is called.

    pub method2(params) : return1, return2
      result1, result2 := something, something_else
    

    This method returns two longs, internally referenced as result1 and result2.
    You can also do this:

    pub method2(params) : return1, return2
      return something, something_else
    

    There are differences between Spin1 and Spin2 that we do have to think about. I still do a lot of P1 development so I have made it my habit to declare and name return values for Spin1 so that the code more easily translates to Spin2 later.

Sign In or Register to comment.