Pick substring from 2D-Char Array based on first 2 datasets

I know… Its a bit a weird title… but hear me on :slight_smile:

Here is the data-landscape (Example):

char payloads[3][16]=
{
    "1;1;payload11",
    "2;2;payload22",
    "3;3;payload33",
};

char delimiter[] = ";";

int gridx = 2;
int gridy = 2;

void setup(void) {
  Serial.begin(9600);
  // handle one field after the other
  for (int i = 0; i <= 3; i++) {
    // Break the string apart into substrings
    char *p = payloads[i];
    char *str;
    while ((str = strtok_r(p, delimiter, &p)) != NULL) {
      Serial.println(str);
    }
  }
}

This spits out

1
1
payload11
2
2
payload22
3
3
payload33

as expected.
However (Weird) if i do the same in the main loop:

void loop(void) {
    // handle one field after the other
    for (int i = 0; i <= 3; i++) {
      // Break the string apart into substrings
      char *p = payloads[i];
      char *str;
      while ((str = strtok_r(p, delimiter, &p)) != NULL) {
        Serial.println(str);
      }
    }
  delay(100);
}

it only spits out

1
2
3

Anyway…

Now, i want to ONLY spit out the payload defined with the 2 grid*-variables (gridx & gridy)
Like if i set them both to 1, it would print the “payload11”, with them set both to 3 it would print “payload33”.
Of course, there will be a large swath of possible payloads. Like an array 336 index long. I took here as an example only the diagonal fields at location 1/1, 2/2, and 3/3.

Can someone point me in the right direction to make this work?

This is wrong as it will access the 4th element.

for (int i = 0; i <= 3; i++)

I’m not sure if I understand your requirement.

It is late here :slight_smile:
Brain is melting...

I found a solution:

String lookupstring;
String payloads[] = {
    "1;1;payload11",
    "2;2;payload22",
    "3;3;payload33",
};

and

    lookupstring = String(gridx) + ";" + String(gridy) + ";";     // Build string with the grids
    if(payloads[1].indexOf(lookupstring) >= 0){ // Look for the built string in the payload-array
      Serial.print("\tYAY!!!!"); //
    }

where payloads[1] will become dynamic later on. But at the moment, it triggers on gridx & gridy = 2 (index 1 of payloads).

Orngrimm:
It is late here :slight_smile:
Brain is melting...

Using the String class is an ugly solution. After getting some sleep, please try to write a coherent description of what you're trying to do.

It is quite simple actually:

  • I have a collection of strings/char
  • Those strings all have the structure X;Y;payload
  • X and Y represent a grid. I have different payloads for each grid
  • I get 2 numbers via external sensor-pad. (Thats not the problem)
  • Those 2 numbers represent the user-chosen X and Y for the payload-selection
  • The code should look
  • if he has a payload for the chosen X and Y
  • if so, the code should extract the payload and send it out (Serial in this example)

Full code as it is currently:

// Touch screen library with X Y and Z (pressure) readings as well
// as oversampling to avoid 'bouncing'
// This demo code returns raw readings, public domain

#include <stdint.h>
#include "TouchScreen.h"

#define YP A0  // must be an analog pin, use "An" notation!
#define XM A1  // must be an analog pin, use "An" notation!
#define YM A2  // can be a digital pin
#define XP A3  // can be a digital pin

// "old" values of previous run
int ox = -1;
int oy = -1;
int oz = 0;


int zerox = 114;
int zeroy = 58;
int maxx = 930;
int maxy = 945;
int rangex = maxx - zerox;
int rangey = maxy - zeroy;
int gridsx = 16.0;
int gridsy = 21.0;
float dividerx = rangex / (gridsx-1) * 1.0; // Delta X per field
float dividery = rangey / (gridsy-1) * 1.0; // Delta X per field

// where we pressed, final result
byte gridx = 0;
byte gridy = 0;

String lookupstring;
String payloads[] = {
    "1;1;payload11",
    "2;2;payload22",
    "3;3;payload33",
};

