New library: StorageHelperRK

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.

@rickkas7 ,

I understand. Thank you for pointing this out. I will add that to the places I look when using a new library.

Chip

@rickkas7 ,

Quick question. In your examples, you have the class definition in the main execution file. I was wondering, would it be possible to refactor the implementation using a Singleton approach? That way, the main execution file could be abstracted from the storage technology. I tried to make this work with the code generator you built for Singletons:

I believe this is important as the current implementation will only work if your program is in a single monolithic file. I am working to see if I can make a class out of this that could be called by more than one file. I will share my progress here and if anyone is good at the concepts of Singletons and Classes and could give me any advice, it would be appreciated. If I / we figure it out, I will share the result here:

Thanks, Chip

So, with @jgskarda 's help, we now have the StorageHelperRK wrapped up in a Singleton approach. Would greatly appreciate anyone’s opinions on how to do this better but, by using the library this way, You can move all the storage-technology-specific references from the main CPP file and share access to the persistent storage across multiple modules that make up a project. Here is what the main file looks like now:

#include "Particle.h"
#include "MyPersistentData.h"
#include "Another_Module.h"

SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(MANUAL);

SerialLogHandler logHandler(LOG_LEVEL_TRACE);

void setup() {
    // Optional: Enable to make it easier to see debug USB serial messages at startup
    waitFor(Serial.isConnected, 10000);
    data.setup();                          // loading is part of setup in this approach
    data.logData("after loading");
}

void loop() {
    static unsigned long lastCheck = 0;
    if (millis() - lastCheck >= 10000) {
        lastCheck = millis();

        data.setValue_test1(data.getValue_test1() + 1);     // Change values to see if these are reflected
        data.setValue_test2(!data.getValue_test2());
        data.setValue_test3(data.getValue_test3() - 0.1);
        data.setValue_test4("testing!"); 
        data.logData("after update");

        testStorageAccess();                                // Show we can access storage from another module
    }  
    data.loop();
}

And here are the files that make StorageHelper into a Class:

/* __MyPersistentData_H */
/**
 * @file    MyPersistentData.h
 * @author  JGSkarda - 
 * @brief   All functions that are used to handle persistent data
 * @details Make library into a class that can be shared across modules in a project
 * @date    9/12/22
 * 
 * Version History:
 * 0.1 - Initial realease - Works
 */

//Include a standard header guard
#ifndef __MyPersistentData_H
#define __MyPersistentData_H

//Include standard header files from libraries:
#include <Particle.h>
#include <MB85RC256V-FRAM-RK.h>
#include <StorageHelperRK.h>

//Include other application specific header files
// #include (no other header files required from other singleton classes)

//Define external class instances. These are typically declared public in the main .CPP. I wonder if we can only declare it here?
extern MB85RC256V fram;

//Macros(#define) to swap out during pre-processing (use sparingly). This is typically used outside of this .H and .CPP file within the main .CPP file or other .CPP files that reference this header file. 
// This way you can do "data.setup()" instead of "MyPersistentData::instance().setup()" as an example
#define data MyPersistentData::instance()

/**
 * This class is a singleton; you do not create one as a global, on the stack, or with new.
 * 
 * From global application setup you must call:
 * MyPersistentData::instance().setup();
 * 
 * From global application loop you must call:
 * MyPersistentData::instance().loop();
 */
class MyPersistentData : public StorageHelperRK::PersistentDataFRAM  {
public:
    /**
     * @brief Gets the singleton instance of this class, allocating it if necessary
     * 
     * Use MyPersistentData::instance() to instantiate the singleton.
     */
    static MyPersistentData &instance();

    /**
     * @brief Perform setup operations; call this from global application setup()
     * 
     * You typically use MyPersistentData::instance().setup();
     */
    void setup();

