Shop OBEX P1 Docs P2 Docs Learn Events
Parser output problem — Parallax Forums

Parser output problem

DiverBobDiverBob Posts: 1,110
edited 2015-01-07 17:30 in Propeller 1
I'm running into a problem that's got me stumped. I'm parsing an input string formatted as '$,1,2,345'. The $ is the start character, the '1' is a position number (value 1 thru 6), the '2' a command code (currently value is 1, 2, or 3), and the '345' is a parameter (normally a value in degrees of rotation).
The parser is correctly breaking up the values and I get the correct output value to the serial output but I'm getting an incorrect value when I assign the parameter value to a global variable (line where parameter := @Buffer[5]). At the bottom of the code snippet is the results I'm getting from the output. The first line is correctly displaying the output angle (112 degrees) but the FemurAngle value is 22723. I've tried several methods to pass the correct value (112) but ran out of ideas. Hopefully someone can see what I'm doing wrong. I've also attached the full listing of the program if you want to see how this fits into the rest of the motor control code.
pub EnterCommand | teststr, pattern, resaddr, rslt, n, legnumber, command, parameter  
' input data from master
' input string format: "$,1,2,345"
' routine derived from Perry James Mole GPS parser for GPS decoding - see thread on GPS Parsing Problems 9/9/2013
  io.tx(0,16)
  Null[0] := 0
  repeat
    longfill(buffer,10,0)
    repeat while Rx <>= "$"
      Rx := io.rx(1)
    cptr := 0
    repeat while Rx <>= CR
      Rx := io.rx(1)
      if RX == ","
        buffer[cptr++] := 0
      else
        buffer[cptr++] := Rx

    if buffer[1] == "6"
      if buffer[3] == "1"
        io.str(0,string(13,"Femur: "))
        parameter := @buffer[5]
        io.str(0,parameter)
        if FemurDone == 0
          FemurAngle := parameter
          io.str(0,string("  FemurAngle: "))
          io.dec(0,FemurAngle)
          FemurDone := 1
      if buffer[3] == "2"
'        io.str(0,string(13,"Tibia: "))
        parameter := @buffer[5]
'        io.str(0,parameter)
      if buffer[3] == "3"
'        io.str(0,string(13,"Coxa: "))
        parameter := @buffer[5]
'        io.str(0,parameter)


Output from above routine:
                                              
Femur: 112
FemurAngle: 22723
Hexapod IK Test 4a - Archive [Date 2014.12.11 Time 20.26].zip

