Skip to content
This repository has been archived by the owner on Feb 8, 2024. It is now read-only.

Add callback to config save #77

Closed
arjena opened this issue Feb 6, 2021 · 6 comments · Fixed by #83
Closed

Add callback to config save #77

arjena opened this issue Feb 6, 2021 · 6 comments · Fixed by #83

Comments

@arjena
Copy link

arjena commented Feb 6, 2021

Not an issue, but I'm not an experienced Github user, so I don't know where to put this. Maybe it is a pull-request, but I don't know how that works...
I needed a way to know if any of the config values has changed, so i can combine that with my MQTT calls. So I added a function to configManager.
My configManager.cpp now looks like this:

#include <EEPROM.h>
#include <Arduino.h>

#include "configManager.h"

//class functions
bool config::begin(int numBytes)
{
    EEPROM.begin(numBytes);

    uint32_t storedVersion;
    uint8_t checksumData = 0;
    uint8_t checksumInternal = 0;

    EEPROM.get(0, internal);
    EEPROM.get(SIZE_INTERNAL, checksumInternal);
    EEPROM.get(SIZE_INTERNAL + 1, storedVersion);
    EEPROM.get(SIZE_INTERNAL + 5, data);
    EEPROM.get(SIZE_INTERNAL + 5 + sizeof(data), checksumData);        

    bool returnValue = true;

    //reset configuration data if checksum mismatch
    if (checksumData != checksum(reinterpret_cast<uint8_t*>(&data), sizeof(data)) || storedVersion != configVersion)
    {
        Serial.println(PSTR("Config data checksum mismatch"));
        reset();
        returnValue = false;
    }

    //reset internal data if checksum mismatch
    if (checksumInternal != checksum(reinterpret_cast<uint8_t*>(&internal), sizeof(internal)))
    {
        Serial.println(PSTR("Internal data checksum mismatch"));
        internal = internalData();
        save();
        returnValue = false;
    }

    return returnValue;        
}

void config::reset()
{
    memcpy_P(&data, &defaults, sizeof(data));
    save();
}

void config::saveRaw(uint8_t bytes[])
{
    memcpy(&data,bytes,sizeof(data));
    save();
}

void config::saveExternal(configData *extData)
{
    memcpy(&data, extData, sizeof(data));
    save();
}

void config::setConfigSaveCallback( std::function<void()> func ) {
  _configsavecallback = func;
}


void config::save()
{
    EEPROM.put(0, internal);

    //save checksum for internal data
    EEPROM.put(SIZE_INTERNAL, checksum(reinterpret_cast<uint8_t*>(&internal), sizeof(internal)));

    EEPROM.put(SIZE_INTERNAL + 1, configVersion);
    EEPROM.put(SIZE_INTERNAL + 5, data);

    //save checksum for configuration data
    EEPROM.put(SIZE_INTERNAL + 5 + sizeof(data), checksum(reinterpret_cast<uint8_t*>(&data), sizeof(data)));
    
    EEPROM.commit();
    
    if ( _configsavecallback != NULL) {
        _configsavecallback();
    }
}

uint8_t config::checksum(uint8_t *byteArray, unsigned long length)
{
    uint8_t value = 0;
    unsigned long counter;

    for (counter=0; counter<length; counter++)
    {
        value += *byteArray;
        byteArray++;
    }

    return (uint8_t)(256-value);

}

config configManager;

And my configManager.h:

#ifndef CONFIGMGR_H
#define CONFIGMGR_H

#include "IPAddress.h"
#include "generated/config.h"

//data that needs to be persisted for other parts of the framework

#define SIZE_INTERNAL 32 //allocate 32 bytes to have room for future expansion

struct internalData
{
	uint32_t ip;
	uint32_t gw;
	uint32_t sub;
	uint32_t dns;
};

class config
{

public:
	configData data;
	internalData internal;
	bool begin(int numBytes = 512);
	void saveRaw(uint8_t bytes[]);
	void saveExternal(configData *extData);
	void save();
	void reset();
	void setConfigSaveCallback( std::function<void()> func );

private:
	uint8_t checksum(uint8_t *byteArray, unsigned long length);
	std::function<void()> _configsavecallback;
};

extern config configManager;

#endif

So now you can define a function to be called in setup() when the 'Save' button is clicked:

configManager.setConfigSaveCallback(saveCallback);

@clabnet
Copy link

clabnet commented Feb 6, 2021

Cool, can you add please an example to call it?
Thanks

@arjena
Copy link
Author

arjena commented Feb 6, 2021

It's very straightforward actually. First, you create a function with what you want to do when the 'Save' button is clicked. The simplest example:

void saveCallback() {
    Serial.println("save button clicked, saveCallback called"); 
}

Then in setup() you set configManager.setConfigSaveCallback(saveCallback);
And that's it. You can do anything in the saveCallback function, like setting variables from config values or sending MQTT messages with the new config settings. I'm still working on the MQTT stuff, but my project is not nearly finished so I cannot show a ready example yet.

@arjena
Copy link
Author

arjena commented Feb 6, 2021

By the way, even though I mention you can do anything in the saveCallback function, it is best to just set a flag there and leave the actual work to be done in loop(). I found my esp going into panic for no apparent (to me at least) reason when doing things like writing to a display and such. No idea why, but when I put the same code in loop() just to be executed when the flag is set by saveCallback() all is fine. Just goes to show I'm a designer, not a programmer...

@arjena
Copy link
Author

arjena commented Feb 6, 2021

I get the esp hard-reboot (panic) when I click the save button a second time too soon (within a second or so) without changing the data. I thought maybe writing to EEPROM takes too much time and that causes a conflict, so I tried a delay(500); between EEPROM.commit() and if ( _configsave... but that does not work because we are in interrupt. So I put a 'fake' delay there (and put my _configsavecallback in the 'if' routine, since commit() is a bool):

    if (EEPROM.commit()){
		for (int d = 0; d<200000; d++){}
		if ( _configsavecallback != NULL) {
			_configsavecallback();
		}
    }

Not the best code I admit, but I don't know how else to give EEPROM.commit() a little time to finish (I still think that is the cause of the panic, but I could be very wrong).
So far so good, no more panics. If anybody who knows a better solution please chime in!

@maakbaas maakbaas linked a pull request Feb 17, 2021 that will close this issue
@maakbaas
Copy link
Owner

maakbaas commented Feb 17, 2021

Thanks for proposing this functionality.

I think the panics are caused by the fact that the saveRaw function is called from within the webserver callback. Increasing the execution time of these callbacks can indeed result in panics.

I have implemented your callback in PR #83, and in addition I have made the save method asynchronous by adding a config loop() function. In this way the save is decoupled from the webserver callbacks, and I have personally not yet observed any panics. I am curious if it works for you as well.

Thanks again for your suggestion and code. This will be added to the next release.

@arjena
Copy link
Author

arjena commented Feb 17, 2021

Thanks for implementing my crude code-idea into your wonderful project. I will try your new code as soon as possible. Hopefully some time this week, but I will let you know what happens.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants