This library is designed to simplify a a common task: Storing a struct of persistent data in the Gen 3 POSIX file system, retained memory, or EEPROM. It also works with SdFat (SD cards), SPIFFS (SPI flash), and FRAM.
It supports all C/C++ primitive types and C strings (null-terminated) up to a maximum length defined in the structure. It includes change detection, deferred saves (to reduce flash wear), and is extensible to store data on other types of file systems or storage methods. Validity checking includes magic bytes, version numbers, and a saved hash to determine if the data has been corrupted.
You can add values to the structure later and still maintain compatibility; new fields will be zeroed out if the new firmware has a bigger structure than the one that was saved.
It also includes a simple file system abstraction for reading and writing files on POSIX, SdFat, or SPIFFS.
There seems to be no end in sight for the phrase “this keeps getting better and better”.
I’m happy.
All the little and big things the Particle team and you keep adding make our work so much easier and enjoyable with the platform.
Thank you, Mr RK!
This is a great resource and complement to sleepHelper - thank you for your amazing work.
Two questions:
Since this code is refactored out of sleepHelper, could you please push the new v0.0.4 to the Particle Library system. I see it on GitHub but when I install sleepHelper, it is still v0.0.3
I am struggling a bit with implementing FRAM storage which I seems that storageHelper supports. Here is what I have done:
Started with your example code- 04-persistent.cpp and verified it compiles
Installed and included MBRC85RC256V-FRAM-RK
instantiated the FRAM using "MB85RC64 fram(Wire, 0); "
changed one line of code as outlined in the instructions:
class MyPersistentData : public StorageHelperRK::PersistentDataFRAM {
I expected some errors but, it seems the FRAM class is not recognized. Here is the compile error:
Creating /Users/chipmc/Documents/Maker/Particle/Utilities/StorageHelper-Demo-FRAM/target/2.3.0/boron/platform_user_ram.ld ...
/Users/chipmc/Documents/Maker/Particle/Utilities/StorageHelper-Demo-FRAM//src/StorageHelper-Demo-FRAM.cpp:22:70: error: expected class-name before '{' token
22 | class MyPersistentData : public StorageHelperRK::PersistentDataFRAM {
I fixed the Particle library upload of SleepHelper. I forgot to make that public. I just fixed it.
Make sure you put the includes in the right order. The FRAM include must be before StorageHelperRK or anything that includes it like SleepHelper 0.0.4.
Thank you for the update to the sleepHelper library and the clue on the order of includes - it was clearly called out in your header file - sorry I missed it.
I am trying to get this working and am running into a compile warning (it does compile) on this line:
Creating /Users/chipmc/Documents/Maker/Particle/Utilities/StorageHelper-Demo-FRAM/target/2.3.0/boron/platform_user_ram.ld ...
D.116051.fram'/Users/chipmc/Documents/Maker/Particle/Utilities/StorageHelper-Demo-FRAM//src/StorageHelper-Demo-FRAM.cpp: In member function 'MyPersistentData::MyPersistentData()':
/Users/chipmc/Documents/Maker/Particle/Utilities/StorageHelper-Demo-FRAM//src/StorageHelper-Demo-FRAM.cpp:40:117: warning: is used uninitialized in this function [-Wuninitialized]
40 | MyPersistentData() : PersistentDataFRAM(fram, FRAM_OFFSET, &myData.header, sizeof(MyData), DATA_MAGIC, DATA_VERSION) {};
Not sure why it is saying that the version is uninitialized when I thought that was taken care of in the line above the call. As you can see from the tool tip, the value of the version number is being seen:
Trying this on a photon running 2.0.1 for testing. Using the eeprom options I can not get this to save. It works as long you do not reboot. I am using the example slimmed down to 2 items with their own names. I call a read and right out of a particle function for testing.
I am using VS Code and pulled in the library from install library button.
This photon is sortof being used for something so a plain vanilla flash will not really work.
Can you share your code? Did you add this to your loop() function?
// Save any data if necessary
persistentData.flush(false);
By default EEPROM does deferred writes from loop, so if you don’t add that call, nothing will be saved. You can change this by using withSaveDelayMs(0) which saves changes immediately.
class MyPersistentData : public StorageHelperRK::PersistentDataEEPROM {
public:
class MyData {
public:
// This structure must always begin with the header (16 bytes)
StorageHelperRK::PersistentDataBase::SavedDataHeader header;
// Your fields go here. Once you've added a field you cannot add fields
// (except at the end), insert fields, remove fields, change size of a field.
// Doing so will cause the data to be corrupted!
// You may want to keep a version number in your data.
int id;
char location[20];
// OK to add more fields here
};
static const uint32_t DATA_MAGIC = 0x21b99b75;
static const uint16_t DATA_VERSION = 1;
MyPersistentData() : PersistentDataEEPROM(EEPROM_OFFSET, &myData.header, sizeof(MyData), DATA_MAGIC, DATA_VERSION) {};
int getValue_id() const {
return getValue<int>(offsetof(MyData, id));
}
void setValue_id(int value) {
setValue<int>(offsetof(MyData, id), value);
}
String getValue_location() const {
String result;
getValueString(offsetof(MyData, location), sizeof(MyData::location), result);
return result;
}
bool setValue_location(const char *str) {
return setValueString(offsetof(MyData, location), sizeof(MyData::location), str);
}
MyData myData;
};
MyPersistentData persistentData;
//setup
persistentData
.withSaveDelayMs(1000)
.setup();
//read / write
else if (command =="getdata"){
JsonWriterStatic<256> jw;{
JsonWriterAutoObject obj(&jw);
jw.insertKeyValue("id", persistentData.getValue_id());
jw.insertKeyValue("location",persistentData.getValue_location());
}
Particle.publish("getdata",jw.getBuffer());
return 0;
}
else if (command =="putdata"){
persistentData.setValue_id(persistentData.getValue_id() + 1);
persistentData.setValue_location("hi there");
delay(1100);
persistentData.flush(true);
return 1;
}
There’s no need for a delay if passing true to flush(). Are you storing anything else in EEPROM elsewhere in your code? If the regions overlap at all the data won’t be restored. StorageHelper will use 16 bytes for the header + 24 bytes of data in your structure so 40 bytes starting at EEPROM_OFFSET.
Any chance you could take a look? I have tried this with EEPROM, Flash with no issues. But when I use this library with FRAM, I get the “uninitialized” compilation error and the Red Flashing hard fault.
Can someone please tell me what I am doing wrong?
#include "MB85RC256V-FRAM-RK.h"
#include "StorageHelperRK.h"
MB85RC64 fram(Wire, 0);
SerialLogHandler logHandler(LOG_LEVEL_INFO);
SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(MANUAL);
class MyPersistentData : public StorageHelperRK::PersistentDataFRAM {
//class MyPersistentData : public StorageHelperRK::PersistentDataEEPROM {
//class MyPersistentData : public StorageHelperRK::PersistentDataFile {
public:
class MyData {
public:
// This structure must always begin with the header (16 bytes)
StorageHelperRK::PersistentDataBase::SavedDataHeader header;
// Your fields go here. Once you've added a field you cannot add fields
// (except at the end), insert fields, remove fields, change size of a field.
// Doing so will cause the data to be corrupted!
// You may want to keep a version number in your data.
int id;
char location[20];
// OK to add more fields here
};
static const uint32_t DATA_MAGIC = 0x20a99e74;
static const uint16_t DATA_VERSION = 1;
static const int OFFSET = 1;
//MyPersistentData() : PersistentDataFile(persistentDataPath, &myData.header, sizeof(MyData), DATA_MAGIC, DATA_VERSION) {};
MyPersistentData() : PersistentDataFRAM(fram, OFFSET, &myData.header, sizeof(MyData), DATA_MAGIC, DATA_VERSION) {};
//MyPersistentData() : PersistentDataEEPROM(OFFSET, &myData.header, sizeof(MyData), sizeof(MyData), DATA_VERSION) {};
// MyPersistentData() : PersistentDataFRAM(fram, OFFSET, &myData.header, sizeof(MyData), sizeof(MyData), DATA_VERSION) {};
int getValue_id() const {
return getValue<int>(offsetof(MyData, id));
}
void setValue_id(int value) {
setValue<int>(offsetof(MyData, id), value);
}
String getValue_location() const {
String result;
getValueString(offsetof(MyData, location), sizeof(MyData::location), result);
return result;
}
bool setValue_location(const char *str) {
return setValueString(offsetof(MyData, location), sizeof(MyData::location), str);
}
MyData myData;
};
MyPersistentData persistentData;
void setup() {
fram.begin(); // Initialize the FRAM module
persistentData.setup(); // Load the persistent data
}
void loop() {
static unsigned long lastCheck = 0;
if (millis() - lastCheck >= 10000) {
lastCheck = millis();
persistentData.setValue_id(persistentData.getValue_id() + 1);
persistentData.setValue_location("hi there");
persistentData.flush(true);
Log.info("Value is: %s", persistentData.getValue_location().c_str());
}
}
I believe it’s because the StorageHelperRK::PersistentDataFRAM class has a protected member variable named fram. For some reason, your code is picking up the member variable instead of your global variable, which does not seem to happen with more-example/53-fram.
Note the addition of the :: before fram. This will make sure the global variable is picked up instead of the member variable of the same name. If that fixes it I can either make the member variable private or change the name to reduce confusion in the future.
It’s only in the Github. The problem is that the more-examples require a separate project.properties for each example because of their additional dependencies. Uploading them in the Particle library upload can cause issues. Same for libraries with an automated-test module.