Working with strings

So I have this string:

String str= "A1N c2 t120 d4 t0 b5 t0 a2 t368 e2 t452 1c t0 e1 t600";

Now I want to create a queue of struct or list with something like:

qq[1]=[A][1][N]
qq[2]=[c][2]
qq[3]=[t][120]
...

Details- Read the string and then split it with spaces then after "t" is encountered in the string split the string either by space or when a character is encountered. Also it checks if after space there is a letter or a number and if it's a number it switches the struct, something(I tried) like:

 Target(char x, uint8_t y, uint16_t time): coords{x,y}, x_first{true},time{time}{}
  Target(uint8_t y, char x, uint16_t time): coords{x,y}, x_first{false}, time{time}{}

I use a library QList.h for this queue. although I still cannot process the string correctly, Any help would be appreciated.

can you describe what this sequence is?

the code below, breaks the sequence into tokens delimited by spaces.

your description of how they are processed is confusing

   A1N
   c2
   t120
   d4
   t0
   b5
   t0
   a2
   t368
   e2
   t452
   1c
   t0
   e1
   t600
#include <stdio.h>
#include <string.h>

char str [] = "A1N c2 t120 d4 t0 b5 t0 a2 t368 e2 t452 1c t0 e1 t600";

int
tokenize (
    char *str,
    char *tok [],
    int   maxTok )
{
    char s [100];

    strcpy (s, str);

    tok [0] = strtok (s, " ");
    int  n;
    for (n = 1; n < maxTok; n++)
        if (NULL == (tok [n] = strtok (NULL, " ")))
            break;
    return n;
}


#define MAX_TOK  20

int
main ()
{
    char *tok [MAX_TOK];
    int n = tokenize (str, tok, MAX_TOK);

    for (int i = 0; i < n; i++)
        printf ("   %s\n", tok [i]);
}

So basically I want a queue of struct which contains the following:

struct t{
//pair of co-ordinates like - (A,1)
//or if the string has number first instead of letter the like - (1,A)
//then there is t, which if t isn't mentioned doesn't matter but if t is mentioned then it should read the number after t like [120] and stuff until it either finds a space or newline or it finds a letter.
}

So basically, Read the string, preprocess it like I said above, then get them into a struct and make a queue of struct. So my end goal is I can get from queue value like: [A][1] or [c][2] and then compare these values like compare letter to letter that is A to c and 1 to 2 and subtract them to get 2 from A,c and 1 from 2,1.

So like if I get a string like- A1N 2b
then it should compare A to b and 1 to 2 and get values 1 and 1.

The code you posted will have it is giving me error in arduino ide

Arduino: 1.8.13 (Windows 10), Board: "Arduino Uno"

C:\Users\seric\Desktop\New folder (2)\MovementsWithSimpleFunctions\projectcoderobot\projectcoderobot.ino: In function 'void loop()':

projectcoderobot:110:40: error: cannot convert 'String' to 'char*' for argument '1' to 'int tokenize(char*, char**, int)'

 int n = tokenize (str, tok, MAX_TOK);

                                    ^

if the string is a String, not a char *, then you need to use str.c_str() to convert it to a c-string

kinda confusing. not even sure if splitting the string on spaces make sense. sounds like you'll need isdigit()

is there a spec (link) for this protocol?

the msg was very informative

here's code that i believe captures the input in the struct you're suggesting.

i didn't look over your code that drives hardware.

setMove:  0  a1n  (null)
setMove:  1   c2  t120
setMove:  2   d4  t0
setMove:  3   b5  t0
setMove:  4   a2  t368
setMove:  5   e2  t452
setMove:  6   1c  t0
setMove:  7   e1  t600

process:  1  (0,0)    0
process:  2  (2,1)  120
process:  3  (3,3)    0
process:  4  (1,4)    0
process:  5  (0,1)  368
process:  6  (4,1)  452
process:  7  (0,2)    0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

struct Move_s {
    int             x;
    int             y;
    unsigned long   msec;
};

#define MAX_MOVES   20
Move_s moves [MAX_MOVES];
int    moveN;

char str [] = "A1N c2 t120 d4 t0 b5 t0 a2 t368 e2 t452 1c t0 e1 t600";

// -----------------------------------------------------------------------------
int
tokenize (
    char *str,
    char *tok [],
    int   maxTok )
{
    static char s [100];
    strcpy (s, str);

    int  n;
    for (n = 0; n < strlen(s); n++)
        s [n] = tolower (s [n]);

    tok [0] = strtok (s, " ");
    for (n = 1; n < maxTok; n++)
        if (NULL == (tok [n] = strtok (NULL, " ")))
            break;
    return n;
}

// ---------------------------------------------------------
void
setMove (
    int    idx,
    char  *pos,
    char  *msec )
{
    printf ("%s: %2d  %3s  %s\n", __func__, idx, pos, msec);

    moves [idx].msec = 0;
    if (NULL != msec && 't' == msec[0])
        moves [idx].msec = atoi (++msec);

    if (isdigit(pos [0]))  {
        moves [idx].x = pos [0] - '1';
        moves [idx].y = pos [1] - 'a';
    }
    else {
        moves [idx].x = pos [0] - 'a';
        moves [idx].y = pos [1] - '1';
    }
}

// ---------------------------------------------------------
void
translate (
    char *tok [],
    int   nTok )
{
    moveN = 0;
    setMove (moveN++, tok [0], NULL);

    for (int i = 1; i < nTok; i+=2)
        setMove (moveN++, tok [i], tok [i+1]);
}

// ---------------------------------------------------------
void
disp ()
{
    Move_s  *m = moves;

    printf ("\n");
    for (int i = 1; i < moveN; i++, m++)
        printf ("%s: %2d  (%d,%d) %4d\n", __func__, i, m->x, m->y, m->msec);
}

// -----------------------------------------------------------------------------
#define MAX_TOK  40
int
main ()
{
    char *tok [MAX_TOK];
    int nTok = tokenize (str, tok, MAX_TOK);

    translate (tok, nTok);
    disp ();
}

That may be a problem. Doesn’t that give a pointer to the String’s internal buffer? And doesn’t strtok() insert nulls into the string to separate tokens? I think that would corrupt the String.

yes, if the string is a constant, such as in my simulation. which is why i've learned to copy the input string

Thank you, although the reason I was using a queue is to determine if the letter is first in the co-ordinate or the number so I can move accordingly in the grid, the code you posted will work but if a number is before the letter there will be no way of knowing that and it will make it quite difficult to know what action to take for the movement. Is there any way to rectify that ?

Although if I translate the letter from pos from setMoves to 5-9 instead of 0-4 I think I can know where to move. Could you tell me how to do that ?

p.s.- Sorry arduino coding is quite new to me and am still learning.

wasn't sure why some "commands" had the number first. as you can see, isdigit() is easy to use

i wonder if it might just be easier to sequentially process the tokens (in pairs) rather than translate them and store them in a struct.

code can keep track of the number of tokens as well as which token pair is being processed. however it seems delays are necessary to allow the robot to move the specified x and y distances as well as between movements.

millis() can be used to determine when those delays have passed without blocking other code execution (e.g. waiting for serial commands that may want to abort the current operation).

but there are different types of "actions" that are being waited on (e.g. an x or y movement, command delay). it seems some state needs to keep track of what action is next

a complete line of input, up to a newline (\n) can easily be read using readBytesUntil (). i've showed you how that line can be tokenized and decoded afterwards

i don't understand the code posted. it's not obvious how the robot maintains vertical and horizontal movments (is it in a maze with walls using "feelers", or dead reckoning)?

regardless of how movements are handled, loop() needs to read commands thru some interface (serial) and process tokens as described above.

i imagine a moveto() routine determining a sub-sequence of operations. it initiates an x or y move, terminated based on a timer expiring in loop() using millis() and finally updating its position.

as I suggested, a timer expiration may invoke some processing function that keeps track (i.e. state) of what sub-sequence step is next (X, Y or cmd-delay)

i assume all this sounds daunting. but as the code i posted demonstrated, can be broken down into sub-processing steps (e.g tokenize, decode, ...)

consider following

A1N c2 t120 d4 t0 b5 t0 a2 t368 e2 t452 1c t0 e1 t600
 move: X 2
 move: Y 1
 process: wait 120
 move: X 1
 move: Y 2
 process: wait 0
 move: X -2
 move: Y 1
 process: wait 0
 move: X -1
 move: Y -3
 process: wait 368
 move: X 4
 move: Y 0
 process: wait 452
 move: Y -1
 move: X -2
 process: wait 0
 move: X 2
 move: Y 0
 process: wait 600
