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