// For better pressure precision, we need to know the resistance
// between X+ and X- Use any multimeter to read it
// For the one we're using, its 300 ohms across the X plate
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 500);

void setup(void) {
  Serial.begin(9600);
  // handle one field after the other
  for (int i = 0; i <= 3; i++) {
    Serial.println(payloads[i]);
  }
}

void loop(void) {
  // a point object holds x y and z coordinates
  TSPoint p = ts.getPoint();
  // we have some minimum pressure we consider 'valid'
  // pressure of 0 means no pressing!
  if (p.z > ts.pressureThreshhold) {
    oz = p.z;
    Serial.print("\nX = "); Serial.print(p.x);
    Serial.print("\tY = "); Serial.print(p.y);

    gridx = ((p.x - zerox + (dividerx / 2)) / dividerx) + 1;
    gridy = ((p.y - zeroy + (dividery / 2)) / dividery) + 1;
    Serial.print("\tGrid = "); Serial.print(gridx); Serial.print("/"); Serial.print(gridy);

    lookupstring = String(gridx) + ";" + String(gridy) + ";";     // Build string with the grids
    for(int j = 0; j <= 3; j++) {
      if(payloads[j].indexOf(lookupstring) >= 0){ // Look for the built string in the payload-array
        Serial.print("\t");
        Serial.print(payloads[j].substring(lookupstring.length(),payloads[j].length())); // Deliver payload -------------------
      }
    }

    // Wait till the press is gone
    while(oz >= 1){
      TSPoint p = ts.getPoint();
      oz = p.z;
    }
  }
  delay(100);
}

Orngrimm:

  • I have a collection of strings/char
  • Those strings all have the structure X;Y;payload

Where do these strings come from? Do you absolutely have to store them in such an awkward data structure? Your life would be much easier if you just stored the "payloads" in a 2-dimensional char array and then indexed into that array using the "numbers" from your sensor pad (after proper limit checking).

EDIT:
Sorry, it would be a 3-D char array (x and y would index into it producing a c-string). Still easier than the way you're trying to do it.

strtok() modifies the original cstring. That's why you can't repeatedly use it on the same cstring.

Below demo of your String implementation using the character arrays from the opening post.

char payloads[][16] =
{
  "1;1;payload11",
  "2;2;payload22",
  "3;3;payload33",
};

void setup()
{
  Serial.begin(57600);
  while (!Serial);

  int gridx = 3;
  int gridy = 3;
  
  // lookup text; caters for 4 digit x and 4 digit y
  char lookup[11];
  sprintf(lookup, "%d;%d;", gridx, gridy);

  // flag indicating if the payload at xy is found or not
  bool found = false;

  // loop through the payloads to find payload at xy
  for (uint8_t cnt = 0; cnt < sizeof(payloads) / sizeof(payloads[0]); cnt++)
  {
    if (memcmp(payloads[cnt], lookup, strlen(lookup)) == 0)
    {
      // got it
      found = true;
      // print the payload
      Serial.print("found "); Serial.println(&(payloads[cnt][strlen(lookup)]));
    }
  }

  // xy not found
  if (found == false)
  {
    Serial.print(lookup); Serial.println(" not found");
  }
}

void loop()
{
}

Make sure that the lookup variable is big enough to hold the full text “x;y;” including terminating ‘\0’ .

arduino_new:
strtok() modifies the original cstring. That's why you can't repeatedly use it on the same cstring.

Ah! Makes sense now... Thanks for pointing this out! Learned something today already and its early. :slight_smile:

gfvalvo:
Where do these strings come from? Do you absolutely have to store them in such an awkward data structure? Your life would be much easier if you just stored the "payloads" in a 2-dimensional char array and then indexed into that array using the "numbers" from your sensor pad (after proper limit checking).

