Help text file to function calls

typical story, old vb, vb.net programmer and totally confused with c++ variables and storage limits on my arduino. I have been using an arduino uno board for a while to control a set of led lights and it has been working great. There are a list of cool functions in my program and I have an array of function pointers that basically builds the order in which it plays the effects.

Here is how that variable is declared

typedef void (*SimplePatternList[])();
SimplePatternList gPatterns = { sinelon, stranger, rainbowWithGlitter, sinelon, rainbowWithGlitter, rainbowWithGlitter  };

Then in the main loop we move call the functions and move the index.

 // Call the current pattern function once, updating the 'leds' array
  gPatterns[gCurrentPatternNumber]();

  // send the 'leds' array out to the actual LED strip
  FastLED.show();  
  // insert a delay to keep the framerate modest
  FastLED.delay(1000/FRAMES_PER_SECOND); 

  // do some periodic updates
  EVERY_N_MILLISECONDS( 20 ) { gHue++; } // slowly cycle the "base color" through the rainbow
  EVERY_N_SECONDS( 10 ) { nextPattern(); } // change patterns periodically

  Serial.println(String("This is gHue: " + gHue));

So all that above works great. Then I had the bright idea that I wanted to store that string ("sinelon, stranger, rainbowWithGlitter, sinelon, rainbowWithGlitter, rainbowWithGlitter ") on an SD card and when the program starts up I read the SD card and the lights get their instructions for how they should operate.

I got the SD working and I can even read the string of text into my program. I have not however been able to use that string to call functions or select case or anything really. I think the strings are too big for my uno board and I am totally lost on the array of char. I have spent a lot of time trying to wrap my head around the issue and I have failed so far.

So I come here for help. I was hoping one of you kind strangers could throw some short code blocks at me to do the following

  1. Read data from a txt file on an SD card into some sort of container.
  2. Then loop through that container and call each function one at a time.

Thanks so much!

Why store the string? Why not just store a sequence of indices into the gPatterns [] array?

When you read the index from the SD, just use it to call the function like you're doing now:

gPatterns[gCurrentPatternNumber]();

If you want to use readable names to invoke the functions, then the program has to have some data structure that stores those names in association with the indices that would cause the corresponding functions to be called.

The data you really need is only the index. But maybe you like the idea of human readable play list.

So you need room for the names, that shouldn't be hard.

Then you need to code a way to look up the name. The simplest would be a for loop over an array of structures, each struct would have a name character string (array of characters) and either use the index as the same one to use on the function pointer array, or also have a number as part, that number would be the index.

Or you could just make the struct be a home for the function pointers, too. Then finding the name would mean you had also found the function pointer.

Code writes itself.

No, srsly, above I put in italics some words that might inform your activities during "learning time".

Where did you get the code you have not yet posted but snippets of which? Please post the code and tell us if the programming you've done has anything like looking up a key to determine a value, or if it has any functions that can put together and take apart strings of characters with alacrity.

a7

How many times will you re-edit the file on the SD-card?

  • almost daily or only a 3 to 5 times?

Either way you will edit the textfile on a computer
Indeed strings will occupy quite some RAM.

So to have both:
the flexibitiy of an SD-card-file for configuring the sequence of pattern
and
small RAM-usage
you already have this indexlist

typedef void (*SimplePatternList[])();
SimplePatternList gPatterns = { sinelon, stranger, rainbowWithGlitter, sinelon, rainbowWithGlitter, rainbowWithGlitter  };

So if you use a excelsheet that translates from the readable names
sinelon, stranger, rainbowWithGlitter
to the index-number and you store just the index-numbers
reading in the index-number is sufficient.

or maybe keeping the names in your SD-card-file but skip the name and just process the index-number

sinelon=0  
stranger=1
rainbowWithGlitter=2

So you would read in a complete line and then determine the position of the "=" and pick and process just what is behind the "=" which is the indexnumber

textfile-content-example

sinelon0=0  
rainbowWithGlitter2=2
stranger1=1
stranger1=1
rainbowWithGlitter2=2
stranger1=1
sinelon0=0  
stranger1=1
sinelon0=0  
rainbowWithGlitter2=2

If you look carefully I added the indexnumber at the end of the name
so even the name itselfs tells you the index-number

