How do you get user entered config data to firmware app?

So I am working on my project and I was wondering how people take user input in their web/mobile apps and get that to their particle based device? In my use case the user will need to select several options that will affect how many instances of certain objects I will need, control flow in my FSM and more. Right now I am using defines in a file included by all of my custom classes but I want to make allowances for the device to receive this data from the user. Look forward to seeing/hearing some of your solutions!

I would recommend a struct stored in EEPROM with cloud functions to get and set the values. Make sure you include a version field in the EEPROM struct to be able to migrate the data later when you add fields.

1 Like

I was thinking along those lines but I really appreciate the confirmation that it was a sound approach! When you say version field I get that but migrating in the future isn’t entirely clear. I am thinking it is because when I change the fields in the struct I can’t use EEPROM.update and pass it the updated struct so I would need to get() the struct object, update the fields then put() it back into EEPROM. The version field is there to ensure I am looking at the correct struct since technically the previous version is still in flash unless I overwrite to the same addr. Should I rotate through the address/page range when these changes occur? Would Flashee handle some of those lower level details for me?

I rewrote the EEPROM handling in the firmware so that update() is no longer necessary. You should only need to use get() and put() now. This update will be available in firmware 0.5.0.

You should add new fields at the end of your struct. When you read the larger struct from EEPROM, this will read garbage into the new fields (0xFF if those EEPROM locations were never written before, or data left over from other programs that used EEPROM). The garbage data will be replaced by your default data in the migration step.

Here’s an example of what I recommend.

const int StorageEepromOffset = 0;

struct Storage {
    // To make it easy to add new fields later
    uint8_t version;
    // Data added in version 0
    uint16_t slope;
    uint16_t offset;
    // Data added in version 1
    float latitude;
    float longitude;
    // Never remove any fields or change the data type of existing fields
    // Rename unused fields to unused1, unused2
    // Add new fields at the end and increment version
};

const Storage defaultStorage = {
    1, // the latest version
    2,
    128,
    37.7,
    -122.4
};

Storage storage;

void setup() {
    loadStorage();
}

void loadStorage() {
    EEPROM.get(StorageEepromOffset, storage);
    migrateStorage();
}

void migrateStorage() {
    bool saveNeeded = false;
    
    while(storage.version != defaultStorage.version) {
        saveNeeded = true;
        switch(storage.version) {
        case 0:
            // migrate from version 0 to version 1
            storage.latitude = defaultStorage.latitude;
            storage.longitude = defaultStorage.longitude;
            storage.version = 1;
            break;
            
        // add migrations for later versions here
        
        default:
            // first run -> initialize all fields
            storage = defaultStorage;
            break;
        }
    }
    
    if(saveNeeded) {
        saveStorage();
    }
}

void saveStorage() {
    EEPROM.put(StorageEepromOffset, storage);
}

bool someCondition = false;
void loop() {
    if(someCondition) {
        // update your data and save
        storage.slope = 5;
        saveStorage();
    }
}
2 Likes