diff --git a/src/CHANGES.md b/src/CHANGES.md index 131e891d..4c1346cb 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -3,8 +3,12 @@ (starting from release version `0.5.66`) ## 0.5.102 +* Warning: old exports are not compatible any more! * fix JSON import #775 * fix save settings, at least already stored settings are not lost #771 +* further save settings improvements (only store inverters which are existing) +* improved display of settings save return value +* made save settings asynchronous (more heap memory is free) ## 0.5.101 * fix SSD1306 diff --git a/src/app.cpp b/src/app.cpp index a69d73a1..23030d76 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -374,6 +374,8 @@ void app::resetSystem(void) { mSendLastIvId = 0; mShowRebootRequest = false; mIVCommunicationOn = true; + mSavePending = false; + mSaveReboot = false; memset(&mStat, 0, sizeof(statistics_t)); } diff --git a/src/app.h b/src/app.h index a792ffe8..afd90565 100644 --- a/src/app.h +++ b/src/app.h @@ -68,9 +68,12 @@ class app : public IApp, public ah::Scheduler { return Scheduler::getTimestamp(); } - bool saveSettings(bool stopFs = false) { + bool saveSettings(bool reboot) { mShowRebootRequest = true; // only message on index, no reboot - return mSettings.saveSettings(stopFs); + mSavePending = true; + mSaveReboot = reboot; + once(std::bind(&app::tickSave, this), 2, "save"); + return true; } bool readSettings(const char *path) { @@ -81,6 +84,14 @@ class app : public IApp, public ah::Scheduler { return mSettings.eraseSettings(eraseWifi); } + bool getSavePending() { + return mSavePending; + } + + bool getLastSaveSucceed() { + return mSettings.getLastSaveSucceed(); + } + statistics_t *getStatistics() { return &mStat; } @@ -214,6 +225,14 @@ class app : public IApp, public ah::Scheduler { ESP.restart(); } + void tickSave(void) { + mSettings.saveSettings(); + mSavePending = false; + + if(mSaveReboot) + once(std::bind(&app::tickReboot, this), 2, "rboot"); + } + void tickNtpUpdate(void); void tickCalcSunrise(void); void tickIVCommunication(void); @@ -253,6 +272,8 @@ class app : public IApp, public ah::Scheduler { char mVersion[12]; settings mSettings; settings_t *mConfig; + bool mSavePending; + bool mSaveReboot; uint8_t mSendLastIvId; bool mSendFirst; diff --git a/src/appInterface.h b/src/appInterface.h index 4c7d6042..a79dcdb1 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -17,6 +17,8 @@ class IApp { virtual bool saveSettings(bool stopFs) = 0; virtual bool readSettings(const char *path) = 0; virtual bool eraseSettings(bool eraseWifi) = 0; + virtual bool getSavePending() = 0; + virtual bool getLastSaveSucceed() = 0; virtual void setOnUpdate() = 0; virtual void setRebootFlag() = 0; virtual const char *getVersion() = 0; diff --git a/src/config/settings.h b/src/config/settings.h index 29974862..24bb6a9e 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -15,9 +15,9 @@ #include "../utils/helper.h" #if defined(ESP32) - #define MAX_ALLOWED_BUF_SIZE ESP.getMaxAllocHeap() - 2048 + #define MAX_ALLOWED_BUF_SIZE ESP.getMaxAllocHeap() - 1024 #else - #define MAX_ALLOWED_BUF_SIZE ESP.getMaxFreeBlockSize() - 2048 + #define MAX_ALLOWED_BUF_SIZE ESP.getMaxFreeBlockSize() - 1024 #endif /** @@ -161,7 +161,9 @@ typedef struct { class settings { public: - settings() {} + settings() { + mLastSaveSucceed = false; + } void setup() { DPRINTLN(DBG_INFO, F("Initializing FS ..")); @@ -208,6 +210,10 @@ class settings { return mCfg.valid; } + inline bool getLastSaveSucceed() { + return mLastSaveSucceed; + } + void getInfo(uint32_t *used, uint32_t *size) { #if !defined(ESP32) FSInfo info; @@ -254,7 +260,7 @@ class settings { return mCfg.valid; } - bool saveSettings(bool stopFs = false) { + bool saveSettings() { DPRINTLN(DBG_DEBUG, F("save settings")); DynamicJsonDocument json(MAX_ALLOWED_BUF_SIZE); @@ -269,31 +275,35 @@ class settings { jsonPlugin(root.createNestedObject(F("plugin")), true); jsonInst(root.createNestedObject(F("inst")), true); - DPRINT(DBG_INFO, "memory usage: "); + DPRINT(DBG_INFO, F("memory usage: ")); DBGPRINTLN(String(json.memoryUsage())); + DPRINT(DBG_INFO, F("capacity: ")); + DBGPRINTLN(String(json.capacity())); + DPRINT(DBG_INFO, F("max alloc: ")); + DBGPRINTLN(String(MAX_ALLOWED_BUF_SIZE)); if(json.overflowed()) { DPRINTLN(DBG_ERROR, F("buffer too small!")); + mLastSaveSucceed = false; return false; } File fp = LittleFS.open("/settings.json", "w"); if(!fp) { DPRINTLN(DBG_ERROR, F("can't open settings file!")); + mLastSaveSucceed = false; return false; } if(0 == serializeJson(root, fp)) { DPRINTLN(DBG_ERROR, F("can't write settings file!")); + mLastSaveSucceed = false; return false; } fp.close(); - DPRINTLN(DBG_INFO, F("settings saved")); - if(stopFs) - stop(); - + mLastSaveSucceed = true; return true; } @@ -301,7 +311,7 @@ class settings { if(true == eraseWifi) return LittleFS.format(); loadDefaults(!eraseWifi); - return saveSettings(true); + return saveSettings(); } private: @@ -549,36 +559,41 @@ class settings { if(set) ivArr = obj.createNestedArray(F("iv")); for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - if(set) - jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true); - else - jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]); + if(set) { + if(mCfg.inst.iv[i].serial.u64 != 0ULL) + jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true); + } + else { + if(!obj[F("iv")][i].isNull()) + jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]); + } } } void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) { if(set) { - obj[F("en")] = (bool)cfg->enabled; - obj[F("name")] = cfg->name; - obj[F("sn")] = cfg->serial.u64; + obj[F("en")] = (bool)cfg->enabled; + obj[F("name")] = cfg->name; + obj[F("sn")] = cfg->serial.u64; for(uint8_t i = 0; i < 4; i++) { - obj[F("yield")][i] = cfg->yieldCor[i]; + obj[F("yc")][i] = cfg->yieldCor[i]; obj[F("pwr")][i] = cfg->chMaxPwr[i]; - obj[F("chName")][i] = cfg->chName[i]; + obj[F("chn")][i] = cfg->chName[i]; } } else { cfg->enabled = (bool)obj[F("en")]; snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as()); cfg->serial.u64 = obj[F("sn")]; for(uint8_t i = 0; i < 4; i++) { - cfg->yieldCor[i] = obj[F("yield")][i]; + cfg->yieldCor[i] = obj[F("yc")][i]; cfg->chMaxPwr[i] = obj[F("pwr")][i]; - snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); + snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chn")][i].as()); } } } settings_t mCfg; + bool mLastSaveSucceed; }; #endif /*__SETTINGS_H__*/ diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 9b14e439..ecabd1f4 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -80,6 +80,7 @@ class RestApi { if(path == "html/system") getHtmlSystem(root); else if(path == "html/logout") getHtmlLogout(root); else if(path == "html/save") getHtmlSave(root); + else if(path == "html/chk_save") getHtmlChkSave(root); else if(path == "system") getSysInfo(root); else if(path == "generic") getGeneric(root); else if(path == "reboot") getReboot(root); @@ -266,9 +267,19 @@ class RestApi { void getHtmlSave(JsonObject obj) { getGeneric(obj.createNestedObject(F("generic"))); - obj[F("refresh")] = 2; - obj[F("refresh_url")] = "/setup"; - obj[F("html")] = F("settings succesfully save"); + obj[F("refresh")] = 1; + obj[F("refresh_url")] = F("/chk_save"); + obj[F("html")] = F("saving settings ..."); + } + + void getHtmlChkSave(JsonObject obj) { + getGeneric(obj.createNestedObject(F("generic"))); + obj[F("refresh")] = (mApp->getLastSaveSucceed()) ? 10 : 1; + obj[F("refresh_url")] = mApp->getSavePending() ? F("/chk_save") : F("/setup"); + if(mApp->getSavePending()) + obj[F("html")] = F("saving settings ..."); + else + obj[F("html")] = mApp->getLastSaveSucceed() ? F("settings succesfully saved") : F("failed saving settings"); } void getReboot(JsonObject obj) { diff --git a/src/web/html/index.html b/src/web/html/index.html index 27a46a9c..72537e5e 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -102,12 +102,12 @@ if(obj["disNightComm"]) { if(((obj["ts_sunrise"] - obj["ts_offset"]) < obj["ts_now"]) && ((obj["ts_sunset"] + obj["ts_offset"]) > obj["ts_now"])) { - commInfo = "Polling inverter(s), will stop at sunset " + (new Date((obj["ts_sunset"] + obj["ts_offset"]) * 1000).toLocaleString('de-DE')); + commInfo = "Polling inverter(s), will pause at sunset " + (new Date((obj["ts_sunset"] + obj["ts_offset"]) * 1000).toLocaleString('de-DE')); } else { commInfo = "Night time, inverter polling disabled, "; if(obj["ts_now"] > (obj["ts_sunrise"] - obj["ts_offset"])) { - commInfo += "stopped at " + (new Date((obj["ts_sunset"] + obj["ts_offset"]) * 1000).toLocaleString('de-DE')); + commInfo += "paused at " + (new Date((obj["ts_sunset"] + obj["ts_offset"]) * 1000).toLocaleString('de-DE')); } else { commInfo += "will start polling at " + (new Date((obj["ts_sunrise"] - obj["ts_offset"]) * 1000).toLocaleString('de-DE')); diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 58144219..93841461 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -157,7 +157,7 @@
-
Reset values when inverter polling stops at sunset
+
Reset values when inverter polling pauses at sunset
@@ -209,7 +209,7 @@
-
Stop polling inverters during night
+
Pause polling inverters during night
@@ -291,8 +291,8 @@
Import / Export JSON Settings
-
Import
-
+
Import
+
@@ -300,8 +300,8 @@
-
Export
-
+
Export
+
Export settings (JSON file) (only values, passwords will be removed!)
diff --git a/src/web/web.h b/src/web/web.h index 814259b8..c1726f87 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -68,6 +68,7 @@ class Web { mWeb.on("/setup", HTTP_GET, std::bind(&Web::onSetup, this, std::placeholders::_1)); mWeb.on("/save", HTTP_ANY, std::bind(&Web::showSave, this, std::placeholders::_1)); + mWeb.on("/chk_save", HTTP_ANY, std::bind(&Web::onCheckSave, this, std::placeholders::_1)); mWeb.on("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1)); //mWeb.on("/api1", HTTP_POST, std::bind(&Web::showWebApi, this, std::placeholders::_1)); @@ -592,13 +593,15 @@ class Web { mApp->saveSettings((request->arg("reboot") == "on")); - if (request->arg("reboot") == "on") - onReboot(request); - else { - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void onCheckSave(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } void onLive(AsyncWebServerRequest *request) {