best regards Stefan

Why is it so bad for @n88n to want to avoid needing to think about, or clutter up, her text file with numbers, however? or just have a file with only numbers.

I think the goal of having a text file with f/x names for a show sequence of some kind is admirable.

This is 2023. Ppl want the machines to do the thinking.

And the code to do that isn't, conceptually, hard at all. It seems this project would afford her a very nice reason to learn about some next features in a new language.

a7

it's just not a short code *), but the basic idea is simple:

put the human readable function names one by one on each line
read the file linewise,
map the readed line to real program functions
fill your SimplePatternList[] array with the function pointers.
process your SimplePatternList[]

it might be that you will need some dedicated "END" information.

*) if you start a "short" demo sketch showing how you process your function pointer array and include all necessary parts about mounting/reading the SD card in setup() - I guess that encourages others to add the missing parts.

Hi @n88n,
I'm not shure if you are aware of the fact that your function names are lost when compiling and uploading the sketch. Your Arduino code doesn't know anything about your function names.
Your code only uses the index of your function table. That's why you have to map a function name to the index number in your txt file. And it is your responsibility that the name matches the correct index.

Take a look at the preprocessor's stringify operator '#' and get creative with macros.

so many good responses, thank you everyone! I have been super busy with my job today so I have not been able to add my code here or really soak in all the replies. I did want to acknowledge all the posts here though. thank you. I dig in over the weekend.

Hi @n88n,

here are two (quite similar) examples how to read the function names from SD card and to call the related function:

The first without the use of the String class:

Sketch I
/*

   Reading lines from SD card
   Comparing each line with the entries of a given string array
   Calls the related function (if found)

   This version uses char arrays / c strings


   Forum: https://forum.arduino.cc/t/help-text-file-to-function-calls/1154798/4
   Wokwi: https://wokwi.com/projects/372306615255913473

  ec2021
  2023-08-06

*/

#include <SD.h>
#define CS_PIN 10



void sinelon() {
  Serial.println("Calling sinelon!");
  delay(2000);
}

void stranger() {
  Serial.println("Calling stranger!");
  delay(2000);
}

void rainbowWithGlitter() {
  Serial.println("Calling rainbowWithGlitter!");
  delay(2000);
}

// Make sure that the Sequence list is in the same order and length as the
// pattern function list and to use the identical names in this array and
// in the SD card text file!
//


typedef void (*SimplePatternList[])();
SimplePatternList gPatterns = { sinelon, stranger, rainbowWithGlitter};

constexpr byte noOfSeq = 3;
constexpr char *Sequence[noOfSeq] = {
  "sinelon", "stranger", "rainbowWithGlitter"
};

void setup() {
  Serial.begin(115200);
  Serial.print(F("Initializing SD card... "));
  if (!SD.begin(CS_PIN)) {
    Serial.println(F("Card initialization failed!"));
    while (true);
  }
  Serial.println(F("initialization done.\n-------------------------------------------\n\n"));
  readSequence();
  Serial.println(F("-------------------------------------------"));
  Serial.println(F("End of Sequences"));
}

void loop() {
  // nothing happens after setup finishes.
}


void readSequence() {
  constexpr byte cLen = 40;
  char aLine[cLen + 1];
  aLine[cLen] = '\0';
  File textFile = SD.open(F("sequence.txt"));
  int i = 0;
  if (textFile) {
    while (textFile.available()) {
      char c = textFile.read();
      if (i >= cLen) {
        i = 0;
      }
      if (c >= ' ') {
        aLine[i] = c;
        i++;
      }
      if (c == 10 && i > 0) {
        aLine[i] = '\0';
        compare(aLine);
        i = 0;
      }
    }
    textFile.close();
    if (i > 0) {
      aLine[i - 1] = '\0';
      compare(aLine);
    }
  }  else {
    Serial.println(F("error opening sequence.txt"));
  }
}


void compare(char * line) {
  int No = -1;
  for (int i = 0; i < noOfSeq; i++) {
    if (strcmp(line, Sequence[i]) == 0 ) {
      No = i;
    }
  }
  char buf[80];
  sprintf(buf, "%-25s relates to ", line);
  Serial.print(buf);
  if (No != -1) {
    Serial.println(No);
    gPatterns[No]();
    //
    // This is where further action can be taken
    //
    // "No" equals gCurrentPatterNumber
  } else {
    Serial.println("-");
    Serial.println(F("Calling nothing .... ;-) "));
    delay(2000);
  }
}

