New library: StorageHelperRK

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.

6 Likes

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!

3 Likes

@rickkas7 ,

This is a great resource and complement to sleepHelper - thank you for your amazing work.

Two questions:

  1. 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

  2. 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  {

Can you pleas tell me what I am missing?

Thanks,

Chip

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.

#include "MB85RC256V-FRAM-RK.h"
#include "StorageHelperRK.h"

@rickkas7

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:

static const uint32_t DATA_MAGIC = 0x20a99e74;
	static const uint16_t DATA_VERSION = 1;

	MyPersistentData() : PersistentDataFRAM(fram, FRAM_OFFSET, &myData.header, sizeof(MyData), DATA_MAGIC, DATA_VERSION) {};

The warning is:

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:

When I flash my device with this code, I get a Red LED SOS - Hard Fault.

Thanks,

Chip

Bump. Anyone had a chance to try this library with FRAM?

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.

I put the save into the spot where I set info.

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.

I had no delay and had just put that in to try something. This device does not have any other eeprom use. The eeprom offset is set to 0 right now.

I am using system threading and these other library’s.

#include <HttpClient.h>
#include <SparkFunMAX17043.h>
#include <PublishQueueAsyncRK.h>
#include <JsonParserGeneratorRK.h>
#include <StorageHelperRK.h>

Where is PublishQueueAsyncRK saving to?

Tested with 2.0.1 on the Photon and there is a bug. I’m looking into it now.

Just a retained buffer only.

There’s a new StorageHelperRK version 0.0.2 available that should fix the problem with EEPROM not saving.

1 Like

Looks like its working on my test case right now.

You are awesome.

@darkstar2002 or @rickkas7

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());
    }  
}

Thanks, Chip

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.

The easiest test is to change this line to:

MyPersistentData() : PersistentDataFRAM(::fram, OFFSET, &myData.header, sizeof(MyData), DATA_MAGIC, DATA_VERSION) {};

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.

1 Like

@rickkas7 ,

That fixed it. Thank you! I am looking forward to putting this new library to work.

Chip

P.S. - you mentioned “more-example/53-fram” in your note. Where might I find that as I don’t see it in the libraries directory structure.

Screen Shot 2022-09-08 at 8.32.23 AM

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.