Rather than use EEPROM, which is a real pain considering that it's shared between all stored data and you need to remember offsets and lengths, use SPIFFS.
By including FS.h
in your program (or the include file that needs to store the data, if you are trying to be modular), you can store several megabytes of data as strings in a named file (much like fprintf() or open() would let you do in C++).
Call SPIFFS.begin();
in setup(), and then use the following sections of code to read or write data to the file:
(assuming you want to store start
and end
in SPIFFS, but this works for any datatype known to Arduino, and probably anything with the printable attribute)
Read saved config:
Serial.println(F("Loading config"));
File f = SPIFFS.open("/ACTIVE_TIMES.cnf", "r");
if (!f) {
//File does not exist -- first run or someone called format()
//Will not create file; run save code to actually do so (no need here since
//it's not changed)
Serial.println(F("Failed to open config file"));
start = 8;
end = 20; // defaults
return;
}
while (f.available()) {
String key = f.readStringUntil('=');
String value = f.readStringUntil('\r');
f.read();
Serial.println(key + F(" = [") + value + ']');
Serial.println(key.length());
if (key == F("START_TIME")) {
start = value.toInt();
}
if (key == F("END_TIME")) {
end = value.toInt();
}
}
f.close();
Save configuration:
Serial.println(F("Saving config"));
File f = SPIFFS.open("/ACTIVE_TIMES.cnf", "w+");
if (!f) {
//It's cases like this when calling SPIFFS.format() might be a good idea,
//but you would not want to do so here, since if it's an error for a
//different reason it would delete all your data. Make it manual or a prompt
//instead.
Serial.println(F("Failed to open config file"));
return;
}
f.print(F("START_TIME="));
f.println(start);
f.print(F("END_TIME="));
f.println(end);
f.flush();
f.close();
Serial.println(F("Saved values"));
You may also call SPIFFS.format()
to reset the filesystem (say, for a factory reset), but this will cause issues on attempting to read any stored data, since the file will not exist. Thus, have a section that sets reasonable defaults (or just prompts the user to set the values).
Note: Since this is for storing an API key and such, just have it fail, since the API key not working is a good notice the user needs to configure it, and you do NOT want to ship a product with a working API key that belongs to you rather than the user.
Final warning: Note carefully the use of F()
around certain items in that code. This macro, specific to Arduino, allows one to store data in Flash ONLY, where strings normally consume RAM as well (once the system boots, it loads to RAM, since most code expects it there). The benefit of F()
, rather than a progmem directive, is that many functions native to Arduino can read it directly thanks to overloads, or you can wrap String()
around the F()
to convert it to a string at runtime if the function does not allow that overload. The end result is that, as I've written it, this code should consume RAM only when it is actually running, as good code should (otherwise, it's really easy to use up all your RAM on such embedded devices if you add enough strings).
I have not bothered to do the latter with the filename passed to open()
since it is used only once per load or save operation, does not support F() (thus I'd need to use the String()
trick), and I'm fairly sure the compiler then shares the memory location, reducing the RAM used to only one edition of that filename.