The dataset will come thru either Serial or from a TXT-file of an SDcard.
If i can read those 2 sources into a 3D-Array, please tell me how. :slight_smile:
See, the conents should remain human readable end humanly editable. i tought to have the location indicator in front of the payload would make things easyer as it whould be possible to store/transmit those coords in random order and still being able to figure out, what payload belongs to what coordinate.
See, i will have multiple coordinates wih the same payload (Big button of 2x2 coordinates which have to be pressed in a hurry). it would be handy if i can say for example
10;20;Sj
11;20;Sj
10;21;Sj
11;21;Sj
for a 2x2-Button 10/20 to 11/21 for Shift-j

But there will also be smaller ones (1x1) or weird shapes (L-Shaped for example) or split...
Thats why i think it will be handy to have the locator in front of each payload.

sterretje:
Below demo of your String implementation using the character arrays from the opening post.

...

Make sure that the lookup variable is big enough to hold the full text "x;y;" including terminating '\0' .

Thanks! Didnt know memcmp. This is very handy and usefull for such tasks. Thanks for the hints and code. I glanced over it and saw already some really nice ideas and methods. Still leraning noob here... :wink:

If i can somehow ready Serial to such a structure or a TXT from a SDcard to such a structure, that would be super handy, yes.
But if i understand it correctly, a 2D-Array of char needs the size in its second dimension as big as the biggest Char?
If i have one index 20 chars long, the array would need to be [21] and thus wasting a lot of space, no?
If i also understood it correctly, the Array of string can have different length of string in the array?

Sooo many question, not enough time to play... 100% job + 50% sport = low time for such cool projects. :frowning:

Some questions
1)
Which Arduino; that will will indicate what is feasible using RAM or EEPROM to store your payloads. This will not take into account a fancy TFT screen or a few hundred neopixels that chew away most of the memory.
2)
How many payloads? Maximum size of payload?
3)
How many tile combinations?

Don't be fooled by String; depending on how you use it, it can leave holes in your memory and you might possibly loose memory instead of gaining.

Orngrimm:
If i can read those 2 sources into a 3D-Array, please tell me how. :slight_smile:

Thinking about it some more, a 2-D array of char pointers would be even more efficient as each c-string would only need to be as long as the actual payload. An absent payload would cost you the space of a NULL pointer:

const uint8_t numCols = 4;

const char * const payloads[][numCols] = {
  {"payload00", "payload01", nullptr, nullptr},
  {nullptr, "payload11", nullptr, "payload13"},
  {"payload20", "payload21", "payload22", "payload23"},
  {nullptr, nullptr, nullptr, "payload33"}
};

const uint8_t numRows = sizeof(payloads) / sizeof(payloads[0]);

void setup() {
  const char *ptr;
  
  Serial.begin(115200);
  delay(1000);
  for (uint8_t i = 0; i < numRows; i++) {
    for (uint8_t j = 0; j < numCols; j++) {
      Serial.print("Element[");
      Serial.print(i);
      Serial.print("][");
      Serial.print(j);
      Serial.print("]: ");
      ptr = payloads[i][j];
      if(ptr) {
        Serial.println(ptr);
      }else {
        Serial.println("NULL");
      }
    }
  }
}

void loop() {
}

OUTPUT:

Element[0][0]: payload00
Element[0][1]: payload01
Element[0][2]: NULL
Element[0][3]: NULL
Element[1][0]: NULL
Element[1][1]: payload11
Element[1][2]: NULL
Element[1][3]: payload13
Element[2][0]: payload20
Element[2][1]: payload21
Element[2][2]: payload22
Element[2][3]: payload23
Element[3][0]: NULL
Element[3][1]: NULL
Element[3][2]: NULL
Element[3][3]: payload33

Of course, this is still a full rectangular array. If your actual array is really sparse and oddly shaped, then I’d go with a linked list of dynamically-allocated struct(s). Each struct would hold the (x, y) coordinates, a pointer to the payload c-string, and a pointer to the next struct in the list. You’d need to traverse the list every time to see if a given (x, y) combination is present. So, your choice is a trade-off between memory efficiency and execution efficiency.

