From 6e41a83a26bed882bb2dab0508006b21533e9b01 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 8 Jan 2023 22:16:14 +0100 Subject: [PATCH] corrected MQTT `comm_disabled` #529 fix Prometheus and JSON endpoints (`config_override.h`) #561 publish MQTT with fixed interval even if inverter is not available #542 added JSON settings upload. NOTE: settings JSON download changed, so only settings should be uploaded starting from version `0.5.70` #551 MQTT topic and inverter name have more allowed characters: `[A-Za-z0-9./#$%&=+_-]+`, thx: @Mo Demman improved potential issue with `checkTicker`, thx @cbscpe MQTT option for reset values on midnight / not avail / communication stop #539 small fix in `tickIVCommunication` #534 add `YieldTotal` correction, eg. to have the option to zero at year start #512 --- src/CHANGES.md | 8 +++ src/app.cpp | 14 +++- src/app.h | 7 +- src/appInterface.h | 1 + src/config/settings.h | 31 +++++++-- src/defines.h | 2 +- src/hm/hmInverter.h | 18 ++++- src/publisher/pubMqtt.h | 143 +++++++++++++++++++++++++++++---------- src/utils/ahoyTimer.h | 2 +- src/utils/llist.h | 110 ------------------------------ src/utils/scheduler.h | 1 + src/web/RestApi.h | 43 +++++++++--- src/web/html/setup.html | 45 +++++++++--- src/web/html/update.html | 1 - src/web/web.h | 129 ++++++++++++++++++++++++++--------- src/wifi/ahoywifi.h | 3 +- 16 files changed, 343 insertions(+), 215 deletions(-) delete mode 100644 src/utils/llist.h diff --git a/src/CHANGES.md b/src/CHANGES.md index 364b2b4d4..ce304e4bb 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -4,6 +4,14 @@ ## 0.5.70 * corrected MQTT `comm_disabled` #529 +* fix Prometheus and JSON endpoints (`config_override.h`) #561 +* publish MQTT with fixed interval even if inverter is not available #542 +* added JSON settings upload. NOTE: settings JSON download changed, so only settings should be uploaded starting from version `0.5.70` #551 +* MQTT topic and inverter name have more allowed characters: `[A-Za-z0-9./#$%&=+_-]+`, thx: @Mo Demman +* improved potential issue with `checkTicker`, thx @cbscpe +* MQTT option for reset values on midnight / not avail / communication stop #539 +* small fix in `tickIVCommunication` #534 +* add `YieldTotal` correction, eg. to have the option to zero at year start #512 ## 0.5.69 * merged SH1106 1.3" Display, thx @dAjaY85 diff --git a/src/app.cpp b/src/app.cpp index 6f53121b8..e42b8c2b2 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -62,6 +62,9 @@ void app::setup() { if (mConfig->mqtt.broker[0] > 0) { everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt)); everyMin(std::bind(&PubMqttType::tickerMinute, &mMqtt)); + uint32_t nxtTrig = mTimestamp - ((mTimestamp - 1) % 86400) + 86400; // next midnight + if(mConfig->mqtt.rstYieldMidNight) + onceAt(std::bind(&app::tickMidnight, this), nxtTrig); mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); } #endif @@ -161,7 +164,7 @@ void app::tickIVCommunication(void) { if (mTimestamp < (mSunrise - mConfig->sun.offsetSec)) { // current time is before communication start, set next trigger to communication start nxtTrig = mSunrise - mConfig->sun.offsetSec; } else { - if (mTimestamp > (mSunset + mConfig->sun.offsetSec)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise + if (mTimestamp >= (mSunset + mConfig->sun.offsetSec)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise nxtTrig = 0; } else { // current time lies within communication start/stop time, set next trigger to communication stop mIVCommunicationOn = true; @@ -207,6 +210,15 @@ void app::tickSend(void) { updateLed(); } +//----------------------------------------------------------------------------- +void app::tickMidnight(void) { + // only used and enabled by MQTT (see setup()) + uint32_t nxtTrig = mTimestamp - ((mTimestamp - 1) % 86400) + 86400; // next midnight + onceAt(std::bind(&app::tickMidnight, this), nxtTrig); + + mMqtt.tickerMidnight(); +} + //----------------------------------------------------------------------------- void app::handleIntr(void) { DPRINTLN(DBG_VERBOSE, F("app::handleIntr")); diff --git a/src/app.h b/src/app.h index b08b7d05e..fdbf96169 100644 --- a/src/app.h +++ b/src/app.h @@ -78,6 +78,10 @@ class app : public IApp, public ah::Scheduler { return mSettings.saveSettings(); } + bool readSettings(const char *path) { + return mSettings.readSettings(path); + } + bool eraseSettings(bool eraseWifi = false) { return mSettings.eraseSettings(eraseWifi); } @@ -95,7 +99,7 @@ class app : public IApp, public ah::Scheduler { } void setRebootFlag() { - once(std::bind(&app::tickReboot, this), 1); + once(std::bind(&app::tickReboot, this), 3); } const char *getVersion() { @@ -203,6 +207,7 @@ class app : public IApp, public ah::Scheduler { void tickCalcSunrise(void); void tickIVCommunication(void); void tickSend(void); + void tickMidnight(void); /*void tickSerial(void) { if(Serial.available() == 0) return; diff --git a/src/appInterface.h b/src/appInterface.h index 4c25c3a2c..c2d191b25 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -15,6 +15,7 @@ class IApp { public: virtual ~IApp() {} virtual bool saveSettings() = 0; + virtual bool readSettings(const char *path) = 0; virtual bool eraseSettings(bool eraseWifi) = 0; virtual void setRebootFlag() = 0; virtual const char *getVersion() = 0; diff --git a/src/config/settings.h b/src/config/settings.h index 921ebc966..50fbe01dd 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -97,6 +97,9 @@ typedef struct { char pwd[MQTT_PWD_LEN]; char topic[MQTT_TOPIC_LEN]; uint16_t interval; + bool rstYieldMidNight; + bool rstValsNotAvail; + bool rstValsCommStop; } cfgMqtt_t; typedef struct { @@ -105,6 +108,7 @@ typedef struct { serial_u serial; uint16_t chMaxPwr[4]; char chName[4][MAX_NAME_LENGTH]; + uint32_t yieldCor; // YieldTotal correction value } cfgIv_t; typedef struct { @@ -155,7 +159,7 @@ class settings { else DPRINTLN(DBG_INFO, F(" .. done")); - readSettings(); + readSettings("/settings.json"); } // should be used before OTA @@ -186,9 +190,10 @@ class settings { #endif } - void readSettings(void) { + bool readSettings(const char* path) { + bool success = false; loadDefaults(); - File fp = LittleFS.open("/settings.json", "r"); + File fp = LittleFS.open(path, "r"); if(!fp) DPRINTLN(DBG_WARN, F("failed to load json, using default config")); else { @@ -206,6 +211,7 @@ class settings { jsonMqtt(root["mqtt"]); jsonLed(root["led"]); jsonInst(root["inst"]); + success = true; } else { Serial.println(F("failed to parse json, using default config")); @@ -213,6 +219,7 @@ class settings { fp.close(); } + return success; } bool saveSettings(void) { @@ -299,6 +306,9 @@ class settings { snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", DEF_MQTT_PWD); snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC); mCfg.mqtt.interval = 0; // off + mCfg.mqtt.rstYieldMidNight = false; + mCfg.mqtt.rstValsNotAvail = false; + mCfg.mqtt.rstValsCommStop = false; mCfg.led.led0 = DEF_LED0_PIN; mCfg.led.led1 = DEF_LED1_PIN; @@ -399,9 +409,16 @@ class settings { obj[F("pwd")] = mCfg.mqtt.pwd; obj[F("topic")] = mCfg.mqtt.topic; obj[F("intvl")] = mCfg.mqtt.interval; + obj[F("rstMidNight")] = (bool)mCfg.mqtt.rstYieldMidNight; + obj[F("rstNotAvail")] = (bool)mCfg.mqtt.rstValsNotAvail; + obj[F("rstComStop")] = (bool)mCfg.mqtt.rstValsCommStop; + } else { mCfg.mqtt.port = obj[F("port")]; mCfg.mqtt.interval = obj[F("intvl")]; + mCfg.mqtt.rstYieldMidNight = (bool)obj["rstMidNight"]; + mCfg.mqtt.rstValsNotAvail = (bool)obj["rstNotAvail"]; + mCfg.mqtt.rstValsCommStop = (bool)obj["rstComStop"]; snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as()); snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as()); snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as()); @@ -438,9 +455,10 @@ class settings { 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; + obj[F("yield")] = cfg->yieldCor; for(uint8_t i = 0; i < 4; i++) { obj[F("pwr")][i] = cfg->chMaxPwr[i]; obj[F("chName")][i] = cfg->chName[i]; @@ -449,6 +467,7 @@ class settings { cfg->enabled = (bool)obj[F("en")]; snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as()); cfg->serial.u64 = obj[F("sn")]; + cfg->yieldCor = obj[F("yield")]; for(uint8_t i = 0; i < 4; i++) { cfg->chMaxPwr[i] = obj[F("pwr")][i]; snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); diff --git a/src/defines.h b/src/defines.h index b37cb6c77..8ab9ccdaa 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 69 +#define VERSION_PATCH 70 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 8c1fccc84..f02c6b9b5 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -233,11 +233,13 @@ class Inverter { val <<= 8; val |= buf[ptr]; } while(++ptr != end); - if(FLD_T == rec->assign[pos].fieldId) { + if (FLD_T == rec->assign[pos].fieldId) { // temperature is a signed value! rec->record[pos] = (REC_TYP)((int16_t)val) / (REC_TYP)(div); - } - else { + } else if ((FLD_YT == rec->assign[pos].fieldId) + && (config->yieldCor != 0)) { + rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div) - (REC_TYP)config->yieldCor; + } else { if ((REC_TYP)(div) > 1) rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div); else @@ -286,6 +288,16 @@ class Inverter { DPRINTLN(DBG_ERROR, F("addValue: assignment not found with cmd 0x")); } + bool setValue(uint8_t pos, record_t<> *rec, REC_TYP val) { + DPRINTLN(DBG_VERBOSE, F("hmInverter.h:setValue")); + if(NULL == rec) + return false; + if(pos > rec->length) + return false; + rec->record[pos] = val; + return true; + } + REC_TYP getValue(uint8_t pos, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getValue")); if(NULL == rec) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 382406f44..85742bd53 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -112,6 +112,27 @@ class PubMqtt { void tickerComm(bool disabled) { publish("comm_disabled", ((disabled) ? "true" : "false"), true); publish("comm_dis_ts", String(*mUtcTimestamp).c_str(), true); + + if(disabled && (mCfgMqtt->rstValsCommStop)) + zeroAllInverters(); + } + + void tickerMidnight() { + Inverter<> *iv; + record_t<> *rec; + + // set YieldDay to zero + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + iv = mSys->getInverterByPos(id); + if (NULL == iv) + continue; // skip to next inverter + rec = iv->getRecordStruct(RealTimeRunData_Debug); + uint8_t pos = iv->getPosByChFld(CH0, FLD_YD, rec); + iv->setValue(pos, rec, 0.0f); + } + + mSendList.push(RealTimeRunData_Debug); + sendIvData(); } void payloadEventListener(uint8_t cmd) { @@ -394,18 +415,21 @@ class PubMqtt { allAvail = false; } } - else if (!iv->isProducing(*mUtcTimestamp, rec)) { + else { mIvAvail = true; - if (MQTT_STATUS_AVAIL_PROD == status) - status = MQTT_STATUS_AVAIL_NOT_PROD; + if (!iv->isProducing(*mUtcTimestamp, rec)) { + if (MQTT_STATUS_AVAIL_PROD == status) + status = MQTT_STATUS_AVAIL_NOT_PROD; + } } - else - mIvAvail = true; if(mLastIvState[id] != status) { mLastIvState[id] = status; changed = true; + if(mCfgMqtt->rstValsNotAvail) + zeroValues(iv); + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name); snprintf(val, 40, "%d", status); publish(topic, val, true); @@ -419,12 +443,13 @@ class PubMqtt { if(changed) { snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((mIvAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); publish("status", val, true); + sendIvData(false); // false prevents loop of same function } return totalComplete; } - void sendIvData(void) { + void sendIvData(bool sendTotals = true) { if(mSendList.empty()) return; @@ -442,49 +467,52 @@ class PubMqtt { record_t<> *rec = iv->getRecordStruct(mSendList.front()); // data - if(iv->isAvailable(*mUtcTimestamp, rec)) { - for (uint8_t i = 0; i < rec->length; i++) { - bool retained = false; - if (mSendList.front() == RealTimeRunData_Debug) { + //if(iv->isAvailable(*mUtcTimestamp, rec) || (0 != mCfgMqtt->interval)) { // is avail or fixed pulish interval was set + for (uint8_t i = 0; i < rec->length; i++) { + bool retained = false; + if (mSendList.front() == RealTimeRunData_Debug) { + switch (rec->assign[i].fieldId) { + case FLD_YT: + case FLD_YD: + retained = true; + break; + } + } + + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); + snprintf(val, 40, "%g", ah::round3(iv->getValue(i, rec))); + publish(topic, val, retained); + + // calculate total values for RealTimeRunData_Debug + if (mSendList.front() == RealTimeRunData_Debug) { + if (CH0 == rec->assign[i].ch) { switch (rec->assign[i].fieldId) { + case FLD_PAC: + total[0] += iv->getValue(i, rec); + break; case FLD_YT: + total[1] += iv->getValue(i, rec); + break; case FLD_YD: - retained = true; + total[2] += iv->getValue(i, rec); + break; + case FLD_PDC: + total[3] += iv->getValue(i, rec); break; } } - - snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); - snprintf(val, 40, "%g", ah::round3(iv->getValue(i, rec))); - publish(topic, val, retained); - - // calculate total values for RealTimeRunData_Debug - if (mSendList.front() == RealTimeRunData_Debug) { - if (CH0 == rec->assign[i].ch) { - switch (rec->assign[i].fieldId) { - case FLD_PAC: - total[0] += iv->getValue(i, rec); - break; - case FLD_YT: - total[1] += iv->getValue(i, rec); - break; - case FLD_YD: - total[2] += iv->getValue(i, rec); - break; - case FLD_PDC: - total[3] += iv->getValue(i, rec); - break; - } - } - sendTotal = true; - } - yield(); + sendTotal = true; } + yield(); } + //} } mSendList.pop(); // remove from list once all inverters were processed + if(!sendTotals) // skip total value calculation + continue; + if ((true == sendTotal) && processIvStatus()) { uint8_t fieldId; for (uint8_t i = 0; i < 4; i++) { @@ -511,6 +539,47 @@ class PubMqtt { } } + void zeroAllInverters() { + Inverter<> *iv; + + // set values to zero, exept yields + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + iv = mSys->getInverterByPos(id); + if (NULL == iv) + continue; // skip to next inverter + + zeroValues(iv); + } + sendIvData(); + } + + void zeroValues(Inverter<> *iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + for(uint8_t ch = 0; ch <= iv->channels; ch++) { + uint8_t pos = 0; + uint8_t fld = 0; + while(0xff != pos) { + switch(fld) { + case FLD_YD: + case FLD_YT: + case FLD_FW_VERSION: + case FLD_FW_BUILD_YEAR: + case FLD_FW_BUILD_MONTH_DAY: + case FLD_FW_BUILD_HOUR_MINUTE: + case FLD_HW_ID: + case FLD_ACT_ACTIVE_PWR_LIMIT: + continue; + break; + } + pos = iv->getPosByChFld(ch, fld, rec); + iv->setValue(pos, rec, 0.0f); + fld++; + } + } + + mSendList.push(RealTimeRunData_Debug); + } + espMqttClient mClient; cfgMqtt_t *mCfgMqtt; #if defined(ESP8266) diff --git a/src/utils/ahoyTimer.h b/src/utils/ahoyTimer.h index 5c960a346..08c09016c 100644 --- a/src/utils/ahoyTimer.h +++ b/src/utils/ahoyTimer.h @@ -15,7 +15,7 @@ namespace ah { *ticker = mil + interval; return true; } - else if(mil < (*ticker - interval)) { + else if((mil + interval) < (*ticker)) { *ticker = mil + interval; return true; } diff --git a/src/utils/llist.h b/src/utils/llist.h deleted file mode 100644 index 69750f19e..000000000 --- a/src/utils/llist.h +++ /dev/null @@ -1,110 +0,0 @@ -//----------------------------------------------------------------------------- -// 2022 Ahoy, https://ahoydtu.de -// Lukas Pusch, lukas@lpusch.de -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ -//----------------------------------------------------------------------------- -#ifndef __LIST_H__ -#define __LIST_H__ - -template -struct node_s { - typedef T dT; - node_s *pre; - node_s *nxt; - uint8_t id; - dT d; - node_s() : pre(NULL), nxt(NULL), d() {} - node_s(Args... args) : id(0), pre(NULL), nxt(NULL), d(args...) {} -}; - -template -class llist { - typedef node_s elmType; - typedef T dataType; - public: - llist() : root(mPool) { - root = NULL; - elmType *p = mPool; - for(uint32_t i = 0; i < MAX_NUM; i++) { - p->id = i; - p++; - } - mFill = mMax = 0; - } - - elmType *add(Args... args) { - elmType *p = root, *t; - if(NULL == (t = getFreeNode())) - return NULL; - if(++mFill > mMax) - mMax = mFill; - - if(NULL == root) { - p = root = t; - p->pre = p; - p->nxt = p; - } - else { - p = root->pre; - t->pre = p; - p->nxt->pre = t; - t->nxt = p->nxt; - p->nxt = t; - } - t->d = dataType(args...); - return p; - } - - elmType *getFront() { - return root; - } - - elmType *get(elmType *p) { - p = p->nxt; - return (p == root) ? NULL : p; - } - - elmType *rem(elmType *p) { - if(NULL == p) - return NULL; - elmType *t = p->nxt; - p->nxt->pre = p->pre; - p->pre->nxt = p->nxt; - if((root == p) && (p->nxt == p)) - root = NULL; - else - root = p->nxt; - p->nxt = NULL; - p->pre = NULL; - p = NULL; - mFill--; - return (NULL == root) ? NULL : ((t == root) ? NULL : t); - } - - uint16_t getFill(void) { - return mFill; - } - - uint16_t getMaxFill(void) { - return mMax; - } - - protected: - elmType *root; - - private: - elmType *getFreeNode(void) { - elmType *n = mPool; - for(uint32_t i = 0; i < MAX_NUM; i++) { - if(NULL == n->nxt) - return n; - n++; - } - return NULL; - } - - elmType mPool[MAX_NUM]; - uint16_t mFill, mMax; -}; - -#endif /*__LIST_H__*/ diff --git a/src/utils/scheduler.h b/src/utils/scheduler.h index 36dcdaaef..330ab080e 100644 --- a/src/utils/scheduler.h +++ b/src/utils/scheduler.h @@ -129,6 +129,7 @@ namespace ah { mTickerInUse[i] = false; else mTicker[i].timeout = mTicker[i].reload; + //DPRINTLN(DBG_INFO, "checkTick " + String(i) + " reload: " + String(mTicker[i].reload) + ", timeout: " + String(mTicker[i].timeout)); (mTicker[i].c)(); yield(); } diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 8986e76d9..07c129944 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -134,17 +134,34 @@ class RestApi { ep[F("record/config")] = url + F("record/config"); ep[F("record/live")] = url + F("record/live"); } + void onDwnldSetup(AsyncWebServerRequest *request) { - AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); - JsonObject root = response->getRoot(); + AsyncWebServerResponse *response; - getSetup(root); + File fp = LittleFS.open("/settings.json", "r"); + if(!fp) { + DPRINTLN(DBG_ERROR, F("failed to load settings")); + response = request->beginResponse(200, F("application/json"), "{}"); + } + else { + String tmp = fp.readString(); + int i = 0; + // remove all passwords + while (i != -1) { + i = tmp.indexOf("\"pwd\":", i); + if(-1 != i) { + i+=7; + tmp.remove(i, tmp.indexOf("\"", i)-i); + } + } + response = request->beginResponse(200, F("application/json"), tmp); + } - response->setLength(); response->addHeader("Content-Type", "application/octet-stream"); response->addHeader("Content-Description", "File Transfer"); response->addHeader("Content-Disposition", "attachment; filename=ahoy_setup.json"); request->send(response); + fp.close(); } void getGeneric(JsonObject obj) { @@ -165,7 +182,7 @@ class RestApi { obj[F("device_name")] = mConfig->sys.deviceName; obj[F("mac")] = WiFi.macAddress(); - obj[F("hostname")] = WiFi.getHostname(); + obj[F("hostname")] = mConfig->sys.deviceName; obj[F("pwd_set")] = (strlen(mConfig->sys.adminPwd) > 0); obj[F("prot_mask")] = mConfig->sys.protectionMask; @@ -263,6 +280,7 @@ class RestApi { obj2[F("serial")] = String(iv->config->serial.u64, HEX); obj2[F("channels")] = iv->channels; obj2[F("version")] = String(iv->getFwVersion()); + obj2[F("yieldCor")] = iv->config->yieldCor; for(uint8_t j = 0; j < iv->channels; j ++) { obj2[F("ch_max_power")][j] = iv->config->chMaxPwr[j]; @@ -276,12 +294,15 @@ class RestApi { } void getMqtt(JsonObject obj) { - obj[F("broker")] = String(mConfig->mqtt.broker); - obj[F("port")] = String(mConfig->mqtt.port); - obj[F("user")] = String(mConfig->mqtt.user); - obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); - obj[F("topic")] = String(mConfig->mqtt.topic); - obj[F("interval")] = String(mConfig->mqtt.interval); + obj[F("broker")] = String(mConfig->mqtt.broker); + obj[F("port")] = String(mConfig->mqtt.port); + obj[F("user")] = String(mConfig->mqtt.user); + obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); + obj[F("topic")] = String(mConfig->mqtt.topic); + obj[F("interval")] = String(mConfig->mqtt.interval); + obj[F("rstMid")] = (bool)mConfig->mqtt.rstYieldMidNight; + obj[F("rstNAvail")] = (bool)mConfig->mqtt.rstValsNotAvail; + obj[F("rstComStop")] = (bool)mConfig->mqtt.rstValsCommStop; } void getNtp(JsonObject obj) { diff --git a/src/web/html/setup.html b/src/web/html/setup.html index c60020e13..aad46232e 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -31,7 +31,13 @@
ERASE SETTINGS (not WiFi) - +
+ Upload JSON Settings +
+ + +
+
Device Host Name @@ -148,6 +154,12 @@ + +
+ +
+ +

Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)

