Browse Source

0.5.102

Warning: old exports are not compatible any more!
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)
pull/788/head
lumapu 2 years ago
parent
commit
57bda044e5
  1. 4
      src/CHANGES.md
  2. 2
      src/app.cpp
  3. 25
      src/app.h
  4. 2
      src/appInterface.h
  5. 57
      src/config/settings.h
  6. 17
      src/web/RestApi.h
  7. 4
      src/web/html/index.html
  8. 12
      src/web/html/setup.html
  9. 17
      src/web/web.h

4
src/CHANGES.md

@ -3,8 +3,12 @@
(starting from release version `0.5.66`) (starting from release version `0.5.66`)
## 0.5.102 ## 0.5.102
* Warning: old exports are not compatible any more!
* fix JSON import #775 * fix JSON import #775
* fix save settings, at least already stored settings are not lost #771 * 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 ## 0.5.101
* fix SSD1306 * fix SSD1306

2
src/app.cpp

@ -374,6 +374,8 @@ void app::resetSystem(void) {
mSendLastIvId = 0; mSendLastIvId = 0;
mShowRebootRequest = false; mShowRebootRequest = false;
mIVCommunicationOn = true; mIVCommunicationOn = true;
mSavePending = false;
mSaveReboot = false;
memset(&mStat, 0, sizeof(statistics_t)); memset(&mStat, 0, sizeof(statistics_t));
} }

25
src/app.h