sterretje:
Some questions
1)
Which Arduino; that will will indicate what is feasible using RAM or EEPROM to store your payloads. This will not take into account a fancy TFT screen or a few hundred neopixels that chew away most of the memory.
2)
How many payloads? Maximum size of payload?
3)
How many tile combinations?

Don't be fooled by String; depending on how you use it, it can leave holes in your memory and you might possibly loose memory instead of gaining.

:slight_smile: Good questuions. I try to answer them below:
1.: Will be a pro Micro with a AT32U4 (https://www.sparkfun.com/products/12640)
No other peripherals other than a X/Y-Resistive Touchpad (Needs 4 Analog pins)... The ressources should be plenty for this i think and if my initial estimations were correct.

2.: Max size is 16x21=336. Payload max will be around 4 Characters.
Lets assume, we have all 2-digit-Coordinates and all the max payload:
336 x (6 + 4 + 1) = 3696 bytes as absolute MAX // 6 chars for "xx;yy;", 4 chars for payload, 1 char for \0
The Mega32u4 has 32kB flash - 4kB for bootloader = 28kB flash usable. Plenty.
Even if calculating with too big for all dual-digit xx;yy; and max payload and minimal gridsize and all grids filled i could put in roughly 5x the amount even if i very generously calculate for code itself (10kB of compiled output code only)

3.: Unsure what you mean with "Tile combinations". How many DIFFERENT payloads there can be? all of them. Will they all be filled and all be different? Propably not.

gfvalvo:
Thinking about it some more, a 2-D array of char pointers would be even more efficient as each c-string would only need to be as long as the actual payload. An absent payload would cost you the space of a NULL pointer:

...

Of course, this is still a full rectangular array. If your actual array is really sparse and oddly shaped, then I'd go with a linked list of dynamically-allocated struct(s). Each struct would hold the (x, y) coordinates, a pointer to the payload c-string, and a pointer to the next struct in the list. You'd need to traverse the list every time to see if a given (x, y) combination is present. So, your choice is a trade-off between memory efficiency and execution efficiency.

Uh! i LIKE that!
I think for flexibility it would be best if i stick with the rectangular shape and fill in the null pointer where it is empty.
But yeah: I think this is a very nice as the actual needed Space is the real payload.
I think today makrs a big point in my Arduino-life: Understanding and seing the use of pointers! :slight_smile:
I think i will follow this path as it offers the sleekest design i can think of (At the moment ;)).

Orngrimm:
:slight_smile: Good questuions. I try to answer them below:
1.: Will be a pro Micro with a AT32U4 (https://www.sparkfun.com/products/12640)
No other peripherals other than a X/Y-Resistive Touchpad (Needs 4 Analog pins)... The ressources should be plenty for this i think and if my initial estimations were correct.

2.: Max size is 16x21=336. Payload max will be around 4 Characters.
Lets assume, we have all 2-digit-Coordinates and all the max payload:
336 x (6 + 4 + 1) = 3696 bytes as absolute MAX // 6 chars for "xx;yy;", 4 chars for payload, 1 char for \0
The Mega32u4 has 32kB flash - 4kB for bootloader = 28kB flash usable. Plenty.
Even if calculating with too big for all dual-digit xx;yy; and max payload and minimal gridsize and all grids filled i could put in roughly 5x the amount even if i very generously calculate for code itself (10kB of compiled output code only)

3.: Unsure what you mean with "Tile combinations". How many DIFFERENT payloads there can be? all of them. Will they all be filled and all be different? Propably not.

OK
2a)
In principle flash can't be updated at run time. You have a requirement that it needs to be user editable based on serial or sd card. If it comes from serial, it will need to be stored in RAM or EEPROM or external storage (SD, EEPROM, FRAM)
2b)
Payload was initially a lot bigger ("payload11") and I (mis)understood that it could be bigger.
3)
Sorry for being unclear or misunderstanding.
You were talking about a big tile (2x2) so coords e.g. 01/01, 01/02, 02/01 and 02/02.
You were talking about an L shape, so coords e.g. 04/01, 04/02, 04/03 and 05/03.
I think I understand now; if any of the the tiles in e.g. the 2x2 is touched, you send the same text. If any of the tiles in the in the L is touched, you send another text (or the same text as for the 2x2).