@@ -184,7 +196,7 @@
- Download your settings (JSON file) (only saved values) + Download your settings (JSON file) (only saved values, passwords will be removed!)
@@ -212,8 +224,9 @@ const re = /11[2,4,6]1.*/; document.getElementById("btnAdd").addEventListener("click", function() { - if(highestId <= (maxInv-1)) - ivHtml(JSON.parse('{"enabled":true,"name":"","serial":"","channels":4,"ch_max_power":[0,0,0,0],"ch_name":["","","",""]}'), highestId + 1); + if(highestId <= (maxInv-1)) { + ivHtml(JSON.parse('{"enabled":true,"name":"","serial":"","channels":4,"ch_max_power":[0,0,0,0],"ch_name":["","","",""]}'), highestId); + } }); function apiCbWifi(obj) { @@ -268,6 +281,12 @@ getAjax("/api/setup", apiCbMqtt, "POST", JSON.stringify(obj)); } + function hide() { + document.getElementById("form").submit(); + var e = document.getElementById("content"); + e.replaceChildren(span("upload started")); + } + function delIv() { var id = this.id.substring(0,4); var e = document.getElementsByName(id + "Addr")[0]; @@ -278,8 +297,8 @@ } function ivHtml(obj, id) { - highestId = id; - if(highestId == (maxInv - 1)) + highestId = id + 1; + if(highestId == maxInv) setHide("btnAdd", true); iv = document.getElementById("inverter"); iv.appendChild(des("Inverter " + id)); @@ -292,7 +311,7 @@ iv.appendChild(br()); iv.appendChild(lbl(id + "Addr", "Serial Number (12 digits)*")); - var addr = inp(id + "Addr", obj["serial"], 12); + var addr = inp(id + "Addr", obj["serial"], 12, ["text"], null, "text", "[0-9]+", "Invalid input"); iv.appendChild(addr); ['keyup', 'change'].forEach(function(evt) { addr.addEventListener(evt, (e) => { @@ -323,7 +342,7 @@ iv.append( lbl(id + "Name", "Name*"), - inp(id + "Name", obj["name"], 32, ["text"], null, "text", "[A-Za-z0-9.\\-_\\+\\/]+", "Invalid input") + inp(id + "Name", obj["name"], 32, ["text"], null, "text", "[A-Za-z0-9./#$%&=+_-]+", "Invalid input") ); for(var j of [["ModPwr", "ch_max_power", "Max Module Power (Wp)", 4, "[0-9]+"], ["ModName", "ch_name", "Module Name", 16, null]]) { @@ -339,10 +358,15 @@ iv.appendChild(d); } + iv.append( + br(), + lbl(id + "YieldCor", "Yield Total Correction (will be subtracted) [kWh]"), + inp(id + "YieldCor", obj["yieldCor"], 32, ["text"], null, "text", "[0-9]+", "Invalid input") + ); + var del = inp(id+"del", "X", 0, ["btn", "btnDel"], id+"del", "button"); del.addEventListener("click", delIv); iv.append( - br(), lbl(id + "lbldel", "Delete"), del ); @@ -394,6 +418,9 @@ function parseMqtt(obj) { for(var i of [["Addr", "broker"], ["Port", "port"], ["User", "user"], ["Pwd", "pwd"], ["Topic", "topic"], ["Interval", "interval"]]) document.getElementsByName("mqtt"+i[0])[0].value = obj[i[1]]; + + for(var i of [["Mid", "rstMid"], ["ComStop", "rstNAvail"], ["NotAvail", "rstComStop"]]) + document.getElementsByName("mqttRst"+i[0])[0].checked = obj[i[1]]; } function parseNtp(obj) { diff --git a/src/web/html/update.html b/src/web/html/update.html index 215188db2..e9bcde87b 100644 --- a/src/web/html/update.html +++ b/src/web/html/update.html @@ -23,7 +23,6 @@ -