@ -68,9 +68,12 @@ class app : public IApp, public ah::Scheduler {
return Scheduler::getTimestamp(); return Scheduler::getTimestamp();
} }
bool saveSettings(bool stopFs = false) { bool saveSettings(bool reboot) {
mShowRebootRequest = true; // only message on index, no 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) { bool readSettings(const char *path) {
@ -81,6 +84,14 @@ class app : public IApp, public ah::Scheduler {
return mSettings.eraseSettings(eraseWifi); return mSettings.eraseSettings(eraseWifi);
} }
bool getSavePending() {
return mSavePending;
}
bool getLastSaveSucceed() {
return mSettings.getLastSaveSucceed();
}
statistics_t *getStatistics() { statistics_t *getStatistics() {
return &mStat; return &mStat;
} }
@ -214,6 +225,14 @@ class app : public IApp, public ah::Scheduler {
ESP.restart(); ESP.restart();
} }
void tickSave(void) {
mSettings.saveSettings();
mSavePending = false;
if(mSaveReboot)
once(std::bind(&app::tickReboot, this), 2, "rboot");
}
void tickNtpUpdate(void); void tickNtpUpdate(void);
void tickCalcSunrise(void); void tickCalcSunrise(void);
void tickIVCommunication(void); void tickIVCommunication(void);
@ -253,6 +272,8 @@ class app : public IApp, public ah::Scheduler {
char mVersion[12]; char mVersion[12];
settings mSettings; settings mSettings;
settings_t *mConfig; settings_t *mConfig;
bool mSavePending;
bool mSaveReboot;
uint8_t mSendLastIvId; uint8_t mSendLastIvId;
bool mSendFirst; bool mSendFirst;

2
src/appInterface.h

@ -17,6 +17,8 @@ class IApp {
virtual bool saveSettings(bool stopFs) = 0; virtual bool saveSettings(bool stopFs) = 0;
virtual bool readSettings(const char *path) = 0; virtual bool readSettings(const char *path) = 0;
virtual bool eraseSettings(bool eraseWifi) = 0; virtual bool eraseSettings(bool eraseWifi) = 0;
virtual bool getSavePending() = 0;
virtual bool getLastSaveSucceed() = 0;
virtual void setOnUpdate() = 0; virtual void setOnUpdate() = 0;
virtual void setRebootFlag() = 0; virtual void setRebootFlag() = 0;
virtual const char *getVersion() = 0; virtual const char *getVersion() = 0;

57
src/config/settings.h

@ -15,9 +15,9 @@
#include "../utils/helper.h" #include "../utils/helper.h"
#if defined(ESP32) #if defined(ESP32)
#define MAX_ALLOWED_BUF_SIZE ESP.getMaxAllocHeap() - 2048 #define MAX_ALLOWED_BUF_SIZE ESP.getMaxAllocHeap() - 1024
#else #else
#define MAX_ALLOWED_BUF_SIZE ESP.getMaxFreeBlockSize() - 2048 #define MAX_ALLOWED_BUF_SIZE ESP.getMaxFreeBlockSize() - 1024
#endif #endif
/** /**
@ -161,7 +161,9 @@ typedef struct {
class settings { class settings {
public: public:
settings() {} settings() {
mLastSaveSucceed = false;
}
void setup() { void setup() {
DPRINTLN(DBG_INFO, F("Initializing FS ..")); DPRINTLN(DBG_INFO, F("Initializing FS .."));
@ -208,6 +210,10 @@ class settings {
return mCfg.valid; return mCfg.valid;
} }
inline bool getLastSaveSucceed() {
return mLastSaveSucceed;
}
void getInfo(uint32_t *used, uint32_t *size) { void getInfo(uint32_t *used, uint32_t *size) {
#if !defined(ESP32) #if !defined(ESP32)
FSInfo info; FSInfo info;
@ -254,7 +260,7 @@ class settings {
return mCfg.valid; return mCfg.valid;
} }
bool saveSettings(bool stopFs = false) { bool saveSettings() {
DPRINTLN(DBG_DEBUG, F("save settings")); DPRINTLN(DBG_DEBUG, F("save settings"));
DynamicJsonDocument json(MAX_ALLOWED_BUF_SIZE); DynamicJsonDocument json(MAX_ALLOWED_BUF_SIZE);
@ -269,31 +275,35 @@ class settings {
jsonPlugin(root.createNestedObject(F("plugin")), true); jsonPlugin(root.createNestedObject(F("plugin")), true);
jsonInst(root.createNestedObject(F("inst")), true); jsonInst(root.createNestedObject(F("inst")), true);
DPRINT(DBG_INFO, "memory usage: "); DPRINT(DBG_INFO, F("memory usage: "));
DBGPRINTLN(String(json.memoryUsage())); 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()) { if(json.overflowed()) {
DPRINTLN(DBG_ERROR, F("buffer too small!")); DPRINTLN(DBG_ERROR, F("buffer too small!"));
mLastSaveSucceed = false;
return false; return false;
} }
File fp = LittleFS.open("/settings.json", "w"); File fp = LittleFS.open("/settings.json", "w");
if(!fp) { if(!fp) {
DPRINTLN(DBG_ERROR, F("can't open settings file!")); DPRINTLN(DBG_ERROR, F("can't open settings file!"));
mLastSaveSucceed = false;
return false; return false;
} }
if(0 == serializeJson(root, fp)) { if(0 == serializeJson(root, fp)) {
DPRINTLN(DBG_ERROR, F("can't write settings file!")); DPRINTLN(DBG_ERROR, F("can't write settings file!"));
mLastSaveSucceed = false;
return false; return false;
} }
fp.close(); fp.close();
DPRINTLN(DBG_INFO, F("settings saved")); DPRINTLN(DBG_INFO, F("settings saved"));
if(stopFs) mLastSaveSucceed = true;
stop();
return true; return true;
} }
@ -301,7 +311,7 @@ class settings {
if(true == eraseWifi) if(true == eraseWifi)
return LittleFS.format(); return LittleFS.format();
loadDefaults(!eraseWifi); loadDefaults(!eraseWifi);
return saveSettings(true); return saveSettings();
} }
private: private:
@ -549,36 +559,41 @@ class settings {
if(set) if(set)
ivArr = obj.createNestedArray(F("iv")); ivArr = obj.createNestedArray(F("iv"));
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
if(set) if(set) {
jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true); if(mCfg.inst.iv[i].serial.u64 != 0ULL)
else jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true);
jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]); }
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) { void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) {
if(set) { if(set) {
obj[F("en")] = (bool)cfg->enabled; obj[F("en")] = (bool)cfg->enabled;
obj[F("name")] = cfg->name; obj[F("name")] = cfg->name;
obj[F("sn")] = cfg->serial.u64; obj[F("sn")] = cfg->serial.u64;
for(uint8_t i = 0; i < 4; i++) { 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("pwr")][i] = cfg->chMaxPwr[i];
obj[F("chName")][i] = cfg->chName[i]; obj[F("chn")][i] = cfg->chName[i];
} }
} else { } else {
cfg->enabled = (bool)obj[F("en")]; cfg->enabled = (bool)obj[F("en")];
snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as<const char*>()); snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as<const char*>());
cfg->serial.u64 = obj[F("sn")]; cfg->serial.u64 = obj[F("sn")];
for(uint8_t i = 0; i < 4; i++) { 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]; cfg->chMaxPwr[i] = obj[F("pwr")][i];
snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as<const char*>()); snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chn")][i].as<const char*>());
} }
} }
} }
settings_t mCfg; settings_t mCfg;
bool mLastSaveSucceed;
}; };
#endif /*__SETTINGS_H__*/ #endif /*__SETTINGS_H__*/

17
src/web/RestApi.h

@ -80,6 +80,7 @@ class RestApi {
if(path == "html/system") getHtmlSystem(root); if(path == "html/system") getHtmlSystem(root);
else if(path == "html/logout") getHtmlLogout(root); else if(path == "html/logout") getHtmlLogout(root);
else if(path == "html/save") getHtmlSave(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 == "system") getSysInfo(root);
else if(path == "generic") getGeneric(root); else if(path == "generic") getGeneric(root);
else if(path == "reboot") getReboot(root); else if(path == "reboot") getReboot(root);
@ -266,9 +267,19 @@ class RestApi {
void getHtmlSave(JsonObject obj) { void getHtmlSave(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
obj[F("refresh")] = 2; obj[F("refresh")] = 1;
obj[F("refresh_url")] = "/setup"; obj[F("refresh_url")] = F("/chk_save");
obj[F("html")] = F("settings succesfully 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) { void getReboot(JsonObject obj) {

4
src/web/html/index.html

@ -102,12 +102,12 @@
if(obj["disNightComm"]) { if(obj["disNightComm"]) {
if(((obj["ts_sunrise"] - obj["ts_offset"]) < obj["ts_now"]) if(((obj["ts_sunrise"] - obj["ts_offset"]) < obj["ts_now"])
&& ((obj["ts_sunset"] + 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 { else {
commInfo = "Night time, inverter polling disabled, "; commInfo = "Night time, inverter polling disabled, ";
if(obj["ts_now"] > (obj["ts_sunrise"] - obj["ts_offset"])) { 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 { else {
commInfo += "will start polling at " + (new Date((obj["ts_sunrise"] - obj["ts_offset"]) * 1000).toLocaleString('de-DE')); commInfo += "will start polling at " + (new Date((obj["ts_sunrise"] - obj["ts_offset"]) * 1000).toLocaleString('de-DE'));

12
src/web/html/setup.html

@ -157,7 +157,7 @@
<div class="col-4 col-sm-9"><input type="checkbox" name="invRstMid"/></div> <div class="col-4 col-sm-9"><input type="checkbox" name="invRstMid"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 col-sm-3 mb-2">Reset values when inverter polling stops at sunset</div> <div class="col-8 col-sm-3 mb-2">Reset values when inverter polling pauses at sunset</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="invRstComStop"/></div> <div class="col-4 col-sm-9"><input type="checkbox" name="invRstComStop"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
@ -209,7 +209,7 @@
<div class="col-12 col-sm-9"><select name="sunOffs"></select></div> <div class="col-12 col-sm-9"><select name="sunOffs"></select></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 col-sm-3">Stop polling inverters during night</div> <div class="col-8 col-sm-3">Pause polling inverters during night</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="sunDisNightCom"/></div> <div class="col-4 col-sm-9"><input type="checkbox" name="sunDisNightCom"/></div>
</div> </div>
</fieldset> </fieldset>
@ -291,8 +291,8 @@
<fieldset class="mb-4"> <fieldset class="mb-4">
<legend class="des">Import / Export JSON Settings</legend> <legend class="des">Import / Export JSON Settings</legend>
<div class="row mb-4 mt-4"> <div class="row mb-4 mt-4">
<div class="col-8 col-sm-3">Import</div> <div class="col-12 col-sm-3 my-2">Import</div>
<div class="col-4 col-sm-9"> <div class="col-12 col-sm-9">
<form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8"> <form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8">
<input type="file" name="upload"> <input type="file" name="upload">
<input type="button" class="btn" value="Import" onclick="hide()"> <input type="button" class="btn" value="Import" onclick="hide()">
@ -300,8 +300,8 @@
</div> </div>
</div> </div>
<div class="row mb-4 mt-4"> <div class="row mb-4 mt-4">
<div class="col-8 col-sm-3">Export</div> <div class="col-12 col-sm-3 my-2">Export</div>
<div class="col-4 col-sm-9"> <div class="col-12 col-sm-9">
<a class="btn" href="/get_setup" target="_blank">Export settings (JSON file)</a><span> (only values, passwords will be removed!)</span> <a class="btn" href="/get_setup" target="_blank">Export settings (JSON file)</a><span> (only values, passwords will be removed!)</span>
</div> </div>
</div> </div>

17
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("/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("/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("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1));
//mWeb.on("/api1", HTTP_POST, std::bind(&Web::showWebApi, 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")); mApp->saveSettings((request->arg("reboot") == "on"));
if (request->arg("reboot") == "on") AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
onReboot(request); response->addHeader(F("Content-Encoding"), "gzip");
else { 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) { void onLive(AsyncWebServerRequest *request) {

Loading…
Cancel
Save