2a)
In principle flash can't be updated at run time. You have a requirement that it needs to be user editable based on serial or sd card. If it comes from serial, it will need to be stored in RAM or EEPROM or external storage (SD, EEPROM, FRAM)

Hm... Thinking about this... Yes...
May pose a problem down the line.
I think this i will solve in Version 2. For now and version 1 i think i go with a simpler "needs to be reprogrammed / reflashed if something changes".

2b)
Payload was initially a lot bigger ("payload11") and I (mis)understood that it could be bigger.

sorry. For programming and testing purposes i like to have a clear marker and not the true payload of the end which could be like "Ec" or "@" or "Sj" as those dont give me info on where i pressed and if the correct thing came out :slight_smile:

Sorry for being unclear or misunderstanding.
You were talking about a big tile (2x2) so coords e.g. 01/01, 01/02, 02/01 and 02/02.
You were talking about an L shape, so coords e.g. 04/01, 04/02, 04/03 and 05/03.
I think I understand now; if any of the the tiles in e.g. the 2x2 is touched, you send the same text. If any of the tiles in the in the L is touched, you send another text (or the same text as for the 2x2).

No need to be sorry! You helped a lot! And yes: basically i have a grid of squares and can "glue" together many such squares to a free-formed button. Whereever on this button i press, the same payload is sent. I think its the simplest thing to just define all the quares as individual buttons and let them have the same payload.

Ah... I see. Variables are stored in RAM and not flash, even if they are basically constants.
Hm... Any way to tell the compiler to store those 3d-Char-tables in flash?
Is the only way declaring every single possible field (336) with

const char Payload_1[] PROGMEM = "payload1/1";
const char Payload_2[] PROGMEM = "payload1/2";
..
const char Payload_16[] PROGMEM = "payload1/16";
const char Payload_17[] PROGMEM = "payload2/1";
..
const char Payload_336[] PROGMEM = "payload16/21";

and then

const char* const payloads[] PROGMEM = { 
  {Payload_1, Payload_2, ... , Payload_16} , 
  {Payload_17, Payload_18, ... , Payload_32} , 
  ...
  {Payload_317, Payload_318, ... , Payload_332}
};

?

Honestly, i think the best solution would be if i donate a small memory (Which is QUICKLY more than large enough to be allowed to piss away space left and right) on I2C and work from there...

I would only store the payloads there. Hear me out how to address them...
Lets say, i allow 32 bytes (Normal size of one flash-page) for each field (MUCH too much but hey, lets see where it leads us) and i have 336 payloads (again, the max), that would make 32x336B=10.5kB or 10'752 Bytes. Pft... I think we have some 24CW1280T-I/SN around i could "borrow"... Organisation 16k x 8 and pagesize of 32. Perfect. Small, cheap, I2C, Fast enough with 1MHz...

Every 32 Bytes i would start a new payload.
Each row is 16 Payloads. So the position to start read till the end-delimiter would be:
(Line * 13 + Colum) * 32
Read till i reach lets say \0 and BAM!

  • Would offload all the payload to external memory.
  • only minimal memory needed on Arduino-side: 32 Bytes (max) for the current payload.
  • Actual transfered data on I2C would be minimal: 32 Bytes (max)
  • I can update the payloads on runtime
  • I can update only certain payloads and dont need to upload all at once
  • Once updated, the payloads would stay as they were on Shutdown
  • Number of Flash-rewrites is no problem. If i do 100 rewrites, that would be way more than anticipated

Sounds quite sleek to me... :wink: