Array of mixed data types & access via loop?

Hi there,

I am polling status messages from a servo controller via RS232 by looping through an array of ASCII status commands. Some of the ASCII responses are meant to be used as integers, some as strings.

I am reading the responses via .readStringUntil() and storing them in a String array. When processing later, I convert the strings into the required data type. I was wondering if I could already do that right after obtaining the values.

I found quite a number of posts (here and elsewhere) suggesting an "an array of structures" to store variables of mixed data types, but they seem to require a descriptor for the variable, e.g.

struct status_responses {
  int st_PIA;
  int st_ESA;
  char* st_GS1;
}

Maybe it's a stupid question, can I write and read to such an array in a loop only by using the element pointer? Or is using the variable name (eg. st_PIA) absolutely necessary? Simply doing a status_responses[i] = status_reply; ?

Entire code is below:

#include <SoftwareSerial.h>
SoftwareSerial ServoSerial(10, 3);

String status_poll_commands[] = {
      "!PIA",			// Returns ASCII decimal integer value between 0 and 192532150.
      "!ESA",			// Returns ASCII decimal value between 0 and 4096. 4096 equals 100 %.
      "!GS1",			// Returns an ASCII character or string matching: ^(W|R|I|D|E\d|E\d{1,2}[A..D])$
};

String status_responses[3];

String status_reply;

void setup()
{
    // Open serial communications
    Serial.begin(9600);
    ServoSerial.begin(9600);
    
    ServoSerial.setTimeout(10);
}

void loop() {
  sub_PollServo();
}

void sub_PollServo() {
  
  for (int i = 0; i <= 2; i++) {

      // Send current element of the status_commands[] array, i.e. a status command, 
      // to the servo via the external serial connection
      ServoSerial.print(status_poll_commands[i] + "\r");

      // Read response string to the command from the servo via external serial connection
      status_reply = ServoSerial.readStringUntil('\r');

      // Clean up response string by removing the response header, i.e. a leading hashmark
      status_reply_placeholder.replace("#","");

      // store the cleaned-up response in the current status_response[] array element
      // whose position corresponds to that of the command that polled it

      status_responses[i] = status_reply;
  }
  
  // pull values back from array in another routine and do something with them, eg. calculations
  
  float  status_PIA =       status_responses[0].toFloat() / 10000000; // convert string to float value
  float  status_ESA = 100 * status_responses[1].toInt()   / 4096; // convert string to percent value
  String status_GS1 = 			status_responses[2];
  
  Serial.println(status_PIA);
  Serial.println(status_ESA);
  Serial.println(status_GS1);
}

// Output is
// 195.31
// 25.00
// R

Arrays cannot hold different types of data, only the type you created array for.

But that data type can be a struct and the struct can hold multiple data types

struct dataLayout
{
  byte a;
  int b;
  char c[10];
};

dataLayout data[3] //an array of 3 structs of type dataLayout
{
  {0, 1, "two"},
  {3, 4, "five"},
  {6, 7, "eight"}
};

void setup()
{
  Serial.begin(115200);
  Serial.println(data[0].a);
  Serial.println(data[1].b);
  Serial.println(data[2].c);
}

void loop()
{
}

Yes. I got that. But my for-loop reading the serial doesn't know the a, b, c names in that struct (unless it's possible to obtain them via the element number?) . It knows it's called data[ ] and it has a pointer i , so the best it can do is to target data[0]=, data[1]= and data[2]=, but not data[0].a=, data[1].b= and data[2].c=

Do you understand what I am getting at?

I don't understand what that means. YOU'RE the one writing the 'for' loop, so YOU know the names. Just add them to the loop code to access the struct elements.

Of course, I can access a particular element of the struct by addressing it manually, i.e. by typing out its name in the code. With three elements, just three lines. But what if I want to automatically iterate through a larger one, step by step?

I'm not sure if I make myself clear, sorry.

I'm afraid you haven't.