To be tested on Wokwi: Read Sequence From SD - Wokwi ESP32, STM32, Arduino Simulator

And the second with (a little) use of the String class:

Sketch II
/*

   Reading lines from SD card
   Comparing each line with the entries of a given string array
   Calls the related function (if found)

   This version makes use of String objects/functions

   Forum: https://forum.arduino.cc/t/help-text-file-to-function-calls/1154798/4
   Wokwi: https://wokwi.com/projects/372315676518280193

  ec2021
  2023-08-06

*/

#include <SD.h>
#define CS_PIN 10

void sinelon() {
  Serial.println("Calling sinelon!");
  delay(2000);
}

void stranger() {
  Serial.println("Calling stranger!");
  delay(2000);
}

void rainbowWithGlitter() {
  Serial.println("Calling rainbowWithGlitter!");
  delay(2000);
}

// Make sure that the Sequence list is in the same order and length as the
// pattern function list and to use the identical names in this array and
// in the SD card text file!
//

typedef void (*SimplePatternList[])();
SimplePatternList gPatterns = { sinelon, stranger, rainbowWithGlitter};

constexpr byte noOfSeq = 3;
constexpr char *Sequence[noOfSeq] = {
  "sinelon", "stranger", "rainbowWithGlitter"
};



void setup() {
  Serial.begin(115200);
  Serial.print(F("Initializing SD card... "));
  if (!SD.begin(CS_PIN)) {
    Serial.println(F("Card initialization failed!"));
    while (true);
  }
  Serial.println(F("initialization done.\n-------------------------------------------\n\n"));
  readSequence();
  Serial.println(F("-------------------------------------------"));
  Serial.println(F("End of Sequences"));
}

void loop() {
  // nothing happens after setup finishes.
}


void readSequence() {
  String aLine;
  File textFile = SD.open(F("sequence.txt"));
  int i = 0;
  if (textFile) {
    while (textFile.available()) {
      aLine = textFile.readStringUntil('\n');
      aLine.trim();                             // trim white spaces
      aLine.replace("\d", "");                  // remove carriage return from string
      if (aLine.length() > 0) {
        compare(aLine);
      }
    }
    textFile.close();
  }  else {
    Serial.println(F("error opening sequence.txt"));
  }
}

void compare(String &line) {
  int No = -1;
  char buf[80];
  for (int i = 0; i < noOfSeq; i++) {
    if (line == Sequence[i]) {
      No = i;
    }
  }
  sprintf(buf, "%-25s relates to ", line.c_str());
  Serial.print(buf);
  if (No != -1) {
    Serial.println(No);
    gPatterns[No]();
    //
    // This is where further action can be taken
    //
    // "No" equals gCurrentPatterNumber
  } else {
    Serial.println("-");
    Serial.println(F("Calling nothing .... ;-) "));
    delay(2000);
  }
}

Wokwi: Read Sequence From SD II - Wokwi ESP32, STM32, Arduino Simulator

In both examples the gPatterns array is no more used to define the sequence but only as an array to point to each different function. Make sure to have the function names in this array exactly at the same position as in the char array Sequence[] ...

Good luck !

The grand idea is to have others be able to modify functionality without my help. Using the names of the functions seemed like a better option for me. However i would be ok storing the indices if that was needed.

thank you for making note on some of the terms and concepts that I need to learn. I will use your words in italics to learn a bit more about C structures. I understand what you say in terms of how the code would work, i just have a lot to learn about how to build those loops. thank you for you input.

thank you for your thoughts. Sorry I figured reading through the text file and putting that data into a container would be a simple task. Just one that I had not been able to figure out yet. I think I made a mistake leaving most of the code out of my post. i was trying to make it simple and ask for something small but I think I took the wrong approach.

Next time I will post all my code so it is easier for you folks.

thank you. i have to learn more about how the function table. I see why vb programmers are considered lasy. :slight_smile:

I will take a look the 3 operator. thank you!