Comments

  • r.daneelr.daneel Posts: 96
    edited 2014-12-11 19:28
    I haven't had a detailed look, but is io.str(0,parameter) right? Should it be io.dec(0, parameter)? I guess it is if it is displaying the correct value, but if 'parameter' is correctly displayed as a string, why would 'FemurAngle' be displayed as a decimal (io.dec(0,FemurAngle))?
  • msrobotsmsrobots Posts: 3,709
    edited 2014-12-11 21:28
    @Diverbob,

    I am following your progress for a while now. I really like your project. So if I sound harsh, please excuse me - English is my third language...

    What is <>= supposed to do ? Looks quite wrong to my eyes. This might check for <> but set RX to"$" ?

    Rx is a global Variable I assume. Are you sure about the initial value? Why you are using a global Variable at all?

    So I would replace
        longfill(buffer,10,0)
        repeat while Rx <>= "$"
          Rx := io.rx(1)
        cptr := 0
        repeat while Rx <>= CR
          Rx := io.rx(1)
          if RX == ","
            buffer[cptr++] := 0
          else
            buffer[cptr++] := Rx
    
    thru
        longfill(buffer,10,0)
        repeat while (Rx := io.rx(1)) <> "$"
        cptr := 0
        repeat while (Rx := io.rx(1)) <> CR
          if RX == ","
            buffer[cptr++] := 0
          else
            buffer[cptr++] := Rx
    

    to make it more readable. Both variants may do the same. Its just me being German. I stick on details.

    Anyways at that point you have a 0 in buffer[0] - the "," after "$" and your first argument in buffer[1] as long as it is a single character. So far so good. buffer[2] is 0. Same works for your second argument buffer[3] as long as it is a single character. buffer[4] is 0. Now you pass more then one byte as argument. But it is still a text (ASCI). It is a string.

    Correctly you are using @buffer[5] to resolve the address of the position in the buffer into your Variable parameter. And since your buffer is zero-filled after them bytes is a 0 so the string is terminated. So far so good.

    so your first output works perfectly fine:
            io.str(0,string(13,"Femur: "))
            parameter := @buffer[5]
            io.str(0,parameter)
    
    but the next is wrong. You are outputting the address of the string containing the value not the value itself.
              FemurAngle := parameter
              io.str(0,string("  FemurAngle: "))
              io.dec(0,FemurAngle)
    

    Solution:

    you need to convert the string value at that address to the binary value to use it as 'normal' variable. "123" is not 123.

    If you subtract 48 from each single ASCI byte you get decimal 0 from string "0" and decimal 9 from string "9". To parse the number you need to convert the first byte and if not done, multiply by 10 and add the next converted byte.

    A simpler way MAY be a object "numbers" in the standard Parallax Library from the PropTool. Just include it. It provides various methods. You will be interested in the 4st one from the top: PUB FromStr(StrAddr, Format)

    something like this
              FemurAngle := FromStr(parameter, format )
              io.str(0,string("  FemurAngle: "))
              io.dec(0,FemurAngle)
    

    should do it. You need to include numbers into your OBJ section and figure out what 'format' is or just build your own string to long converter as I did because I never grooked 'numbers' ...

    The function you need inside 'numbers' is this one:
    PUB FromStr(StrAddr, Format): Num | Idx, N, Val, Char, Base, GChar, IChar, Field
    ''Convert z-string (at StrAddr) to long Num using Format.
    ''PARAMETERS: StrAddr = Address of string buffer containing the numeric string to convert.
    ''            Format  = Indicates input format: base, width, etc. See "FORMAT SYNTAX" for more information.  Note: three Format elements are ignored by
    ''                      FromStr(): Zero/Space Padding, Hide/Show Plus Sign, and Digit Group Size.  All other elements are actively used during translation.
    ''RETURNS:    Long containing 32-bit signed result.
      Base := Format & $1F #> 2 <# 16                                                                       'Get base
      if GChar := Format >> 13 & 7                                                                          'Get grouping character
        GChar := Symbols[--GChar #> 0]
      if IChar := Format >> 19 & 7                                                                          'Get indicator character
        IChar := Symbols[--IChar #> 0]
      Field := Format >> 5 & $3F - 1                                                                        'Get field size, if any (subtract out sign char)
      longfill(@Idx, 0, 3)                                                                                  'Clear Idx, N and Val
      repeat while Char := byte[StrAddr][Idx]                                                               'While not null
        if (not IChar or (IChar and Val)) and InBaseRange(Char, Base) > 0                                   'Found first valid digit? (with prefix indicator if required)?
          quit                                                                                              '  exit to process digits
        else                                                                                                'else
          if not Val := IChar and (Char == IChar)                                                           '  look for indicator character (if required)
            N := Char == "-"                                                                                'Update N flag if not indicator
        Idx++
      Field += Val                                                                                          'Subract indicator character from remaining field size
      repeat while (Field--) and (Char := byte[StrAddr][Idx++]) and ((Val := InBaseRange(Char, Base)) > 0 or (GChar and (Char == GChar)))           
        if Val                                                                                              'While not null and valid digit or grouping char
          Num := Num * Base + --Val                                                                         'Accumulate if valid digit
      if N
        -Num                                                                                                'Negate if necessary
    
    

    If you mentally throw away all the un-needed you may see the algorithm.


    I hope this helps a bit

    Enjoy!

    Mike









    for readability and to be sure
  • DiverBobDiverBob Posts: 1,110
    edited 2014-12-13 05:36
    Thanks for the replies! I'll give them a try today and report back on it.
  • kwinnkwinn Posts: 8,697
    edited 2014-12-13 08:53
    The problem is that you are storing the address of parameter 3 rather than the number the ascii string represents. You need to convert the string to an integer and store that. I have added some comments to the code below.

    It looks like you want to control three servos for each of the six legs of a hexapod. If that is the case I would suggest storing the angle (or servo pulse width) data in an array of 18 bytes or words. Use the first two parameters to calculate the offset into the array. This way you only need one routine to handle the conversion for all the segments of all the limbs.
    pub EnterCommand | teststr, pattern, resaddr, rslt, n, legnumber, command, parameter  
    ' input data from master
    ' input string format: "$,1,2,345"
    ' routine derived from Perry James Mole GPS parser for GPS decoding - see thread on GPS Parsing Problems 9/9/2013
      io.tx(0,16)
      Null[0] := 0
      repeat
        longfill(buffer,10,0)                               ' fill the buffer with 10 zeros
        repeat while Rx <>= "$"                             ' repeat until "$" is input
          Rx := io.rx(1)
        cptr := 0
    '****************************************************************************************************************************
    '* stores the parameters in the buffer, starting at buffer[1], and replaces comma seperators with zeros (string terminators) 
    '****************************************************************************************************************************
        repeat while Rx <>= CR                              ' repeat and store characters until CR is input
          Rx := io.rx(1)
          if RX == ","                                      ' if RX is a comma replace it with 0  - string terminator 
            buffer[cptr++] := 0                             ' and incr cptr - := 0 NOT NEEDED - already 0 from longfill
          else
            buffer[cptr++] := Rx                            ' else store the character in the buffer
    
    '****************************************************************************************************************************
    '* decode command
    '****************************************************************************************************************************
        if buffer[1] == "6"                                 ' guessing this is the limb number
          if buffer[3] == "1"                               ' limb segment ?
            io.str(0,string(13,"Femur: "))
            parameter := @buffer[5]                         ' parameter = memory address of buffer[5] - Femur angle
            io.str(0,parameter)                             ' print the Femur angle text in the buffer
            if FemurDone == 0                               ' ??? where does FemurDone come from ???
              FemurAngle := parameter                       ' FemurAngle = memory address of buffer[5]
              io.str(0,string("  FemurAngle: "))
              io.dec(0,FemurAngle)                          ' now printing the memory address of buffer[5] as a decimal number
              FemurDone := 1
          if buffer[3] == "2"
    '        io.str(0,string(13,"Tibia: "))
            parameter := @buffer[5]
    '        io.str(0,parameter)
          if buffer[3] == "3"
    '        io.str(0,string(13,"Coxa: "))
            parameter := @buffer[5]
    '        io.str(0,parameter)
    
    
    {Output from above routine:
                                                  
    Femur: 112
    FemurAngle: 22723
    
    Hexapod IK Test 4a - Archive [Date 2014.12.11 Tim
    }
    
  • DiverBobDiverBob Posts: 1,110
    edited 2014-12-13 19:48
    I tried out some of the suggestions and got motor movement but very strange actions. The motor would go to the correct position and then move to another angles repeatedly. For example, if I requested 50 degrees, I would get 50, then 138, followed by 37, and other outputs. I wasn't getting this type of a result earlier so I don't believe I'm getting noise from the transmitter, it must be in the parsing of the input command. I played around with it a while without any real success, every once in a while it would go to the requested angle for a period of time and then randomly start acting up again.
    I'm going to sleep on this and then delete the parser and just re-code it from scratch using the ideas from above and see what happens. This should be a fairly simple parser and output of an angle but it's becoming a bigger problem than I anticipated. I may go back to the original, more complex parser as I didn't have this issue there.
  • JonnyMacJonnyMac Posts: 9,105
    edited 2014-12-13 20:57
    I written a few programs that parse input strings. My preference is to break things into small chunks -- have a look at the demo to see if it's helpful.
    pub get_cmd(hdr, p_str, len) | idx, k
    
    '' Get command from serial stream
    '' -- hdr is required header character
    '' -- p_str is pointer to string space for comand
    '' -- len is maximum length of input (buffer len - 2)
    
      repeat
        byte[p_str][0] := (k := term.rx)                            ' get char from stream
        if (k == hdr)                                               ' if header
          quit
    
      idx := 1                                                      ' set starting index
          
      repeat while (idx < len)
        byte[p_str][idx] := (k := term.rx)                          ' get char from stream
        if (k == 8)                                                 ' if backspace
          idx := --idx #> 1                                         '  back up; stop at header
        elseif ((k == 0) or (k == 13))                              ' if 0 or CR
          quit                                                      '  done
        else
          ++idx                                                     ' advance pointer
    
      byte[p_str][idx] := 0                                         ' terminate string   
        
    
    pub count_char(c, p_str) | n
    
    '' Returns count of char c in string at p_str
    
      n := 0                                                        ' clear count
    
      repeat strsize(p_str)                                         ' loop through string
        if (byte[p_str++] == c)                                     ' if match
          ++n                                                       '  increment count
    
      return n
    
    
    pub extract_field(n, p_str, sep) | len, val, sign, flag, k
    
    '' Returns decimal value of field n
    '' -- p_str is source string/buffer
    '' -- sep is seperator character
    
      ' look for start of field    
    
      len := strsize(p_str)                                         ' maximum length to search
    
      repeat while (n > 0)
        if (byte[p_str++] == sep)                                   ' if character is seperator
          --n                                                       '  decrement field count
        if (--len == 0)                                             ' if end of string
          return 0                                                  '  early exit
    
      ' extract value
    
      val := 0
      sign := 1
      flag := false
    
      repeat len
        k := byte[p_str++]                                          ' get character from string
        case k
          " " :                                                      
            if (flag)                                               ' if already in field                                              
              quit
    
          "-" :
            ifnot (flag)                                            ' if not in field
              sign := -1                                            '  set sign
            else                                                
              quit
              
          "0".."9" :                                                
            val := (val * 10) + (k - "0")                           ' convert ascii to decimal 
            flag := true                                            ' mark entry to field
    
          other :
            quit
        
      return val * sign
    
  • DiverBobDiverBob Posts: 1,110
    edited 2014-12-15 19:14
    Thanks Jon, I want to try this out soon. I'm on the road for a bit so can't test it out on my code just yet.
  • DiverBobDiverBob Posts: 1,110
    edited 2015-01-01 12:29
    Finally home again so I put in Jon's parsing code and it looks very good so far! I'm getting some inconsistent output from the master computer that I need to track down such as missing CR and start char in the middle of another output. I'll finish writing the interface code from this parser to the leg controller and see how that works out.

    Thanks again Jon for the help!

    Bob Sweeney
  • DiverBobDiverBob Posts: 1,110
    edited 2015-01-04 11:08
    The parser is working very well for getting the motor commands from the master controller to the leg controller. So I decided to use the same code in the master controller to decode the feedback signals from the leg controller computers. Right now the leg controller is only feeding back data that is being used to control 3 RGB LEDs for the leg. The RGBs change color based on the action being taken by the 3 motors. This makes for a simple visual debugging system that runs all the time and looks nice also. I'm using the same format ($,1,2,3) as the leg control since it allows me to easily add new functions in the future without having to recode every time I want to add a new sensor.

    The issue that is happening right now is the GetCmd routine appears to be outputting differently when I add debug code vice when the debug code is disabled. The code is located in the Q4 Rx V3.61 archive attached. I'm not understanding how this affects the routine and results in different outputs. The value "ÿ" corresponds to 255 according to my ASCII table, not sure what is causing it to appear during the parsing. I copied some of the serial terminal output values into the CODE location below for both situations.
    The command string sent is "$,6,1,7", "6,2,7" or "$,6,3,7". When the routine is modified with debug code it outputs the correct output but removing the debug statements results in what appears to be value '255' into the buffer
    
    pub getcmd(hdr, pstr, len) | idx, k                                             'routine designed by Jon McPhalen
    ' get command from serial stream
    ' hdr is required header character
    ' pstr is pointer to string space for command
    ' len is maximum length of input (buffer len-2)
      repeat
        byte[pstr][0] := (k := uarts.rxcheck(2))                                    ' get char from stream
        if (k == hdr)                                                               ' if header
          quit
      idx := 1                                                                      ' set starting index
      repeat while (idx < len)
        byte[pstr][idx] := (k := uarts.rxcheck(2))                                 ' get char from stream
    
        uarts.str(1,string("  Test: "))						'debug statements
        uarts.dec(1,byte[pstr][idx])
        uarts.tx(1,13)
    
        if (k == 8)                                                                 ' if backspace
          idx := --idx #> 1                                                         ' backup
        elseif ((k == 0) OR (k == 13))                                              ' if 0 or CR, quit
          quit
        else
          ++idx                                                                     ' advance pointer
      byte[pstr][idx] := 0                                                          ' terminate string
    
    
    
    -- Serial output with unmodified GETCMD routine
    
    Femur: 0
    $,6,2,ÿÿ7
    
    Tibia: 0
    $,6,3,ÿÿ7
    
    Coxa: 0
    $,6,1,ÿ7
    
    
    --Output with modified GETCMD routine
    
    Femur: 7
      Test: 44
      Test: 54
      Test: 44
      Test: 50
      Test: 44
      Test: 55
      Test: 13
    $,6,2,7
    
    Tibia: 7
      Test: 44
      Test: 54
      Test: 44
      Test: 51
      Test: 44
      Test: 55
      Test: 13
    $,6,3,7
    
    Coxa: 7
      Test: 44
      Test: 54
      Test: 44
      Test: 49
      Test: 44
      Test: 55
      Test: 13
    $,6,1,7
    

    Q4 Rx V3.61 - Archive [Date 2015.01.04 Time 13.50].zip
    Hexapod IK Test 5 - Archive [Date 2015.01.04 Time 13.51].zip
  • msrobotsmsrobots Posts: 3,709
    edited 2015-01-05 11:05
    RxCheck is not returning a Byte but a long.

    It will return a POSITIVE value from 0 to 255 (your usable byte) or -1 for "no character available".

    So if you want to wait for a character you need to use just uarts.rx. not uart.rxcheck. Or you need to check for the "no character there" condition (-1)

    You are now 'pressing' or 'casting' your rxCheck value into a Byte, thus loosing the distinction between -1 and 255..

    255 might be a usable value in your protocol, or might not. you can decide this. If you exclude 255 as a meaningful value in your protocol you can simply ignore this value.

    You can also use uarts.rx, witch will wait for the next transmitted byte or rxtime to wait for a specified amount of time.

    rxCheck and rxTime allow you to check for a timeout condition and restart your communication if - say - incomplete messages are received and your leg controller is waiting for some byte, lost thru noise or whatever.

    IMHO the proper way to handle this (with a robot of this size) is to restart you leg-controller in case of communication problems, informing the master controller about the current positions.

    Failure tolerance is a quite important part on 'heavy' equipment like you are working with and should be included into the main design.

    Usually serial communication runs perfect between some microcontrollers in close proximity. But **** happens. It does. And you need to take care of that problem in your software. Re-syncing in case of error.

    I do understand that you are still fighting with the basic communication between multiple propeller controllers. So step back for a moment and look at the overall design. Each of your leg controllers needs to be sure to have valid communication to the master. If not **** happens.

    By now (besides not checking for "no Byte there" them ÿ) you are checking the serial stream for your header "$" and then read in up to 'len' bytes or until you hit CR or 0. Not sure why you check for BackSpace here, this makes just sense for testing with a serial terminal, your master controller will not send BackSpace at all, right?

    But you do not check for '**** happened'. And that is where rxTime comes in or rxCheck when you need to do something other in between and are not able to wait as rx and rxtime do.

    English is my third language, so I somehow need to talk AROUND the main point, because I am not able to write the main point down as I could do in German.

    But I will try anyways because I want to see this thing walking. GO Bob GO!

    One way to handle '**** happened' is thru exceptions. Spin can 'throw' and 'catch' them and @Chip did a nice job there, as usual. So if your leg controller decides that all of them inputs make no sense he can in any procedure throw an error(code), catch'd by the main routine. That then can decide to 'reboot' and inform the master.

    Highly recommended for you. The last resort before the leg got 'hanging'. Reboot.

    Enjoy!

    Mike

    Basically check for uarts.rxCheck(xx)==-1

    if negative do not add to your buffer, just ignore. Nothing there.

    figure out some timeout procedure to avoid getting stuck while waiting for some byte in the loop.
  • DiverBobDiverBob Posts: 1,110
    edited 2015-01-05 16:41
    The original code from Jon used just uarts.rx, I changed it somewhere during the testing process and forgot about that particular change. I believe at one point I thought the serial string was hanging up in this routine and went to .rxcheck so it would proceed. Forgot to reset the buffer after that.
    I'll try that out and see how it all shakes out, that definitely explains the odd results I was seeing. I agree that good communications is critical to the operation of this robot, the system will be sending commands fast and furiously and can't afford a missed or misinterpreted command. I always add in error checking but not until I get the basic code operational. The leg movement code mainly contains error checking code. The feedback loop I'm working on now is part of the overall schema to validate commands and the successful completion of the commands.

    Thanks for taking a look at the code and catching the mistake!
  • DiverBobDiverBob Posts: 1,110
    edited 2015-01-07 17:30
    Finally got a chance to try out the correction and that fixed the bad outputs. Thanks again for the help. Now on to the next problem!
Sign In or Register to comment.