I understand. Thank you for pointing this out. I will add that to the places I look when using a new library.
Chip
I understand. Thank you for pointing this out. I will add that to the places I look when using a new library.
Chip
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
Yes, that is a good way to make your settings object available across multiple modules.
you guys rock!
This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.