New library: StorageHelperRK

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