#define MAX_TOK  40
char *tok [MAX_TOK];
int   nTok;
int   iTok;

unsigned long msec;
unsigned long msecLst;

int posX = 0;
int posY = 0;

char str [] = "A1N c2 t120 d4 t0 b5 t0 a2 t368 e2 t452 1c t0 e1 t600";
char s [80];        // used for sprintf()

// -----------------------------------------------------------------------------
void
tokDisp ()
{
    Serial.println ("tokDisp:");

    for (int i = 1; i < nTok; i++)  {
        sprintf (s, "  %2d: %s", i, tok [i]);
        Serial.println (s);
    }
    Serial.println ();
}

// ---------------------------------------------------------
int
tokenize (
    char *str,
    char *tok [],
    int   maxTok )
{
 // Serial.println ("tokenize:");

    int n = 0;

    static char s [100];
    strcpy (s, str);
    Serial.println (s);
    tok [0] = strtok (s, " ");
    for (n = 1; n < maxTok; n++)
        if (NULL == (tok [n] = strtok (NULL, " ")))
            break;

    nTok = n;
    iTok = 0;

#if 0
    tokDisp();
#endif

    return n;
}

// ---------------------------------------------------------
#define Abs(x)      (0 > x ? -x : x)
int
move (
    char c )
{
    int dist;

    if (isdigit (c))  {
        dist  = (c - '1') -  posY;
        posY += dist;
        sprintf (s, " move: Y %d", dist);
    }
    else {
        dist  = (c - 'a') - posX;
        posX += dist;
        sprintf (s, " move: X %d", dist);
    }

#if 1
    Serial.println (s);
#endif

    return Abs(dist);
}

// ---------------------------------------------------------
void
process (void)
{
    static int step = 0;

    char *xy   = tok [iTok];
    char *time = tok [iTok+1];
    int   dist = 0;

    // process first token
    if (0 == iTok)  {
        posX = tolower(xy [0]) - 'a';
        posY =         xy [1]  - '1';

        iTok++;
        step = 0;
        goto done;
    }

    // process following token
    switch (step)  {
    case 0:
        msecLst = msec + 500 * (dist = move (xy [0]));
        step++;
        break;    

    case 1:
        msecLst = msec + 500 * move (xy [1]);
        step++;
        break;    

    case 2:
        msecLst = msec + atoi (& time [1]);
        sprintf (s, " process: wait %s", & time [1]);
        Serial.println (s);

        iTok += 2;
        step  = 0;
        break;    

    default:
        break;
    }

done:
#if 0
    sprintf (s, "process: %3s %4s -- (%d,%d) step %d, msec %lu, dist %d",
        xy, time, posX, posY, step, msecLst, dist);
    Serial.println (s);
#endif

    if (nTok == iTok)
        iTok = -1;
}

// -----------------------------------------------------------------------------
void
loop (void)
{
    msec = millis ();

    if (0 <= iTok && msec > msecLst)  {
        process ();
    }
}

// -----------------------------------------------------------------------------
void
setup (void)
{
    Serial.begin (9600);

    tokenize (str, tok, MAX_TOK);
    msecLst = millis ();
}

Thank you also could you explain a little of how this is working and how to acccess the moves sequentially?

doesn't the code i posted demonstrate how the moves can be executed sequentially?

Yes it is but I am asking about how to access the values of the dist inside the setup since you are printing it on s it prints out the whole process that the robot should follow, dunno if i can access it to integrate with movement algorithm.

Like in your above code I could access it using

x+m[i].x+y+m[i].y+m1+m[i].msec

i assumed that move() would be fleshed out to actual move the robot in the x or y direction by the dist it calculates.

Yes thank you soo much, Sorry I did not notice the xy unfortunately, just one thing is the function move how can I keep track of current and target co-ordinates? Since I need them for moves to make, Like for first Currpos=A1 , targetPos = c2 and so on

move() calculates "dist" using the current coordinates: "posX" and "posY", and then updating them.

i'm curious how the code manages direction -- know what direction to turn to before moving some "dist"

I am debugging my algorithm for it, It works properly on the serial monitor but does not work in real life

post the code