Shop OBEX P1 Docs P2 Docs Learn Events
Get data between delimiters starting from a certain point. — Parallax Forums

Get data between delimiters starting from a certain point.

eagletalontimeagletalontim Posts: 1,399
edited 2015-02-10 19:32 in Propeller 1
What I am trying to accomplish is a simple function that I can call to extract a decimal value from a string right after the "search value".

I have a byte array (BYTE buffer[255]) that can look like this : R:C:03:V:12: D:7231:# (Ignore space between ":" and "D") What I would like to do is call this new function by using Value := extract_data(@buffer, "C") Variable "Value" is a LONG. When parsed properly, the number 3 will be the new value. Same would go if using : Value := extract_data(@buffer, "D") which would return 7231 as the new value.

There is a string method I found on the forums and have tried to make it work, but no go :( Any help is greatly appreciated!

Comments

  • average joeaverage joe Posts: 795
    edited 2015-02-08 16:00
    Using numbers.spin, could be something like this?
    PUB extract_data(ptr, key, delim) | t, i, n[?]
      result := false
      t := 0
      i := 0
      repeat
        if byte[ptr][t++] == key
          repeat
            i +=1
          until byte[ptr][t+ i] == delim
          bytemove(@n, ptr + t, i)
          byte[@n] [i + 1] := 0
          return num.toStr(n, num#dec) 
    

    *edit, forgot delimiters*

    *aedit*

    This should be something like you want?
  • eagletalontimeagletalontim Posts: 1,399
    edited 2015-02-08 16:25
    I must be doing something wrong...
    CON
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
    
      ' Serial Terminal Stuff
      DEBUG_BAUD = 9_600
      DEBUG_HEADING_X = 1          ' Output formatting data
      DEBUG_HEADING_Y = 1
      DEBUG_DYNAMIC_X = DEBUG_HEADING_X + 10
      DEBUG_DYNAMIC_Y = DEBUG_HEADING_Y + 2
    
    VAR
      BYTE buffer[255]
      LONG value1
      
    OBJ
      Pst           : "Parallax Serial Terminal"
      num           : "Numbers"
    
    PUB go
      NUM.init
      pause(1000)
    
      Pst.Start(DEBUG_BAUD)                                                         '1 Cog
      
      Pst.Clear
      Pst.Position(DEBUG_HEADING_X, DEBUG_HEADING_Y)
      Pst.Str(string("Current Position", 13))
      Pst.PositionX(DEBUG_HEADING_X)
      Pst.Str(string("------------------------", 13))
      Pst.PositionX(DEBUG_HEADING_X)
      Pst.Str(string("Input  ................", 13))
      Pst.PositionX(DEBUG_HEADING_X)
      Pst.Str(string("Value  ................", 13))
    
      repeat
        Update_Debug_Window
        pst.StrIn(@buffer)
        value1 := extract_data(buffer, STRING("C"), STRING(":"))
        'value1 := 12
        
    PUB Update_Debug_Window
        Pst.Position(DEBUG_DYNAMIC_X, DEBUG_DYNAMIC_Y)
        Pst.Str(@buffer)
        Pst.Str(string("..."))
        Pst.Position(DEBUG_DYNAMIC_X, DEBUG_DYNAMIC_Y + 1)
        Pst.Dec(value1)
        Pst.Str(string("..."))
        return
    
    PUB extract_data(ptr, key, delim) | t, i, n[255]
      result := false
      t := 0
      i := 0
      repeat
        if byte[ptr][t++] == key
          repeat
            i +=1
          until byte[ptr][t+ i] == delim
          bytemove(@n, ptr + t, i)
          byte[@n] [i + 1] := 0
          return num.toStr(n, num#dec)
    
    PUB pause(Duration)
      if(Duration > 0)  
        waitcnt((clkfreq / 1_000 * Duration) + cnt)
      return
    

    When I type in the PST input : R:C:43:G I am trying to get "43" to show up as Value.

    Note * : n[?] showed up as an error.
  • average joeaverage joe Posts: 795
    edited 2015-02-08 16:34
    Sorry, I was on my phone before..

    You need to make the ? the max size of your string (the longest value you expect to return) + 1 (for zero delimiter) / 4 (bytes to long)

    Now, I should have used num.FromStr

    Other than that, I'd have to actually prototype it. I was just hoping to give a jumping off point. I think there are several errors at this point. I'll try to revisit it when I have a few extra minutes..


    *edit*
    I think this is closer,..
    PUB extract_data(ptr, key) | t, i, n[4]
      result := false           ' might change this   
      repeat t from 0 to 255
        if byte[ptr][t] == key
          t += 1
          repeat i from 0 to 16   ' max string size 
            if byte[ptr][t + i] == ":"
          bytemove(@n, ptr + t, i)
          byte[@n] [i + 1] := 0
          return num.toStr(n, num#dec)    
    

    The first repeat loop (t) looks for the key. Increment t to point at first byte of value, then the second repeat loop looks for the delimiter. Then copy to a temporary variable, null terminate the string and pass it to numbers. (Max number string size would be 16 bytes in this example.
  • idbruceidbruce Posts: 6,197
    edited 2015-02-08 16:43
    There is a text parser located within the source code of the Teacup port at this thread, http://forums.parallax.com/showthread.php/159950-The-Teacup-Port-A-Work-In-Progress-3D-Printer-Firmware.

    Additionally, JonnyMac provided some real good insight to parsing at this post http://forums.parallax.com/showthread.php/159947-HELP.spin-I-Am-Going-In-Circles-And-Getting-Dizzy-Byte-Array?p=1313935&viewfull=1#post1313935 and then expands his his discussion in Post #9 of that thread, and then further expansion of the same concept, is made in the thread of the first link provided.

    You should now be well armed, with several methods :)

    Bruce
  • eagletalontimeagletalontim Posts: 1,399
    edited 2015-02-08 16:43
    I set "n" to 255 as the string could vary in size and 255 is more than enough room for data that may be sent.

    I added an ELSE to the extract_data IF statement and apparently I am calling this function incorrectly because it always hits the ELSE....

    Attempts at calling have been :
    value1 := extract_data(@buffer, "C", ":")
    value1 := extract_data(buffer, "C", ":")
    value1 := extract_data(@buffer, string("C"), string(":"))
    value1 := extract_data(buffer, string("C"), string(":"))

    None have worked....
  • average joeaverage joe Posts: 795
    edited 2015-02-08 16:50
    Oh, here's a DUH!
    PUB extract_data(ptr, key) | t, i, n[4]
      result := false           ' might change this   
      repeat t from 0 to 255
        if byte[ptr][t] == key
          t += 1
          repeat i from 0 to 16   ' max string size 
            if byte[ptr][t + i] == ":"
          bytemove(@n, ptr + t, i)
          byte[@n] [i + 1] := 0
          return num.FromStr(@n, num#dec)    
    

    num.fromStr(@n, num#dec)
  • average joeaverage joe Posts: 795
    edited 2015-02-08 16:52
    Not sure why it's not hitting the if statement... Calling value1 := extract_data(@buffer, "C", ":") SHOULD hit the if??

    And it should increment t by 2...
    PUB extract_data(ptr, key) | t, i, n[4]
      result := false           ' might change this   
      repeat t from 0 to 255
        if byte[ptr][t] == key
          t+= 2                     ' point at start of number
          repeat i from 0 to 16   ' max string size 
            if byte[ptr][t + i] == delim
              bytemove(@n, ptr + t, i)
              byte[@n] [i + 1] := 0
              return num.FromStr(@n, num#dec)    
    
    


    *edit*
    Fixed a couple mistakes
  • eagletalontimeagletalontim Posts: 1,399
    edited 2015-02-08 16:56
    Your last method works like a champ! Thank you very very much! One of these days I will better understand the BYTE / LONG stuff.... I can do IF statements, toggle pins, and do Math with no problem :) Throw in a curve ball and I am out!
  • average joeaverage joe Posts: 795
    edited 2015-02-08 17:01
    The byte / long stuff is tricky sometimes. But it's key to making Spin work for me. You could also write

    byte[@n] [i + 1] := 0

    as

    n.byte [i + 1] := 0

    Trying to get used to using the second method since it seems cleaner to me. (usually)

    Glad it finally worked. It could be optimized a bit depending on what you are doing. Returning false might be bad if you are expecting negative numbers... In that case, an abort would be best..
  • JonnyMacJonnyMac Posts: 9,105
    edited 2015-02-08 17:24
    As I'm struggling with my own project and my name was bandied about, I was able to solve this in just a couple minutes with methods already in my strings library (which borrows heavily from a lot of good sources). Here's the method that extracts the value from your string. Note that you pass a string to look for, and the method assumes that the value characters follow after (spaces are allowed, but nothing else).
    pub get_value(p_input, p_header) | pos
    
      pos := str.instr(p_input, p_header)                            ' look for header in input
    
      if (pos => 0)
        return str.asc2dec(p_input+pos+strsize(p_header), 10)
      else
        return -1
    


    I realize that you have a single character and colon, but what if your input changes to something like "TEMP=123 FLOW=456"? My method will still work. The lesson here is to write code that solves the immediate problem, yet is open to others within the realm of possibility.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2015-02-08 17:52
    Thank you JonnyMac for your input! You have always had some good resources and I use quite a few on a regular basis. Just added some more to my JM folder :)
  • tonyp12tonyp12 Posts: 1,951
    edited 2015-02-08 18:38
    I needed a IPV4 to decimal big endian
        wifi.connect(0,3000,string("44.213.1.12"))   'showing a IP in decimal text with dots is the standard and raw 32bit hex is not common 
        ...
    PUB connect(sockethandle,port,IPv4)  
        hci_byte(parsedot(IPv4,0))                   'Destination IPv4 (Big-endian)
        hci_byte(parsedot(IPv4,1))
        hci_byte(parsedot(IPv4,2))
        hci_byte(parsedot(IPv4,3))
        ...
    PUB parsedot(mystr, dotn)
        i:=0
        repeat dotn                                  'repeat 0 is like a nop
          repeat until byte[mystr][i++]== 46
        repeat until byte[mystr][i]== 46 or byte[mystr][i]== 0 
          result *=10                                'use system-var result, it's always initialized to zero
          result += byte[mystr][i++]-48              'get asc to dec
    
  • eagletalontimeagletalontim Posts: 1,399
    edited 2015-02-09 21:46
    Well, I think I still have something wrong.... I am getting a random glitch where the variables are not being pulled from the string. Here is the output of a Telnet connection to my server showing what is happening.
    R:C:11:#
    Unknown Command : 0
    R:C:11:#
    SR:70#WSR:10#PWS:13#MAW:3#MVM:100#PV:70#WT:10000#MD:50#WD:70000
    PING
    R:C:11:#
    SR:70#WSR:10#PWS:13#MAW:3#MVM:100#PV:70#WT:10000#MD:50#WD:70000
    

    Anything with "R:C:" was send from the browser. The next line is the data from the Prop.

    When I call command 10, the string looks like this : <R:C:10:N:6:V:74:#> I always get "Failed Saving!"

    Here are bits and pieces of my receiving / processing code on the Prop :
    VAR
      LONG which_one
      LONG value
    
    PUB Handle_Wifi | char, i
      xb.Start(XB_OUT, XB_IN, %0000, 9_600)
      pause(5000)
      xb.str(string(BEGIN_CHR))
      xb.str(string("Solar Connected!"))
      xb.str(string(END_CHR))
      index := 0
      i := 0
      repeat
        i++
        index := 0
        if i => 10
          xb.str(string(BEGIN_CHR))
          xb.str(string("PING"))
          xb.str(string(END_CHR))
          i := 0
        char := xb.rxtime(2000)
        if char == "R"
          repeat until char == "#"
            i++
            if i == 5
              quit
            if char > 31 and char < 126
              i := 0
              buffer[index++] := char
            char := xb.rxtime(1000)
          xb.rxflush
          buffer[index]~                       '  ******************  Maybe I am not doing the z-string correctly? ****************
          i := 0
          if(char == "#") AND strsize(@buffer) > 4
            Process_Data
            bytefill(@buffer, 0, 256)
          else
            bytefill(@buffer, 0, 256)
        elseif char > -1
          xb.rxflush
                
        char := -1
    
    PUB Process_Data
      command := extract_data(@buffer, string("C:"))
      pause(100)
      if(command => 0)     
        case command
          1 :
          '.............
          10 :                                              ' PROGRAM VALUE TO VARIABLE
            which_one := extract_data(@buffer, string("N:"))
            value := extract_data(@buffer, string("V:"))
            if which_one > -1 AND value > -1 AND str.is_number(which_one) == true AND str.is_number(value) == true
              copy_to(which_one, value)
              save_flag := 1                                '  Trigger Save in Main loop
              xb.str(string(BEGIN_CHR))
              xb.str(string("Saved New Value!"))
              xb.str(string(END_CHR))
            else
              xb.str(string(BEGIN_CHR))
              xb.str(string("Failed Saving!"))
              xb.str(string(END_CHR))
          OTHER :
            move_EW_flag := 0
            move_UD_flag := 0
            move_to_park := 0
            xb.str(string(BEGIN_CHR))
            xb.str(string("Unknown Command : "))
            xb.dec(command)
            xb.str(string(END_CHR))
    
    PUB extract_data(p_input, p_header) | pos
      pos := str.instr(p_input, p_header)                            ' look for header in input
    
      if (pos => 0)
        return str.asc2dec(p_input+pos+strsize(p_header), 10)
      else
        return -1
    
  • eagletalontimeagletalontim Posts: 1,399
    edited 2015-02-10 19:32
    I believe I figured it out... I was using the variable "i" in 2 different spots and it was throwing it off... New code :
    PUB Handle_Wifi | char, i, x
      xb.Start(XB_OUT, XB_IN, %0000, 9_600)
      pause(5000)
      xb.str(string(BEGIN_CHR))
      xb.str(string("Solar Connected!"))
      xb.str(string(END_CHR))
      index := 0
      i := 0
      repeat
        i++
        x := 0               
        index := 0
        if i => 20
          xb.str(string(BEGIN_CHR))
          xb.str(string("PING"))
          xb.str(string(END_CHR))
          i := 0
        char := xb.rxtime(2000)
        if char == "R"
          repeat until char == "#"
            x++
            if x => 5
              quit
            if char > 31 and char < 126
              x := 0
              buffer[index++] := char
            char := xb.rxtime(100)
          xb.rxflush
          buffer[index++]~
          i := 0
          if(str.instr(@buffer, string("R") => 0 AND str.instr(@buffer, string("#")) => 0))
            Process_Data
            bytefill(@buffer, 0, 255)
          else
            bytefill(@buffer, 0, 255)
        'elseif char > -1
        '  xb.rxflush
        '  bytefill(@buffer, 0, 255)
    
Sign In or Register to comment.