Introducing the ParticleRetainedAtomic library

electron
Tags: #<Tag:0x00007fe21f6f50c8>

#1

Hi all,

I created this library to get around issues I encountered when using the retained keyword to store program state:

There is no way to guarantee that updates happen atomically, and it’s very easy to put your program into a bad state if you encounter a crash or reset while in the middle of updating different saved variables.

This library automatically manages two retained memory locations and keeps a sequence number and checksum to transactionally and atomically store the saved data. That is, if a save does not properly complete, it will be rolled back the next time it is loaded (transactional) and either all of the data is committed or none of it is committed (atomic).

I have been using it for a while, but I would definitely still consider it “experimental”. Please submit bug reports if you encounter problems!

Usage:

#include "ParticleRetainedAtomic.h"

// Declare your own persistent state:
typedef struct {
  float lastReportTemperatureC;   // last reported tempererature Celcius
  float lastReportBaroKpa;        // last reported barometric pressure (kPa)
  time_t lastReportTime;          // last reported time (Unix time)
  uint32_t reconnectCount;        // number of reconnection attempts
  bool hasGoodReading;            // a good measurement has been taken
  
} retainedData_t;

const retainedData_t PRAInitVals = {-1000, -1000, 0, 0, false};
retained retainedData_t saveArea1, saveArea2;   // save pages
retained ParticleRetainedAtomicData_t PRAData;  // checksums

ParticleRetainedAtomic<retainedData_t> gAppState(saveArea1,
                                                 saveArea2,
                                                 PRAData,
                                                 PRAInitVals);

void setup() {
  // gAppState already contains either init values or the last saved values
  // You can access these stored values like so:
  time_t lastEventTime = gAppState->lastReportTime;
  // hasGoodReading initializes to false, so we know there is a good reading!
  if (gAppState->hasGoodReading == true) {
    printLastGoodValue(lastEventTime,
                       gAppState->lastReportTemperatureC,
                       gAppState->lastReportBaroKpa);
  }
}

// Update saved state
// Note that if a crash happens in this function,
// the saved state will revert to the last successful save on reboot
void myEvent() {
    // Write new values like so:
    gAppState->lastReportTemperatureC = getTemp();
    gAppState->lastReportBaroKpa = getPres();
    gAppState->lastReportTime = Time.now();
    gAppState->hasGoodReading = true;
    // then commit the changes all at once:
    gAppState.save();
}

As you can see, the values are transparently readable and writeable using the -> operator. This is because the library overrides that operator and substitutes in a pointer to the proper struct at any time, giving you direct access to the members of the struct you defined.

(Note that this is not a hack – the compiler understands what is going on and can check types appropriately with no warnings)

In my opinion, this library can cut down a significant vector of random bugs that are simply untraceable!