Thank you. I tried to name the elements numerically, in order to be able to access the element via the 'i' pointer from the loop. But that doesn't seem to work either. But perhaps it lets you understand what I am trying to achieve

struct dataLayout
	{
  	int 1;
  	int 2;
  	char* 3;
	};

String response_data {"19532150", "1024", "R"}

dataLayout data[1]; //an array of 1 structs of type dataLayout

void setup()	{
  for (int i = 0; i <= 2; i++) {
      data[0].i = response_data[i];
  }
}

void loop() {}

I am afraid that it doesn't

Let's go back to your original post

There is some confusion between Strings and strings, but putting that aside, converting the data when needed seems a perfectly valid thing to do.

If you see some advantage to storing the data in the format that it will be used then you could convert it as it is received and put it into a struct. You do not need an array of structs. In fact, you don't need a struct at all, just individual variables

Where an array of structs is helpful is if you want to store multiple sets of received data to perhaps analyse them over a time period

As to the possibility of accessing the members of a struct, be it an element of an array or not, without using the name of the member, why do you think that is a good idea ? Variable names mean nothing to the compiler but sensible variable names are worth their weight in gold to the programmer

How do YOU know what data type you have read?

Assuming they are all the same size they can be simply stored in an array of bytes then you can parse them because you (should) know how to distinguish between the data types.

Note that read until reads until nothing has been received for a second, therefore it is always slow.

I would use
https://www.arduino.cc/reference/en/language/functions/communication/serial/readbytesuntil/

Thank you for your comments.

The actual number of status commands is actually greater already (~20). I only showed three to make the demo code less convoluted.

To keep the code short, and for easier addition of further commands later, I had hoped to avoid having to write out the routines (sending, reading and storing) for each command separately - by creating an array and looping over it. The resulting disadvantage being having to deal with different data types in my storage construct.

Thank you for your comments.

Unfortunately, the replies are not the same size.

As for .readStringUntil(), the .setTimeout(10) in the code above works quite well. I have no complaints about it being slow. I've actually had it working with a 1ms timeout already.

A byte is always the same size.
Did you not understand what I said?

You parse the bytes into strings and numbers from the bytes.

Thank you for your reply.

But how is that better than parsing the strings obtained from .readStringUntil() into numbers, floats, characters or other strings? Because that works quite reliably, already. I am wondering if there's a way to have an iterable, singular storage construct for those parsed results. If not, then I'll have to use another solution, e.g. by making separate per-data-type arrays. I had hoped to be able to keep it in one, though.

Will you only be holding the current set of data received or do you need to hold sets of data received over a period ?

Thank you for your comment.

Some of the values in the servo will be changing quickly, some will remain constant for a longer period (eg. error statuses). I'll be polling them constantly. For interpretation, I'm only interested in the most current set received.

Then why not put the values in normal variables ?

Thanks for your patience.

Because I would have to maintain and individually refer to those variables somewhere in the depths of the code. Whereas now I can just keep the servo commands and corresponding result slots that I want to use (send/read/store/display) in an neat little array in the setup section on the very top of my code, while the actual job of sending/reading etc. is done by a stupid loop that will process whatever commands the array currently contains. With strings alone it works very neatly, as you can see from the initial example.

It should even be possible to store the desired data type along with the servo command to allow branching into the respective conversion routine without ever having to delve into the code section again for any later adjustments of the servo command set.

Want to see the motion profile amplitude? Add !EPA to the command set array, and it will magically be polled and appear on a status display with the other values, in its correct format.

Let's suppose that you could do what you want and have an array of mixed data types, the number of data elements of which will change as the sketch is modified. You would still need to maintain and individually refer to those array elements somewhere in the depths of the code.

However, you would then also have the additional problem that the array elements are themselves anonymous and are just integers. There are ways round this by using an enum or just integer variable names so that you could use to refer to the array elements in a more friendly way, but this too would require making changes when a data element is added, removed or changes its purpose

Thanks for sticking it out with me.

I am aware that this has its own challenges. But that's part of the fun.

The idea with the integer variable names sounds intriguing.