Wow! this was so kind of you. thank you so much for all the info here. The simulator was pretty a fun tool. I moved your sketch II code over to my arduino and it works great. Well after i figured out to adjust the baud rate it was easier to see that the code was working well.

This is going to be so helpful to me. thank you :heart:

You are welcome... The most important thing is whether the examples are understandable for you (or not)...

Have fun!

I took the sketch from @ec2021 and thought it would be easier to maintain if you think of a "mapping table" from the function to a human readable text on the SD file.

so I recommend to define a structure and put the entries in one array:

/*

   Reading lines from SD card
   Comparing each line with the entries of a given string array
   Calls the related function (if found)

   This version uses char arrays / c strings


   Forum: https://forum.arduino.cc/t/help-text-file-to-function-calls/1154798/4
   Wokwi: https://wokwi.com/projects/372306615255913473

  based on ec2021
  adopted by noiasca
  2023-08-20

*/

#include <SD.h>
#define CS_PIN 10

void sinelon() {
  Serial.println("Calling sinelon!");
  delay(2000);
}

void stranger() {
  Serial.println("Calling stranger!");
  delay(2000);
}

void rainbowWithGlitter() {
  Serial.println("Calling rainbowWithGlitter!");
  delay(2000);
}

// Make sure that the Sequence list is in the same order and length as the
// pattern function list and to use the identical names in this array and
// in the SD card text file!

/*
  typedef void (*SimplePatternList[])();
  SimplePatternList gPatterns = { sinelon, stranger, rainbowWithGlitter};

  constexpr byte noOfSeq = 3;
  constexpr char *Sequence[noOfSeq] = {
  "sinelon", "stranger", "rainbowWithGlitter"
  };
*/

using FunctionPtr = void (*)();

struct TableRow {
  const char *text;         // human readable text/name on the SD Card
  FunctionPtr functionPtr;  // internal function name
};

TableRow table[] {
  //name on SD    -->    function
  {"sinelon",            sinelon},
  {"stranger",           stranger},
  {"rainbowWithGlitter", rainbowWithGlitter}
};
constexpr byte noOfEntries = sizeof(table) / sizeof(table[0]);


void setup() {
  Serial.begin(115200);
  Serial.print(F("Initializing SD card... "));
  if (!SD.begin(CS_PIN)) {
    Serial.println(F("Card initialization failed!"));
    while (true);
  }
  Serial.println(F("initialization done.\n-------------------------------------------\n\n"));
  readSequence();
  Serial.println(F("-------------------------------------------"));
  Serial.println(F("End of Sequences"));
}

void loop() {
  // nothing happens after setup finishes.
}


void readSequence() {
  constexpr byte cLen = 40;
  char aLine[cLen + 1];
  aLine[cLen] = '\0';
  File textFile = SD.open(F("sequence.txt"));
  int i = 0;
  if (textFile) {
    while (textFile.available()) {
      char c = textFile.read();
      if (i >= cLen) {
        i = 0;
      }
      if (c >= ' ') {
        aLine[i] = c;
        i++;
      }
      if (c == 10 && i > 0) {
        aLine[i] = '\0';
        compare(aLine);
        i = 0;
      }
    }
    textFile.close();
    if (i > 0) {
      aLine[i - 1] = '\0';
      compare(aLine);
    }
  }  else {
    Serial.println(F("error opening sequence.txt"));
  }
}


void compare(char * line) {
  int result = -1;
  for (int i = 0; i < noOfEntries; i++) {
    if (strcmp(line, table[i].text) == 0 ) {
      result = i;
      break; // no need to proceed the for
    }
  }
  char buf[80];
  sprintf(buf, "%-25s relates to ", line);
  Serial.print(buf);
  if (result != -1) {
    Serial.println(result);
    table[result].functionPtr();
    //
    // This is where further action can be taken
    //
    // "result" equals the index in the table
  } else {
    Serial.println("-");
    Serial.println(F("Calling nothing .... ;-) "));
    delay(2000);
  }
}
1 Like

It might also help your logic and thought process to realize you are creating an interpreted programming language. A next step would be to create loops in the function name lists.

1 Like

nice! thanks for giving me a different way to build that data structure. I tested it in the simulator and now I can add some more serial.println in spots to better understand what is happening in your loops.

Thanks so much!