    /**
     * @brief Perform application loop operations; call this from global application loop()
     * 
     * You typically use MyPersistentData::instance().loop();
     */
    void loop();

    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 test1;
		bool test2;
		double test3;
		char test4[10];
		// OK to add more fields here 
	};	
    MyData myData;

    /**
     * @brief Get the value of....
     * 
     * You typically use MyPersistentData2::instance().getValue_test1();
     */
    int getValue_test1() const;

    /**
     * @brief Set the value of....
     * 
     * You typically use MyPersistentData2::instance().setValue_test1;
     */
	void setValue_test1(int value);

    /**
     * @brief Get the value of....
     * 
     * You typically use MyPersistentData2::instance().getValue_test2();
     */
	bool getValue_test2() const;
	
    
    /**
     * @brief Set the value of....
     * 
     * You typically use MyPersistentData2::instance().setValue_test2;
     */
    void setValue_test2(bool value);
	
     /**
     * @brief Get the value of....
     * 
     * You typically use MyPersistentData2::instance().getValue_test3();
     */
    double getValue_test3() const;
	
    /**
     * @brief Set the value of....
     * 
     * You typically use MyPersistentData2::instance().setValue_test3;
     */
    void setValue_test3(double value);

    /**
     * @brief Get the value of....
     * 
     * You typically use MyPersistentData2::instance().getValue_test4();
     */
	String getValue_test4() const;

    /**
     * @brief Set the value of....
     * 
     * You typically use MyPersistentData2::instance().setValue_test4;
     */
	bool setValue_test4(const char *str);
    
    /**
     * @brief Log the data
     * 
     * You typically use MyPersistentData2::instance().logData;
     */
    void logData(const char *msg); 


//Members here are internal only and therefore protected
protected:
    /**
     * @brief The constructor is protected because the class is a singleton
     * 
     * Use MyPersistentData::instance() to instantiate the singleton.
     */
    MyPersistentData();

    /**
     * @brief The destructor is protected because the class is a singleton and cannot be deleted
     */
    virtual ~MyPersistentData();

    /**
     * This class is a singleton and cannot be copied
     */
    MyPersistentData(const MyPersistentData&) = delete;

    /**
     * This class is a singleton and cannot be copied
     */
    MyPersistentData& operator=(const MyPersistentData&) = delete;

    /**
     * @brief Singleton instance of this class
     * 
     * The object pointer to this class is stored here. It's NULL at system boot.
     */
    static MyPersistentData *_instance;

    //Since these variables are only used internally - They can be private. 
    static const uint32_t DATA_MAGIC = 0x20a99e73;
	static const uint16_t DATA_VERSION = 1;

};

#endif  /* __MyPersistentData_H */

and here is the CPP file

#include "Particle.h"
#include "MyPersistentData.h"

MB85RC256V fram(Wire, 0);

MyPersistentData *MyPersistentData::_instance;

// [static]
MyPersistentData &MyPersistentData::instance() {
    if (!_instance) {
        _instance = new MyPersistentData();
    }
    return *_instance;
}

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

MyPersistentData::~MyPersistentData() {
}

void MyPersistentData::setup() {
    fram.begin();
    data.load();
}

void MyPersistentData::loop() {
    data.flush(false);
}

int MyPersistentData::getValue_test1() const 
{
    return getValue<int>(offsetof(MyData, test1));
}


void MyPersistentData::setValue_test1(int value) {
    setValue<int>(offsetof(MyData, test1), value);
}

bool MyPersistentData::getValue_test2() const {
    return getValue<bool>(offsetof(MyData, test2));
}

void MyPersistentData::setValue_test2(bool value) {
    setValue<bool>(offsetof(MyData, test2), value);
}

double MyPersistentData::getValue_test3() const {
    return getValue<double>(offsetof(MyData, test3));
}

void MyPersistentData::setValue_test3(double value) {
    setValue<double>(offsetof(MyData, test3), value);
}

String MyPersistentData::getValue_test4() const {
    String result;
    getValueString(offsetof(MyData, test4), sizeof(MyData::test4), result);
    return result;
}
bool MyPersistentData::setValue_test4(const char *str) {
    return setValueString(offsetof(MyData, test4), sizeof(MyData::test4), str);
}

void MyPersistentData::logData(const char *msg) {
    Log.info("%s: %d, %d, %lf, %s", msg, myData.test1, (int)myData.test2, myData.test3, myData.test4);
}

To test whether we should share storage access with another file, I make this simple module that would access storage and was called from the main file

#ifndef ANOTHER_MODULE_H
#define ANOTHER_MODULE_H

#include "Particle.h"

void testStorageAccess();
#endif

and the test function

#include "Particle.h"
#include "Another_Module.h"
#include "MyPersistentData.h"


void testStorageAccess() {
    data.logData("From another module:");

}

Again, @jgskarda and I are just getting started on this approach. Any comments / suggestions would be appreciated. I would be especially interested if @rickkas7 would agree this is a reasonable use of the StorageHelperRK library.

Thanks, Chip

2 Likes

Yes, that is a good way to make your settings object available across multiple modules.

2 Likes

you guys rock!

1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.