diff --git a/src/app.cpp b/src/app.cpp index a3d93bb1..1edef85c 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -14,7 +14,11 @@ //----------------------------------------------------------------------------- app::app() : ah::Scheduler {} { + #if defined(PLUGIN_ZEROEXPORT) memset(mVersion, 0, sizeof(char) * 17); + #else + memset(mVersion, 0, sizeof(char) * 12); + #endif memset(mVersionModules, 0, sizeof(char) * 12); } @@ -118,17 +122,21 @@ void app::setup() { esp_task_wdt_reset(); + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) +// TODO: aufräumen +// if (mConfig->plugin.zeroExport.enabled) { + mZeroExport.setup(&mConfig->plugin.zeroExport, &mSys, mConfig); +// } + #endif + // Plugin ZeroExport - Ende + #if defined(ENABLE_HISTORY) mHistory.setup(this, &mSys, mConfig, &mTimestamp); #endif /*ENABLE_HISTORY*/ mPubSerial.setup(mConfig, &mSys, &mTimestamp); - // ZeroExport - if (mConfig->plugin.zexport.enabled) { - mzExport.setup(&mConfig->plugin.zexport, &mSys, mConfig); - } - #if !defined(ETHERNET) //mImprov.setup(this, mConfig->sys.deviceName, mVersion); #endif @@ -164,6 +172,15 @@ void app::loop(void) { if (mMqttEnabled && mNetworkConnected) mMqtt.loop(); #endif + + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) + if(mConfig->nrf.enabled || mConfig->cmt.enabled) { + mZeroExport.loop(); + } + #endif + // Plugin ZeroExport - Ende + yield(); } @@ -197,11 +214,14 @@ void app::regularTickers(void) { everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp"); #endif - // ZeroExport + // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) - if (mConfig->plugin.zexport.enabled) - everySec(std::bind(&ZeroExportType::tickerSecond, &mzExport), "zExport"); +// TODO: aufräumen +// if (mConfig->plugin.zeroExport.enabled) { + everySec(std::bind(&ZeroExportType::tickerSecond, &mZeroExport), "ZeroExport"); +// } #endif + // Plugin ZeroExport - Ende every(std::bind(&PubSerialType::tick, &mPubSerial), 5, "uart"); #if !defined(ETHERNET) @@ -458,9 +478,15 @@ void app::tickSend(void) { mCommunication.add(iv, cmd); }); - #if defined(ESP32) - if(mConfig->nrf.enabled || mConfig->cmt.enabled) zeroexport(); + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) +// TODO: aufräumen + if(mConfig->nrf.enabled || mConfig->cmt.enabled) { + mZeroExport.loop(); +// zeroexport(); + } #endif + // Plugin ZeroExport - Ende } } @@ -521,7 +547,11 @@ void app:: zeroIvValues(bool checkAvail, bool skipYieldDay) { //----------------------------------------------------------------------------- void app::resetSystem(void) { + #if defined(PLUGIN_ZEROEXPORT) snprintf(mVersion, sizeof(mVersion), "zero-%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); + #else + snprintf(mVersion, sizeof(mVersion), "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); + #endif snprintf(mVersionModules, sizeof(mVersionModules), "%s", #ifdef ENABLE_PROMETHEUS_EP "P" @@ -635,37 +665,45 @@ void app::updateLed(void) { } } //----------------------------------------------------------------------------- -#if defined(ESP32) +// Plugin ZeroExport +#if defined(PLUGIN_ZEROEXPORT) void app::zeroexport() { - if (!mConfig->plugin.zexport.enabled || - !mSys.getInverterByPos(mConfig->plugin.zexport.Iv)->isProducing()) { // check if plugin is enabled && indicate to send new value - mConfig->plugin.zexport.lastTime = millis(); // set last timestamp + return; +// TODO: aufräumen +// TODO: umziehen nach loop + + +/* + + if (!mConfig->plugin.zeroExport.enabled || + !mSys.getInverterByPos(mConfig->plugin.zeroExport.Iv)->isProducing()) { // check if plugin is enabled && indicate to send new value + mConfig->plugin.zeroExport.lastTime = millis(); // set last timestamp return; } - if (millis() - mConfig->plugin.zexport.lastTime > mConfig->plugin.zexport.count_avg * 1000UL) + if (millis() - mConfig->plugin.zeroExport.lastTime > mConfig->plugin.zeroExport.count_avg * 1000UL) { - Inverter<> *iv = mSys.getInverterByPos(mConfig->plugin.zexport.Iv); + Inverter<> *iv = mSys.getInverterByPos(mConfig->plugin.zeroExport.Iv); DynamicJsonDocument doc(512); JsonObject object = doc.to(); - double nValue = round(mzExport.getPowertoSetnewValue()); + double nValue = round(mZeroExport.getPowertoSetnewValue()); double twoPerVal = nValue <= (iv->getMaxPower() / 100 * 2 ); - if(mConfig->plugin.zexport.two_percent && (nValue <= twoPerVal)) + if(mConfig->plugin.zeroExport.two_percent && (nValue <= twoPerVal)) nValue = twoPerVal; - if(mConfig->plugin.zexport.max_power <= nValue) - nValue = mConfig->plugin.zexport.max_power; + if(mConfig->plugin.zeroExport.max_power <= nValue) + nValue = mConfig->plugin.zeroExport.max_power; if(iv->actPowerLimit == nValue) { - mConfig->plugin.zexport.lastTime = millis(); // set last timestamp + mConfig->plugin.zeroExport.lastTime = millis(); // set last timestamp return; // if PowerLimit same as befor, then skip } object["val"] = nValue; - object["id"] = mConfig->plugin.zexport.Iv; + object["id"] = mConfig->plugin.zeroExport.Iv; object["path"] = "ctrl"; object["cmd"] = "limit_nonpersistent_absolute"; @@ -674,7 +712,9 @@ void app::zeroexport() { DPRINTLN(DBG_INFO, data); mApi.ctrlRequest(object); - mConfig->plugin.zexport.lastTime = millis(); // set last timestamp + mConfig->plugin.zeroExport.lastTime = millis(); // set last timestamp } +*/ } #endif +// Plugin ZeroExport - Ende diff --git a/src/app.h b/src/app.h index 0f51eebe..73477943 100644 --- a/src/app.h +++ b/src/app.h @@ -77,10 +77,12 @@ typedef Simulator SimulatorType; typedef Display DisplayType; #endif +// Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) #include "plugins/zeroExport/zeroExport.h" typedef ZeroExport ZeroExportType; #endif +// Plugin ZeroExport - Ende class app : public IApp, public ah::Scheduler { public: @@ -353,9 +355,11 @@ class app : public IApp, public ah::Scheduler { void setupLed(); void updateLed(); - #if defined(ESP32) + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) void zeroexport(); #endif + // Plugin ZeroExport - Ende void tickReboot(void) { DPRINTLN(DBG_INFO, F("Rebooting...")); @@ -423,7 +427,11 @@ class app : public IApp, public ah::Scheduler { CmtRadio<> mCmtRadio; #endif + #if defined(PLUGIN_ZEROEXPORT) char mVersion[17]; + #else + char mVersion[12]; + #endif char mVersionModules[12]; settings mSettings; settings_t *mConfig = nullptr; @@ -460,9 +468,11 @@ class app : public IApp, public ah::Scheduler { SimulatorType mSimulator; #endif /*ENABLE_SIMULATOR*/ + // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) - ZeroExportType mzExport; + ZeroExportType mZeroExport; #endif + // Plugin ZeroExport - Ende }; #endif /*__APP_H__*/ diff --git a/src/config/settings.h b/src/config/settings.h index 55d7031b..1162e63d 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -139,24 +139,6 @@ typedef struct { uint16_t interval; } cfgMqtt_t; -/* Zero Export section */ -#if defined(ESP32) -typedef struct { - char monitor_url[ZEXPORT_ADDR_LEN]; - char json_path[ZEXPORT_ADDR_LEN]; - uint8_t query_device; // 0 - Tibber, 1 - Shelly, 2 - other (rs232?) - uint8_t Iv; // saves the inverter that is used for regulation - bool enabled; - float power_avg; - uint8_t count_avg; - double total_power; - unsigned long lastTime; // tic toc - double max_power; - bool two_percent; // ask if not go lower then 2% - char tibber_pw[10]; // needed for tibber QWGH-ED12 -} cfgzeroExport_t; -#endif - typedef struct { bool enabled; char name[MAX_NAME_LENGTH]; @@ -203,14 +185,133 @@ typedef struct { } display_t; #endif +// Plugin ZeroExport +#define ZEROEXPORT_MAX_GROUPS 6 +#define ZEROEXPORT_GROUP_MAX_LEN_NAME 25 +#define ZEROEXPORT_GROUP_MAX_LEN_PM_URL 100 +#define ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH 100 +#define ZEROEXPORT_GROUP_MAX_LEN_PM_USER 25 +#define ZEROEXPORT_GROUP_MAX_LEN_PM_PASS 25 +#define ZEROEXPORT_GROUP_MAX_INVERTERS 3 +#define ZEROEXPORT_POWERMETER_MAX_ERRORS 5 +#define ZEROEXPORT_DEF_INV_WAITINGTIME_MS 10000 + +#if defined(PLUGIN_ZEROEXPORT) +enum class zeroExportState : uint8_t { + RESET, GETPOWERMETER, GETINVERTERPOWER, FINISH +}; + +typedef enum { + None = 0, + Shelly = 1, + Tasmota = 2, + Mqtt = 3, + Hichi = 4, + Tibber = 5, +} zeroExportPowermeterType_t; +/* +typedef struct { + uint8_t type; + uint8_t ip; + uint8_t url; + bool login; + uint8_t username; + uint8_t password; + uint8_t group; + uint8_t phase; + uint16_t nextRun; + uint16_t interval; + uint8_t error; + uint16_t power; +} zeroExportPowermeter_t; +*/ +typedef enum { + Sum = 0, + L1 = 1, + L2 = 2, + L3 = 3, + L1Sum = 4, + L2Sum = 5, + L3Sum = 6, +} zeroExportInverterTarget_t; + +typedef struct { + bool enabled; + int8_t id; + int8_t target; + bool twoPercent; + uint16_t powerMax; + + + float power; + uint16_t limit; + bool limitAck; + float dcVoltage; +// uint16_t waitingTime; +} zeroExportGroupInverter_t; + +typedef struct { + // General + bool enabled; + char name[ZEROEXPORT_GROUP_MAX_LEN_NAME]; + // Powermeter + uint8_t pm_type; + char pm_url[ZEROEXPORT_GROUP_MAX_LEN_PM_URL]; + char pm_jsonPath[ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH]; + char pm_user[ZEROEXPORT_GROUP_MAX_LEN_PM_USER]; + char pm_pass[ZEROEXPORT_GROUP_MAX_LEN_PM_PASS]; + // Inverters + zeroExportGroupInverter_t inverters[ZEROEXPORT_GROUP_MAX_INVERTERS]; + // Battery + bool battEnabled; + float battVoltageOn; + float battVoltageOff; + // Advanced + uint8_t refresh; + uint8_t powerTolerance; + uint16_t powerMax; + + + zeroExportState state; + unsigned long lastRun; + float pmPower; + float pmPowerL1; + float pmPowerL2; + float pmPowerL3; +// uint16_t power; // Aktueller Verbrauch +// uint16_t powerLimitAkt; // Aktuelles Limit +// uint16_t powerHyst; // Hysterese +} zeroExportGroup_t; + +typedef struct { + bool enabled; + zeroExportGroup_t groups[ZEROEXPORT_MAX_GROUPS]; + + + +// uint8_t query_device; // 0 - Tibber, 1 - Shelly, 2 - other (rs232?) +// char monitor_url[ZEXPORT_ADDR_LEN]; +// char json_path[ZEXPORT_ADDR_LEN]; +// char tibber_pw[10]; // needed for tibber QWGH-ED12 +// uint8_t Iv; // saves the inverter that is used for regulation +// float power_avg; +// uint8_t count_avg; +// double total_power; +// unsigned long lastTime; // tic toc +// double max_power; +// bool two_percent; // ask if not go lower then 2% +} zeroExport_t; +#endif +// Plugin ZeroExport - Ende + typedef struct { #if defined(PLUGIN_DISPLAY) display_t display; #endif char customLink[MAX_CUSTOM_LINK_LEN]; char customLinkText[MAX_CUSTOM_LINK_TEXT_LEN]; - #if defined(ESP32) - cfgzeroExport_t zexport; + #if defined(PLUGIN_ZEROEXPORT) + zeroExport_t zeroExport; #endif } plugins_t; @@ -315,7 +416,6 @@ class settings { if(root.containsKey(F("nrf"))) jsonNrf(root[F("nrf")]); #if defined(ESP32) if(root.containsKey(F("cmt"))) jsonCmt(root[F("cmt")]); - if(root.containsKey(F("zeroExport"))) jsonzeroExport(root[F("zeroExport")]); #endif if(root.containsKey(F("ntp"))) jsonNtp(root[F("ntp")]); if(root.containsKey(F("sun"))) jsonSun(root[F("sun")]); @@ -345,7 +445,6 @@ class settings { jsonNrf(root[F("nrf")].to(), true); #if defined(ESP32) jsonCmt(root[F("cmt")].to(), true); - jsonzeroExport(root.createNestedObject(F("zeroExport")), true); #endif jsonNtp(root[F("ntp")].to(), true); jsonSun(root[F("sun")].to(), true); @@ -473,22 +572,7 @@ class settings { snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC); mCfg.mqtt.interval = 0; // off - // Zero-Export - #if defined(ESP32) - snprintf(mCfg.plugin.zexport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); - snprintf(mCfg.plugin.zexport.tibber_pw, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); - snprintf(mCfg.plugin.zexport.json_path, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); - mCfg.plugin.zexport.enabled = false; - mCfg.plugin.zexport.count_avg = 10; - mCfg.plugin.zexport.lastTime = millis(); // do not change! - - mCfg.plugin.zexport.query_device = 1; // Standard shelly - mCfg.plugin.zexport.power_avg = 10; - mCfg.plugin.zexport.Iv = 0; - mCfg.plugin.zexport.max_power = 600; // Max 600W to stay safe - mCfg.plugin.zexport.two_percent = true; - #endif - + mCfg.inst.sendInterval = SEND_INTERVAL; mCfg.inst.rstYieldMidNight = false; mCfg.inst.rstValsNotAvail = false; mCfg.inst.rstValsCommStop = false; @@ -523,6 +607,77 @@ class settings { mCfg.plugin.display.disp_dc = DEF_PIN_OFF; mCfg.plugin.display.pirPin = DEF_PIN_OFF; #endif + + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) + mCfg.plugin.zeroExport.enabled = false; + for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + // General + mCfg.plugin.zeroExport.groups[group].enabled = false; + snprintf(mCfg.plugin.zeroExport.groups[group].name, ZEROEXPORT_GROUP_MAX_LEN_NAME, "%s", DEF_ZEXPORT); + // Powermeter + mCfg.plugin.zeroExport.groups[group].pm_type = zeroExportPowermeterType_t::None; + snprintf(mCfg.plugin.zeroExport.groups[group].pm_url, ZEROEXPORT_GROUP_MAX_LEN_PM_URL, "%s", DEF_ZEXPORT); + snprintf(mCfg.plugin.zeroExport.groups[group].pm_jsonPath, ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH, "%s", DEF_ZEXPORT); + snprintf(mCfg.plugin.zeroExport.groups[group].pm_user, ZEROEXPORT_GROUP_MAX_LEN_PM_USER, "%s", DEF_ZEXPORT); + snprintf(mCfg.plugin.zeroExport.groups[group].pm_pass, ZEROEXPORT_GROUP_MAX_LEN_PM_PASS, "%s", DEF_ZEXPORT); + // Inverters + for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mCfg.plugin.zeroExport.groups[group].inverters[inv].enabled = false; + mCfg.plugin.zeroExport.groups[group].inverters[inv].id = -1; + mCfg.plugin.zeroExport.groups[group].inverters[inv].target = -1; + mCfg.plugin.zeroExport.groups[group].inverters[inv].twoPercent = false; + mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax = 600; + } + // Battery + mCfg.plugin.zeroExport.groups[group].battEnabled = false; + mCfg.plugin.zeroExport.groups[group].battVoltageOn = 0; + mCfg.plugin.zeroExport.groups[group].battVoltageOff = 0; + // Advanced + mCfg.plugin.zeroExport.groups[group].refresh = 10; + mCfg.plugin.zeroExport.groups[group].powerTolerance = 10; + mCfg.plugin.zeroExport.groups[group].powerMax = 600; +// + mCfg.plugin.zeroExport.groups[group].state = zeroExportState::RESET; + mCfg.plugin.zeroExport.groups[group].lastRun = 0; + mCfg.plugin.zeroExport.groups[group].pmPower = 0; + mCfg.plugin.zeroExport.groups[group].pmPowerL1 = 0; + mCfg.plugin.zeroExport.groups[group].pmPowerL2 = 0; + mCfg.plugin.zeroExport.groups[group].pmPowerL3 = 0; + + } +// snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); +// snprintf(mCfg.plugin.zeroExport.tibber_pw, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); +// snprintf(mCfg.plugin.zeroExport.json_path, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); +// mCfg.plugin.zeroExport.enabled = false; +// mCfg.plugin.zeroExport.count_avg = 10; +// mCfg.plugin.zeroExport.lastTime = millis(); // do not change! + +// mCfg.plugin.zeroExport.query_device = 1; // Standard shelly +// mCfg.plugin.zeroExport.power_avg = 10; +// mCfg.plugin.zeroExport.Iv = 0; +// mCfg.plugin.zeroExport.max_power = 600; // Max 600W to stay safe +// mCfg.plugin.zeroExport.two_percent = true; +// uint8_t ip; +// uint8_t url; +// bool login; +// uint8_t username; +// uint8_t password; +// snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); +// snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); +// snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); +// uint8_t group; +// uint8_t phase; +// mCfg.plugin.zeroExport.groups[group].powermeter.nextRun = 0; +// mCfg.plugin.zeroExport.groups[group].powermeter.interval = 10000; +// mCfg.plugin.zeroExport.groups[group].powermeter.error = 0; +// mCfg.plugin.zeroExport.groups[group].powermeter.power = 0; +// mCfg.plugin.zeroExport.groups[group].inverters[inv].waitingTime = 0; +// mCfg.plugin.zeroExport.groups[group].inverters[inv].limit = -1; +// mCfg.plugin.zeroExport.groups[group].inverters[inv].limitAck = false; + // Plugin ZeroExport - Ende + #endif + } void loadAddedDefaults() { @@ -737,40 +892,6 @@ class settings { } } - #if defined(ESP32) - void jsonzeroExport(JsonObject obj, bool set = false) { - if(set) { - obj[F("en_zeroexport")] = (bool) mCfg.plugin.zexport.enabled; - obj[F("monitor_url")] = mCfg.plugin.zexport.monitor_url; - obj[F("json_path")] = mCfg.plugin.zexport.json_path; - obj[F("Iv")] = mCfg.plugin.zexport.Iv; - obj[F("power_avg")] = mCfg.plugin.zexport.power_avg; - obj[F("query_device")] = mCfg.plugin.zexport.query_device; - obj[F("count_avg")] = mCfg.plugin.zexport.count_avg; - obj[F("max_power")] = mCfg.plugin.zexport.max_power; - obj[F("total_power")] = mCfg.plugin.zexport.total_power; - obj[F("two_percent")] = (bool)mCfg.plugin.zexport.two_percent; - } - else - { - getVal(obj, F("en_zeroexport"), &mCfg.plugin.zexport.enabled); - getVal(obj, F("two_percent"), &mCfg.plugin.zexport.two_percent); - - getChar(obj, F("monitor_url"), mCfg.plugin.zexport.monitor_url, ZEXPORT_ADDR_LEN); - getChar(obj, F("json_path"), mCfg.plugin.zexport.json_path, ZEXPORT_ADDR_LEN); - - getVal(obj, F("Iv"), &mCfg.plugin.zexport.Iv); - getVal(obj, F("count_avg"), &mCfg.plugin.zexport.count_avg); - getVal(obj, F("max_power"), &mCfg.plugin.zexport.max_power); - - getVal(obj, F("power_avg"), &mCfg.plugin.zexport.power_avg); - getVal(obj, F("query_device"), &mCfg.plugin.zexport.query_device); - - getVal(obj, F("total_power"), &mCfg.plugin.zexport.total_power); - } - } - #endif - void jsonLed(JsonObject obj, bool set = false) { if(set) { obj[F("0")] = mCfg.led.led[0]; @@ -787,6 +908,116 @@ class settings { } } + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) + + void jsonZeroExportGroupInverter(JsonObject obj, uint8_t group, uint8_t inv, bool set = false) { + if(set) { + obj[F("enabled")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].enabled; + obj[F("id")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].id; + obj[F("target")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].target; + obj[F("twoPercent")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].twoPercent; + obj[F("powerMax")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax; + } else { + if (obj.containsKey(F("enabled"))) + getVal(obj, F("enabled"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].enabled); + if (obj.containsKey(F("id"))) + getVal(obj, F("id"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].id); + if (obj.containsKey(F("target"))) + getVal(obj, F("target"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].target); + if (obj.containsKey(F("twoPercent"))) + getVal(obj, F("twoPercent"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].twoPercent); + if (obj.containsKey(F("powerMax"))) + getVal(obj, F("powerMax"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax); + } + } + + void jsonZeroExportGroup(JsonObject obj, uint8_t group, bool set = false) { + if(set) { + // General + obj[F("enabled")] = mCfg.plugin.zeroExport.groups[group].enabled; + obj[F("name")] = mCfg.plugin.zeroExport.groups[group].name; + // Powermeter + obj[F("pm_type")] = mCfg.plugin.zeroExport.groups[group].pm_type; + obj[F("pm_url")] = mCfg.plugin.zeroExport.groups[group].pm_url; + obj[F("pm_jsonPath")] = mCfg.plugin.zeroExport.groups[group].pm_jsonPath; + obj[F("pm_user")] = mCfg.plugin.zeroExport.groups[group].pm_user; + obj[F("pm_pass")] = mCfg.plugin.zeroExport.groups[group].pm_pass; + // Inverters + JsonArray invArr = obj.createNestedArray(F("inverters")); + for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + jsonZeroExportGroupInverter(invArr.createNestedObject(), group, inv, set); + } + // Battery + obj[F("battEnabled")] = mCfg.plugin.zeroExport.groups[group].battEnabled; + obj[F("battVoltageOn")] = mCfg.plugin.zeroExport.groups[group].battVoltageOn; + obj[F("battVoltageOff")] = mCfg.plugin.zeroExport.groups[group].battVoltageOff; + // Advanced + obj[F("refresh")] = mCfg.plugin.zeroExport.groups[group].refresh; + obj[F("powerTolerance")] = mCfg.plugin.zeroExport.groups[group].powerTolerance; + obj[F("powerMax")] = mCfg.plugin.zeroExport.groups[group].powerMax; + } else { + // General + if (obj.containsKey(F("enabled"))) + getVal(obj, F("enabled"), &mCfg.plugin.zeroExport.groups[group].enabled); + if (obj.containsKey(F("name"))) + getChar(obj, F("name"), mCfg.plugin.zeroExport.groups[group].name, ZEXPORT_ADDR_LEN); + // Powermeter + if (obj.containsKey(F("pm_type"))) + getVal(obj, F("pm_type"), &mCfg.plugin.zeroExport.groups[group].pm_type); + if (obj.containsKey(F("pm_url"))) + getChar(obj, F("pm_url"), mCfg.plugin.zeroExport.groups[group].pm_url, ZEROEXPORT_GROUP_MAX_LEN_PM_URL); + if (obj.containsKey(F("pm_jsonPath"))) + getChar(obj, F("pm_jsonPath"), mCfg.plugin.zeroExport.groups[group].pm_jsonPath, ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH); + if (obj.containsKey(F("pm_user"))) + getChar(obj, F("pm_user"), mCfg.plugin.zeroExport.groups[group].pm_user, ZEROEXPORT_GROUP_MAX_LEN_PM_USER); + if (obj.containsKey(F("pm_pass"))) + getChar(obj, F("pm_pass"), mCfg.plugin.zeroExport.groups[group].pm_pass, ZEROEXPORT_GROUP_MAX_LEN_PM_PASS); + // Inverters + if (obj.containsKey(F("inverters"))) { + for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + jsonZeroExportGroupInverter(obj[F("inverters")][inv], group, inv, set); + } + } + // Battery + if (obj.containsKey(F("battEnabled"))) + getVal(obj, F("battEnabled"), &mCfg.plugin.zeroExport.groups[group].battEnabled); + if (obj.containsKey(F("battVoltageOn"))) + getVal(obj, F("battVoltageOn"), &mCfg.plugin.zeroExport.groups[group].battVoltageOn); + if (obj.containsKey(F("battVoltageOff"))) + getVal(obj, F("battVoltageOff"), &mCfg.plugin.zeroExport.groups[group].battVoltageOff); + // Advanced + if (obj.containsKey(F("refresh"))) + getVal(obj, F("refresh"), &mCfg.plugin.zeroExport.groups[group].refresh); + if (obj.containsKey(F("powerTolerance"))) + getVal(obj, F("powerTolerance"), &mCfg.plugin.zeroExport.groups[group].powerTolerance); + if (obj.containsKey(F("powerMax"))) + getVal(obj, F("powerMax"), &mCfg.plugin.zeroExport.groups[group].powerMax); + } + } + + void jsonZeroExport(JsonObject obj, bool set = false) { + if(set) { + obj[F("enabled")] = mCfg.plugin.zeroExport.enabled; + JsonArray grpArr = obj.createNestedArray(F("groups")); + for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + jsonZeroExportGroup(grpArr.createNestedObject(), group, set); + } + } + else + { + if (obj.containsKey(F("enabled"))) + getVal(obj, F("enabled"), &mCfg.plugin.zeroExport.enabled); + if (obj.containsKey(F("groups"))) { + for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + jsonZeroExportGroup(obj[F("groups")][group], group, set); + } + } + } + } + #endif + // Plugin ZeroExport - Ende + void jsonPlugin(JsonObject obj, bool set = false) { if(set) { #if defined(PLUGIN_DISPLAY) @@ -810,6 +1041,11 @@ class settings { #endif obj[F("cst_lnk")] = mCfg.plugin.customLink; obj[F("cst_lnk_txt")] = mCfg.plugin.customLinkText; + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) + jsonZeroExport(obj.createNestedObject("zeroExport"), set); + #endif + // Plugin ZeroExport - Ende } else { #if defined(PLUGIN_DISPLAY) JsonObject disp = obj["disp"]; @@ -832,6 +1068,11 @@ class settings { #endif getChar(obj, F("cst_lnk"), mCfg.plugin.customLink, MAX_CUSTOM_LINK_LEN); getChar(obj, F("cst_lnk_txt"), mCfg.plugin.customLinkText, MAX_CUSTOM_LINK_TEXT_LEN); + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) + jsonZeroExport(obj["zeroExport"], set); + #endif + // Plugin ZeroExport - Ende } } diff --git a/src/plugins/zeroExport/zeroExport.h b/src/plugins/zeroExport/zeroExport.h index 192b8d63..5818923a 100644 --- a/src/plugins/zeroExport/zeroExport.h +++ b/src/plugins/zeroExport/zeroExport.h @@ -1,4 +1,4 @@ -#if defined(ESP32) +#if defined(PLUGIN_ZEROEXPORT) #ifndef __ZEROEXPORT__ #define __ZEROEXPORT__ @@ -8,29 +8,232 @@ #include "AsyncJson.h" #include "SML.h" + template class ZeroExport { + +// bool enabled; // true +// uint8_t query_device; // 0 - Tibber, 1 - Shelly, 2 - other (rs232?) +// char monitor_url[ZEXPORT_ADDR_LEN]; // +// char json_path[ZEXPORT_ADDR_LEN]; // +// char tibber_pw[10]; // +// uint8_t Iv; // Id gemäß anlegen +// float power_avg; // +// uint8_t count_avg; // +// double total_power; // +// unsigned long lastTime; // tic toc +// double max_power; // 600 W +// bool two_percent; // P >= 2% von max_power + +// mCfg // -> siehe oben +// mSys // -> +// mConfig // -> + public: - ZeroExport() { } + ZeroExport() { + mIsInitialized = false; + } - void setup(cfgzeroExport_t *cfg, HMSYSTEM *sys, settings_t *config) { + void setup(zeroExport_t *cfg, HMSYSTEM *sys, settings_t *config) { mCfg = cfg; mSys = sys; mConfig = config; + + if (!mCfg->enabled) { + return; + } + + mIsInitialized = true; + } + + void loop(void) { + if ((!mIsInitialized) || (!mCfg->enabled)) { + return; + } + + for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + if (!mCfg->groups[group].enabled) { + continue; + } + + switch (mCfg->groups[group].state) { + case zeroExportState::RESET: +//DBGPRINT(F("State::RESET: ")); +//DBGPRINTLN(String(group)); + mCfg->groups[group].lastRun = millis(); + // Weiter zum nächsten State + mCfg->groups[group].state = zeroExportState::GETPOWERMETER; + break; + case zeroExportState::GETPOWERMETER: + if ((millis() - mCfg->groups[group].lastRun) > (mCfg->groups[group].refresh * 1000UL)) { +//DBGPRINT(F("State::GETPOWERMETER:")); +//DBGPRINTLN(String(group)); + if (getPowermeterWatts(group)) { + // Weiter zum nächsten State + mCfg->groups[group].state = zeroExportState::GETINVERTERPOWER; + } else { + // Wartezeit wenn Keine Daten vom Powermeter + mCfg->groups[group].lastRun = (millis() - (mCfg->groups[group].refresh * 100UL)); + } + } + break; + case zeroExportState::GETINVERTERPOWER: + if ((millis() - mCfg->groups[group].lastRun) > (mCfg->groups[group].refresh * 1000UL)) { +//DBGPRINT(F("State::GETINVERTERPOWER:")); +//DBGPRINTLN(String(group)); + if (getInverterPowerWatts(group)) { + // Weiter zum nächsten State + mCfg->groups[group].state = zeroExportState::FINISH; + } else { + // Wartezeit wenn Keine Daten vom Powermeter + mCfg->groups[group].lastRun = (millis() - (mCfg->groups[group].refresh * 100UL)); + } + } + break; +/* + case 4: + // + mCfg->groups[group].state = zeroExportState::RESET; + break; + case 5: + // + mCfg->groups[group].state = zeroExportState::RESET; + break; +*/ + default: + // +//DBGPRINT(F("State::?: ")); +//DBGPRINTLN(String(group)); + mCfg->groups[group].state = zeroExportState::RESET; + break; + } + + + + + + + + + + + + + + +/* + if (!mCfg->groups[group].powermeter.error) { +DBGPRINTLN(String("ok verarbeiten.")); + } + + if (mCfg->groups[group].powermeter.error >= ZEROEXPORT_POWERMETER_MAX_ERRORS) { +DBGPRINTLN(String("nok Notmodus.")); + } +*/ + +/* + // getPowermeterGroup + mCfg->zeGroup[group].PowermeterSum = getPowermeterSum(); + // getPowermeterPhase + for (uint8_t phase = 1; phase <= 3; phase++) { + mCfg->zeGroup[group].PowermeterPhase[phase] = getPowermeterPhase(); + } + // getInverterPower + for (uint8_t inv = 0; inv < MAX; inv++) { + mCfg->zeGroup[group].InverterpowerGroup = getInverterpowerGroup(); + for (uint8_t phase = 1; phase <= 3; phase++) { + mCfg->zeGroup[group].InverterpowerPhase[phase] = getInverterpowerPhase(); + } + } + // calcPowerSum + mCfg->zeGroup[group].LimitSumNew = mCfg->zeGroup[group].LimitSumOld + mCfg->zeGroup[group].PowermeterSum; + // calcPowerPhase + for (uint8_t phase = 1; phase <= 3; phase++) { + mCfg->zeGroup[group].LimitPhaseNew[phase] = mCfg->zeGroup[group].LimitPhaseOld[phase] - mCfg->zeGroup[group].PowermeterPhase[phase]; + } + // calcPowerInverter + for (uint8_t inv = 0; inv < MAX; inv++) { + + } +*/ + } + + // setPower + for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + if (!mCfg->groups[group].enabled) { + continue; + } +/* +// TODO: Inverter sortieren nach Leistung +// -> Aufsteigend bei Leistungserhöhung +// -> Absteigend bei Leistungsreduzierung + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + if (!mCfg->groups[group].inverters[inv].enabled) { + continue; + } + if (mCfg->groups[group].inverters[inv].waitingTime) { + mCfg->groups[group].inverters[inv].waitingTime--; + continue; + } + // Leistung erhöhen + if (mCfg->groups[group].power < mCfg->groups[group].powerLimitAkt - mCfg->groups[group].powerHyst) { +// mCfg->groups[group].powerLimitAkt = mCfg->groups[group].power + + + mCfg->groups[group].inverters[inv].waitingTime = ZEROEXPORT_DEF_INV_WAITINGTIME_MS; + return; + } + // Leistung reduzieren + if (mCfg->groups[group].power > mCfg->groups[group].powerLimitAkt + mCfg->groups[group].powerHyst) { + + + + mCfg->groups[group].inverters[inv].waitingTime = ZEROEXPORT_DEF_INV_WAITINGTIME_MS; + return; + } + + + + if ((Power < PowerLimit - Hyst) || (Power > PowerLimit + Hyst)) { + if (Limit < 2%) { + setPower(Off); + setPowerLimit(100%) + } else { + setPower(On); + setPowerLimit(Limit); + mCfg->Inv[inv].waitingTime = ZEROEXPORT_DEF_INV_WAITINGTIME_MS; + } + } + + + + } +*/ + } + } void tickerSecond() { - //DPRINTLN(DBG_INFO, (F("tickerSecond()"))); - if (millis() - mCfg->lastTime < mCfg->count_avg * 1000UL) { - zero(); // just refresh when it is needed. To get cpu load low. + if ((!mIsInitialized) || (!mCfg->enabled)) { + return; } + + + + + + //DPRINTLN(DBG_INFO, (F("tickerSecond()"))); +// if (millis() - mCfg->lastTime < mCfg->count_avg * 1000UL) { +/// zero(); // just refresh when it is needed. To get cpu load low. +// } } // Sums up the power values ​​of all phases and returns them. // If the value is negative, all power values ​​from the inverter are taken into account double getPowertoSetnewValue() { +/* float ivPower = 0; Inverter<> *iv; record_t<> *rec; @@ -41,8 +244,9 @@ class ZeroExport { continue; ivPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec); } - - return ((unsigned)(mCfg->total_power - mCfg->power_avg) >= mCfg->power_avg) ? ivPower + mCfg->total_power : ivPower - mCfg->total_power; +*/ +// return ((unsigned)(mCfg->total_power - mCfg->power_avg) >= mCfg->power_avg) ? ivPower + mCfg->total_power : ivPower - mCfg->total_power; + return 0; } //C2T2-B91B private: @@ -51,6 +255,7 @@ class ZeroExport { // TODO: Need to improve here. 2048 for a JSON Obj is to big!? bool zero() { +/* httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); httpClient.setUserAgent("Ahoy-Agent"); httpClient.setConnectTimeout(1000); @@ -103,6 +308,7 @@ class ZeroExport { return false; } httpClient.end(); +*/ return true; } @@ -111,12 +317,342 @@ class ZeroExport { } + + + + bool getPowermeterWatts(uint8_t group) { + bool ret = false; +DBGPRINT(String("getPowermeterWatts: ")); +DBGPRINTLN(String(group)); + switch (mCfg->groups[group].pm_type) { + case 1: + ret = getPowermeterWattsShelly(group); + break; +// case 2: +// ret = getPowermeterWattsTasmota(group); +// break; +// case 3: +// ret = getPowermeterWattsMqtt(group); +// break; +// case 4: +// ret = getPowermeterWattsHichi(group); +// break; +// case 5: +// ret = getPowermeterWattsTibber(group); +// break; +/// default: +/// ret = false; +/// break; + } + return ret; + } + + int getPowermeterWattsShelly(uint8_t group) { + bool ret = false; + HTTPClient http; +// httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + httpClient.setUserAgent("Ahoy-Agent"); +// TODO: Ahoy-0.8.850024-zero +// httpClient.setConnectTimeout(1000); +// httpClient.setTimeout(1000); +// httpClient.addHeader("Content-Type", "application/json"); +// httpClient.addHeader("Accept", "application/json"); + http.begin(mCfg->groups[group].pm_url); + if (http.GET() == HTTP_CODE_OK) + { + + // Parsing + DynamicJsonDocument doc(2048); + DeserializationError error = deserializeJson(doc, http.getString()); + if (error) + { +DBGPRINT(String("deserializeJson() failed: ")); +DBGPRINTLN(String(error.c_str())); + return ret; + } + + // Shelly 3EM + if (doc.containsKey(F("total_power"))) { + mCfg->groups[group].pmPower = doc["total_power"]; + ret = true; + // Shelly pro 3EM + } else if (doc.containsKey(F("em:0"))) { + mCfg->groups[group].pmPower = doc["em:0"]["total_act_power"]; + ret = true; + // Keine Daten + } else { + mCfg->groups[group].pmPower = 0; + } + + // Shelly 3EM + if (doc.containsKey(F("emeters"))) { + mCfg->groups[group].pmPowerL1 = doc["emeters"][0]["power"]; + ret = true; + // Shelly pro 3EM + } else if (doc.containsKey(F("em:0"))) { + mCfg->groups[group].pmPowerL1 = doc["em:0"]["a_act_power"]; + ret = true; + // Shelly plus1pm plus2pm + } else if (doc.containsKey(F("switch:0"))) { + mCfg->groups[group].pmPowerL1 = doc["switch:0"]["apower"]; + mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL1; + ret = true; + // Shelly Alternative + } else if (doc.containsKey(F("apower"))) { + mCfg->groups[group].pmPowerL1 = doc["apower"]; + mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL1; + ret = true; + // Keine Daten + } else { + mCfg->groups[group].pmPowerL1 = 0; + } +DBGPRINT(String("pmPowerL1: ")); +DBGPRINTLN(String(mCfg->groups[group].pmPowerL1)); + + // Shelly 3EM + if (doc.containsKey(F("emeters"))) { + mCfg->groups[group].pmPowerL2 = doc["emeters"][1]["power"]; + ret = true; + // Shelly pro 3EM + } else if (doc.containsKey(F("em:0"))) { + mCfg->groups[group].pmPowerL2 = doc["em:0"]["b_act_power"]; + ret = true; + // Shelly plus1pm plus2pm + } else if (doc.containsKey(F("switch:1"))) { + mCfg->groups[group].pmPowerL2 = doc["switch.1"]["apower"]; + mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL2; + ret = true; +// // Shelly Alternative +// } else if (doc.containsKey(F("apower"))) { +// mCfg->groups[group].pmPowerL2 = doc["apower"]; +// mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL2; +// ret = true; + // Keine Daten + } else { + mCfg->groups[group].pmPowerL2 = 0; + } +DBGPRINT(String("pmPowerL2: ")); +DBGPRINTLN(String(mCfg->groups[group].pmPowerL2)); + + // Shelly 3EM + if (doc.containsKey(F("emeters"))) { + mCfg->groups[group].pmPowerL3 = doc["emeters"][2]["power"]; + ret = true; + // Shelly pro 3EM + } else if (doc.containsKey(F("em:0"))) { + mCfg->groups[group].pmPowerL3 = doc["em:0"]["c_act_power"]; + ret = true; + // Shelly plus1pm plus2pm + } else if (doc.containsKey(F("switch:2"))) { + mCfg->groups[group].pmPowerL3 = doc["switch:2"]["apower"]; + mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL3; + ret = true; +// // Shelly Alternative +// } else if (doc.containsKey(F("apower"))) { +// mCfg->groups[group].pmPowerL3 = doc["apower"]; +// mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL3; +// ret = true; + // Keine Daten + } else { + mCfg->groups[group].pmPowerL3 = 0; + } +DBGPRINT(String("pmPowerL3: ")); +DBGPRINTLN(String(mCfg->groups[group].pmPowerL3)); + +DBGPRINT(String("pmPower: ")); +DBGPRINTLN(String(mCfg->groups[group].pmPower)); + } + http.end(); + + return ret; + } + + int getPowermeterWattsTasmota(void) { +/* + HTTPClient http; + char url[100] = "http://"; + strcat(url, mCfg->monitor_url); + strcat(url, "/cm?cmd=status%2010"); + http.begin(url); + + if (http.GET() == 200) { + // Parsing + DynamicJsonDocument doc(384); + DeserializationError error = deserializeJson(doc, http.getString()); + if (error) { + Serial.print("deserializeJson() failed: "); + Serial.println(error.c_str()); + return 0; + } + + JsonObject Tasmota_ENERGY = doc["StatusSNS"]["ENERGY"]; + int Tasmota_Power = Tasmota_ENERGY["Power"]; // 0 + return Tasmota_Power; +*/ + /* + String url = "http://" + String(TASMOTA_IP) + "/cm?cmnd=status%2010"; + ParsedData = http.get(url).json(); + int Watts = ParsedData[TASMOTA_JSON_STATUS][TASMOTA_JSON_PAYLOAD_MQTT_PREFIX][TASMOTA_JSON_POWER_MQTT_LABEL].toInt(); + return Watts; + */ +// } +// http.end(); + return 0; + } + + int getPowermeterWattsMqtt(void) { + // TODO: + return 0; + } + + int getPowermeterWattsHichi(void) { + // TODO: + return 0; + } + + int getPowermeterWattsTibber(void) { + // TODO: + return 0; + } + + + + bool getInverterPowerWatts(uint8_t group) { + bool ret = false; +DBGPRINT(String("getInverterPowerWatts: ")); +DBGPRINTLN(String(group)); + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { +DBGPRINT(String("iv: ")); +DBGPRINT(String(inv)); +DBGPRINT(String(" ")); + // Wenn Inverter deaktiviert -> Eintrag ignorieren + if (!mCfg->groups[group].inverters[inv].enabled) { +DBGPRINT(String("(ze disabled).")); + continue; + } + // Daten holen + Inverter<> *iv; + record_t<> *rec; +// TODO: getInverterById + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { + iv = mSys->getInverterByPos(i); + // Wenn kein Inverter -> ignorieren + if (iv == NULL) { + continue; + } + // Wenn falscher Inverter -> ignorieren + if (iv->id != (uint8_t)mCfg->groups[group].inverters[inv].id) { + continue; + } +DBGPRINT(String("(")); +DBGPRINT(String(iv->id)); +DBGPRINT(String("( gefunden)")); + // wenn Inverter deaktiviert -> Daten ignorieren +// if (!iv->enabled()) { +//DBGPRINT(String(" aber deaktiviert ")); +// continue; +// } + // wenn Inverter nicht verfügbar -> Daten ignorieren + if (!iv->isAvailable()) { +DBGPRINT(String(" aber nicht erreichbar ")); + continue; + } + // wenn Inverter nicht produziert -> Daten ignorieren +// if (!iv->isProducing()) { +//DBGPRINT(String(" aber produziert nix ")); +// continue; +// } + // Daten abrufen + rec = iv->getRecordStruct(RealTimeRunData_Debug); + +// TODO: gibts hier nen Timestamp? Wenn die Daten nicht aktueller sind als beim letzten Durchlauf dann brauch ich nix machen + +mCfg->groups[group].inverters[inv].power = iv->getChannelFieldValue(CH0, FLD_PAC, rec); +DBGPRINT(String("(P=")); +DBGPRINT(String(mCfg->groups[group].inverters[inv].power)); +DBGPRINT(String("W ")); + +mCfg->groups[group].inverters[inv].limit = iv->actPowerLimit; +DBGPRINT(String("Li=")); +DBGPRINT(String(mCfg->groups[group].inverters[inv].limit)); +DBGPRINT(String("% ")); + +mCfg->groups[group].inverters[inv].limitAck = iv->powerLimitAck; +DBGPRINT(String("Ack= ")); +DBGPRINT(String(mCfg->groups[group].inverters[inv].limitAck)); +DBGPRINT(String(" ")); + +mCfg->groups[group].inverters[inv].dcVoltage = iv->getChannelFieldValue(CH1, FLD_UDC, rec); +DBGPRINT(String("U=")); +DBGPRINT(String(mCfg->groups[group].inverters[inv].dcVoltage)); +DBGPRINT(String("V) ")); +// TODO: Eingang muss konfigurierbar sein + + + ret = true; + } + } +DBGPRINTLN(String("")); + return ret; + } + + + + + + // private member variables - cfgzeroExport_t *mCfg; + bool mIsInitialized = false; + zeroExport_t *mCfg; settings_t *mConfig; HMSYSTEM *mSys; }; + + +/* +Shelly 1pm +Der Shelly 1pm verfügt über keine eigene Spannungsmessung sondern geht von 220V * Korrekturfaktor aus. Dadurch wird die Leistungsmessung verfälscht und der Shelly ist ungeeignet. + + +http://192.168.xxx.xxx/status +Shelly 3em (oder em3) : +ok +{"wifi_sta":{"connected":true,"ssid":"Odyssee2001","ip":"192.168.100.85","rssi":-23},"cloud":{"enabled":false,"connected":false},"mqtt":{"connected":true},"time":"17:13","unixtime":1709223219,"serial":27384,"has_update":false,"mac":"3494547B94EE","cfg_changed_cnt":1,"actions_stats":{"skipped":0},"relays":[{"ison":false,"has_timer":false,"timer_started":0,"timer_duration":0,"timer_remaining":0,"overpower":false,"is_valid":true,"source":"input"}],"emeters":[{"power":51.08,"pf":0.27,"current":0.78,"voltage":234.90,"is_valid":true,"total":1686297.2,"total_returned":428958.4},{"power":155.02,"pf":0.98,"current":0.66,"voltage":235.57,"is_valid":true,"total":878905.6,"total_returned":4.1},{"power":6.75,"pf":0.26,"current":0.11,"voltage":234.70,"is_valid":true,"total":206151.1,"total_returned":0.0}],"total_power":212.85,"emeter_n":{"current":0.00,"ixsum":1.29,"mismatch":false,"is_valid":false},"fs_mounted":true,"v_data":1,"ct_calst":0,"update":{"status":"idle","has_update":false,"new_version":"20230913-114244/v1.14.0-gcb84623","old_version":"20230913-114244/v1.14.0-gcb84623","beta_version":"20231107-165007/v1.14.1-rc1-g0617c15"},"ram_total":49920,"ram_free":30192,"fs_size":233681,"fs_free":154616,"uptime":13728721} + + +Shelly plus 2pm : +ok +{"ble":{},"cloud":{"connected":false},"input:0":{"id":0,"state":false},"input:1":{"id":1,"state":false},"mqtt":{"connected":true},"switch:0":{"id":0, "source":"MQTT", "output":false, "apower":0.0, "voltage":237.0, "freq":50.0, "current":0.000, "pf":0.00, "aenergy":{"total":62758.285,"by_minute":[0.000,0.000,0.000],"minute_ts":1709223337},"temperature":{"tC":35.5, "tF":96.0}},"switch:1":{"id":1, "source":"MQTT", "output":false, "apower":0.0, "voltage":237.1, "freq":50.0, "current":0.000, "pf":0.00, "aenergy":{"total":61917.211,"by_minute":[0.000,0.000,0.000],"minute_ts":1709223337},"temperature":{"tC":35.5, "tF":96.0}},"sys":{"mac":"B0B21C10A478","restart_required":false,"time":"17:15","unixtime":1709223338,"uptime":8746115,"ram_size":245016,"ram_free":141004,"fs_size":458752,"fs_free":135168,"cfg_rev":7,"kvs_rev":0,"schedule_rev":0,"webhook_rev":0,"available_updates":{"stable":{"version":"1.2.2"}}},"wifi":{"sta_ip":"192.168.100.87","status":"got ip","ssid":"Odyssee2001","rssi":-62},"ws":{"connected":false}} + +http://192.168.xxx.xxx/rpc/Shelly.GetStatus +Shelly plus 1pm : +nok keine negativen Leistungswerte +{"ble":{},"cloud":{"connected":false},"input:0":{"id":0,"state":false},"mqtt":{"connected":true},"switch:0":{"id":0, "source":"MQTT", "output":false, "apower":0.0, "voltage":235.9, "current":0.000, "aenergy":{"total":20393.619,"by_minute":[0.000,0.000,0.000],"minute_ts":1709223441},"temperature":{"tC":34.6, "tF":94.3}},"sys":{"mac":"FCB467A66E3C","restart_required":false,"time":"17:17","unixtime":1709223443,"uptime":8644483,"ram_size":246256,"ram_free":143544,"fs_size":458752,"fs_free":147456,"cfg_rev":9,"kvs_rev":0,"schedule_rev":0,"webhook_rev":0,"available_updates":{"stable":{"version":"1.2.2"}}},"wifi":{"sta_ip":"192.168.100.88","status":"got ip","ssid":"Odyssee2001","rssi":-42},"ws":{"connected":false}} + + + + + + + + + + + + + + + + + + + + + + +*/ + #endif /*__ZEROEXPORT__*/ #endif /* #if defined(ESP32) */ \ No newline at end of file diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 36a7ec61..8b0983ca 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -618,22 +618,6 @@ class RestApi { obj[F("interval")] = String(mConfig->mqtt.interval); } - #if defined(ESP32) - void getzeroExport(JsonObject obj) { - obj[F("en_zeroexport")] = (bool) mConfig->plugin.zexport.enabled; - obj[F("two_percent")] = (bool) mConfig->plugin.zexport.two_percent; - obj[F("monitor_url")] = String(mConfig->plugin.zexport.monitor_url); - obj[F("json_path")] = String(mConfig->plugin.zexport.json_path); - obj[F("count_avg")] = (uint8_t)mConfig->plugin.zexport.count_avg; - obj[F("max_power")] = (double)mConfig->plugin.zexport.max_power; - obj[F("Iv")] = (uint8_t)mConfig->plugin.zexport.Iv; - obj[F("power_avg")] = (float)mConfig->plugin.zexport.power_avg; - obj[F("query_device")] = (float)mConfig->plugin.zexport.query_device; - obj[F("total_power")] = (double)mConfig->plugin.zexport.total_power; - //obj[F("device")] = (uint8_t)mCfg.plugin.zexport.device; - } - #endif - void getNtp(JsonObject obj) { obj[F("addr")] = String(mConfig->ntp.addr); obj[F("port")] = String(mConfig->ntp.port); @@ -730,6 +714,49 @@ class RestApi { } #endif + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) + void getZeroExport(JsonObject obj) { + obj[F("enabled")] = (bool) mConfig->plugin.zeroExport.enabled; + // Groups + obj[F("max_groups")] = ZEROEXPORT_MAX_GROUPS; + JsonArray arrGroup = obj.createNestedArray(F("groups")); + for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + JsonObject objGroup = arrGroup.createNestedObject(); + // General + objGroup[F("id")] = (uint8_t)group; + objGroup[F("enabled")] = (bool)mConfig->plugin.zeroExport.groups[group].enabled; + objGroup[F("name")] = String(mConfig->plugin.zeroExport.groups[group].name); + // Powermeter + objGroup[F("pm_type")] = (uint8_t)mConfig->plugin.zeroExport.groups[group].pm_type; + objGroup[F("pm_url")] = String(mConfig->plugin.zeroExport.groups[group].pm_url); + objGroup[F("pm_jsonPath")] = String(mConfig->plugin.zeroExport.groups[group].pm_jsonPath); + objGroup[F("pm_user")] = String(mConfig->plugin.zeroExport.groups[group].pm_user); + objGroup[F("pm_pass")] = String(mConfig->plugin.zeroExport.groups[group].pm_pass); + // Inverters + objGroup[F("max_inverters")] = ZEROEXPORT_GROUP_MAX_INVERTERS; + JsonArray arrInv = objGroup.createNestedArray(F("inverters")); + for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + JsonObject objGroupInv = arrInv.createNestedObject(); + objGroupInv[F("enabled")] = (bool)mConfig->plugin.zeroExport.groups[group].inverters[inv].enabled; + objGroupInv[F("id")] = (int8_t)mConfig->plugin.zeroExport.groups[group].inverters[inv].id; + objGroupInv[F("target")] = (int8_t)mConfig->plugin.zeroExport.groups[group].inverters[inv].target; + objGroupInv[F("twoPercent")] = (bool)mConfig->plugin.zeroExport.groups[group].inverters[inv].twoPercent; + objGroupInv[F("powerMax")] = (uint16_t)mConfig->plugin.zeroExport.groups[group].inverters[inv].powerMax; + } + // Battery + objGroup[F("battEnabled")] = (bool)mConfig->plugin.zeroExport.groups[group].battEnabled; + objGroup[F("battVoltageOn")] = ah::round3((float)mConfig->plugin.zeroExport.groups[group].battVoltageOn); + objGroup[F("battVoltageOff")] = ah::round3((float)mConfig->plugin.zeroExport.groups[group].battVoltageOff); + // Advanced + objGroup[F("refresh")] = (uint8_t)mConfig->plugin.zeroExport.groups[group].refresh; + objGroup[F("powerTolerance")] = (uint8_t)mConfig->plugin.zeroExport.groups[group].powerTolerance; + objGroup[F("powerMax")] = (uint16_t)mConfig->plugin.zeroExport.groups[group].powerMax; + } + } + #endif + // Plugin ZeroExport - Ende + void getMqttInfo(JsonObject obj) { obj[F("enabled")] = (mConfig->mqtt.broker[0] != '\0'); obj[F("connected")] = mApp->getMqttIsConnected(); @@ -798,10 +825,11 @@ class RestApi { #if defined(PLUGIN_DISPLAY) getDisplay(obj.createNestedObject(F("display"))); #endif - - #if defined(ESP32) - getzeroExport(obj.createNestedObject(F("zeroExport"))); + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) + getZeroExport(obj.createNestedObject(F("zeroExport"))); #endif + // Plugin ZeroExport - Ende } #if !defined(ETHERNET) @@ -955,7 +983,42 @@ class RestApi { iv->config->powerLevel = jsonIn[F("pa")]; iv->config->disNightCom = jsonIn[F("disnightcom")]; mApp->saveSettings(false); // without reboot - } else { + } + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) + else if(F("ze_save_group") == jsonIn[F("cmd")]) { + // General + uint8_t group = jsonIn[F("id")]; + mConfig->plugin.zeroExport.groups[group].enabled = jsonIn[F("enabled")]; + snprintf(mConfig->plugin.zeroExport.groups[group].name, ZEROEXPORT_GROUP_MAX_LEN_NAME, "%s", jsonIn[F("name")].as()); + // Powermeter + mConfig->plugin.zeroExport.groups[group].pm_type = jsonIn[F("pm_type")]; + snprintf(mConfig->plugin.zeroExport.groups[group].pm_url, ZEROEXPORT_GROUP_MAX_LEN_PM_URL, "%s", jsonIn[F("pm_url")].as()); + snprintf(mConfig->plugin.zeroExport.groups[group].pm_jsonPath, ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH, "%s", jsonIn[F("pm_jsonPath")].as()); + snprintf(mConfig->plugin.zeroExport.groups[group].pm_user, ZEROEXPORT_GROUP_MAX_LEN_PM_USER, "%s", jsonIn[F("pm_user")].as()); + snprintf(mConfig->plugin.zeroExport.groups[group].pm_pass, ZEROEXPORT_GROUP_MAX_LEN_PM_PASS, "%s", jsonIn[F("pm_pass")].as()); + // Inverters + for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mConfig->plugin.zeroExport.groups[group].inverters[inv].enabled = jsonIn[F("inverters")][inv][F("enabled")]; + mConfig->plugin.zeroExport.groups[group].inverters[inv].id = jsonIn[F("inverters")][inv][F("id")]; + mConfig->plugin.zeroExport.groups[group].inverters[inv].target = jsonIn[F("inverters")][inv][F("target")]; + mConfig->plugin.zeroExport.groups[group].inverters[inv].twoPercent = jsonIn[F("inverters")][inv][F("twoPercent")]; + mConfig->plugin.zeroExport.groups[group].inverters[inv].powerMax = jsonIn[F("inverters")][inv][F("powerMax")]; + } + // Battery + mConfig->plugin.zeroExport.groups[group].battEnabled = jsonIn[F("battEnabled")]; + mConfig->plugin.zeroExport.groups[group].battVoltageOn = jsonIn[F("battVoltageOn")]; + mConfig->plugin.zeroExport.groups[group].battVoltageOff = jsonIn[F("battVoltageOff")]; + // Advanced + mConfig->plugin.zeroExport.groups[group].refresh = jsonIn[F("refresh")]; + mConfig->plugin.zeroExport.groups[group].powerTolerance = jsonIn[F("powerTolerance")]; + mConfig->plugin.zeroExport.groups[group].powerMax = jsonIn[F("powerMax")]; + // Global + mApp->saveSettings(false); // without reboot + } + #endif + // Plugin ZeroExport - Ende + else { jsonOut[F("error")] = F("ERR_UNKNOWN_CMD"); return false; } diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 18500cf7..0d58c0a9 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -304,50 +304,20 @@ - - + +
-
- Zero Export -
- -
-
Enable zero export
-
-

Please select your favorite query interface:

-
- -
-
Monitor IP:
- - - - - - - -
- A JSON-Format is required to work properly.
- HICHI: http://IP_Address/cm?cmnd=status%208
- -
Prio Inverter
-
Which Inverter should be regulated.
- -
JSON Path:
-
Only for HICHI needed!
- -
2% protection:
-
-
-
Max Power:
-
-
-
Refresh rate (sec.)
-
Power tolerances (Watt)
-
-

Total: n/a

-
+
+ {#ZE} +
+
+
{#ZE_ENABLED}
+
+
+
+
+
{#BTN_REBOOT_SUCCESSFUL_SAVE}
@@ -1201,54 +1171,383 @@ document.getElementById("date").innerHTML = toIsoDateStr((new Date((++ts) * 1000))); } - function parsezeroExport(obj, type, ) { - if ("ESP8266" == type) { - var e = document.getElementById("zeroExport"); - e.remove(); + /*IF_PLUGIN_ZEROEXPORT*/ + // Plugin ZeroExport + function ZeroExportGroup_Modal(obj, ivObj) { - var e = document.getElementById("zeroExport_button"); - e.textContent += " (only for ESP32 available)"; - e.disabled = true; - element.classList.add("disabled"); + // Tab_General + var cbEnabled = ml("input", {name: "enabled", type: "checkbox"}, null); + cbEnabled.checked = (obj.enabled); + + // Tab_Powermeter + + // Tab_Inverter + maxInv = obj["max_inverters"]; + + var lines = []; + + lines.push(ml("tr", {}, [ + ml("th", {style: "width: 10%;"}, ml("input", {name: "invMax", id: "invMax", type: "hidden", value: maxInv}, null)), + ml("th", {style: "width: 10%;"}, "{#ZE_GROUP_TAB_INVERTER_ENABLED}"), + ml("th", {}, "{#ZE_GROUP_TAB_INVERTER_NAME}"), + ml("th", {}, "{#ZE_GROUP_TAB_INVERTER_SUM}"), + ml("th", {style: "width: 10%;"}, "{#ZE_GROUP_TAB_INVERTER_TWOPERCENT}"), + ml("th", {style: "width: 15%;"}, "{#ZE_GROUP_TAB_INVERTER_POWERMAX}"), + ])); - return; + for(var inv = 0; inv < maxInv; inv++) { + lines.push(ml("tr", {}, [ + ml("td", {}, String(inv)), + ml("td", {}, + ml("div", {}, [ + ml("input", {name: "invEnabled"+inv, class: "text", id: "invEnabled"+inv, type: "checkbox"}, null) + ]), + ), + ml("td", {}, + ml("div", {}, [ + ml("select", {name: "invId"+inv, class: "text", id: "invId"+inv}, null), + ]), + ), + ml("td", {}, + ml("div", {}, [ + ml("select", {name: "invTarget"+inv, class: "text", id: "invTarget"+inv}, null), + ]), + ), + ml("td", {}, + ml("div", {}, [ + ml("input", {name: "invTwoPercent"+inv, class: "text", id: "invTwoPercent"+inv, type: "checkbox"}, null) + ]), + ), + ml("td", {}, + ml("div", {}, [ + ml("input", {name: "invPowerMax"+inv, class: "text", id: "invPowerMax"+inv, type: "number", min: "0", max: "65535"}, null) + ]), + ), + ])); } - document.getElementsByName("en_zeroexport")[0].checked = obj["en_zeroexport"]; - document.getElementsByName("two_percent")[0].checked = obj["two_percent"]; + // Tab_Battery + var cb_battEnabled = ml("input", {name: "battEnabled", type: "checkbox"}, null); + cb_battEnabled.checked = (obj.battEnabled); - document.getElementsByName("dev_Tibber")[0].checked = (obj["query_device"] == 1); - document.getElementsByName("dev_Shelly")[0].checked = (obj["query_device"] == 2); - document.getElementsByName("dev_Other")[0].checked = (obj["query_device"] == 3); + // Tab_Advanced + // Tab + var html = ml("div", {}, [ + tabs(["{#ZE_GROUP_TAB_GENERAL}", "{#ZE_GROUP_TAB_POWERMETER}", "{#ZE_GROUP_TAB_INVERTER}", "{#ZE_GROUP_TAB_BATTERY}", "{#ZE_GROUP_TAB_ADVANCED}"]), + // General + ml("div", {id: "div{#ZE_GROUP_TAB_GENERAL}", class: "tab-content"}, [ + divRow("{#ZE_GROUP_TAB_GENERAL_GRUPPE}", String(obj.id)), + divRow("{#ZE_GROUP_TAB_GENERAL_ENABLE}", cbEnabled), + divRow("{#ZE_GROUP_TAB_GENERAL_NAME}", ml("input", {name: "name", class: "text", type: "text", value: obj.name}, null)), + ]), + // Powermeter + ml("div", {id: "div{#ZE_GROUP_TAB_POWERMETER}", class: "tab-content hide"}, [ + divRow("{#ZE_GROUP_TAB_POWERMETER_TYPE}", + ml("select", {name: "pm_type", class: "text", id: "pm_type"}, null), + ), + divRow("{#ZE_GROUP_TAB_POWERMETER_URL}", [ + ml("input", {name: "pm_url", class: "text", type: "text", value: obj.pm_url, maxlength: "100"}, null), +// TODO: Hilfstexte -> übersetzen mit lang.json + ml("p", {}, "(3em) - http://IP/status"), + ml("p", {}, "(pro3em) - http://IP/rpc/Shelly.GetStatus"), + ml("p", {}, "(plus1pm) - http://IP/rpc/Shelly.GetStatus"), + ml("p", {}, "(plus2pm) - http://IP/rpc/Shelly.GetStatus"), + ml("p", {}, "(plus1pmAlternative) - http://IP/rpc/switch.GetStatus?id=0"), + ml("p", {}, "(plus2pmAlternative) - http://IP/rpc/switch.GetStatus?id=0"), +// ml("p", {}, "A JSON-Format is required to work properly.
HICHI: http://IP_Address/cm?cmnd=status%208"), + ]), + divRow("{#ZE_GROUP_TAB_POWERMETER_JSONPATH}", [ + ml("input", {name: "pm_jsonPath", class: "text", type: "text", value: obj.pm_jsonPath}, null), +// TODO: Hilfstexte -> übersetzen mit lang.json +// ml("p", {}, "Only for HICHI needed!"), + ]), + divRow("{#ZE_GROUP_TAB_POWERMETER_USER}", + ml("input", {name: "pm_user", class: "text", type: "text", value: obj.pm_user}, null), + ), + divRow("{#ZE_GROUP_TAB_POWERMETER_PASS}", + ml("input", {name: "pm_pass", class: "text", type: "text", value: obj.pm_pass}, null), + ), + ]), + // Inverter + ml("div", {id: "div{#ZE_GROUP_TAB_INVERTER}", class: "tab-content hide"}, [ + ml("table", {class: "table"}, ml("tbody", {}, lines)), + ]), + // Battery + ml("div", {id: "div{#ZE_GROUP_TAB_BATTERY}", class: "tab-content hide"}, [ + divRow("{#ZE_GROUP_TAB_BATTERY_BATTENABLED}", cb_battEnabled), + divRow("{#ZE_GROUP_TAB_BATTERY_BATTVOLTAGEON}", ml("input", {name: "battVoltageOn", class: "text", type: "number", min: "0", max: "100", step: "0.1", value: obj.battVoltageOn}, null)), + divRow("{#ZE_GROUP_TAB_BATTERY_BATTVOLTAGEOFF}", ml("input", {name: "battVoltageOff", class: "text", type: "number", min: "0", max: "100", step: "0.1", value: obj.battVoltageOff}, null)), + ]), + // Advanced + ml("div", {id: "div{#ZE_GROUP_TAB_ADVANCED}", class: "tab-content hide"}, [ + divRow("{#ZE_GROUP_TAB_ADVANCED_REFRESH}", ml("input", {name: "refresh", class: "text", type: "number", min: "0", max: "255", value: obj.refresh}, null)), + divRow("{#ZE_GROUP_TAB_ADVANCED_POWERTOLERANCE}", ml("input", {name: "powerTolerance", class: "text", type: "number", min: "0", max: "255", value: obj.powerTolerance}, null)), + divRow("{#ZE_GROUP_TAB_ADVANCED_POWERMAX}", ml("input", {name: "powerMax", class: "text", type: "number", min: "0", max: "65535", value: obj.powerMax}, null)), + ]), + // Global + ml("div", {class: "row mt-5"}, [ + ml("div", {class: "col-8", id: "res"}, ""), + ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "{#ZE_GROUP_EDIT_BTN_SAVE}", class: "btn", onclick: function() { save(); }}, null)) + ]) + ]); - getAjax("/api/inverter/list", parseZeroIv); + modal("{#ZE_GROUP_EDIT_MODAL}: " + obj.id, html); +// ser.dispatchEvent(new Event('change')); - for(var i of [["monitor_url", "monitor_url"], ["power_avg", "power_avg"], ["count_avg", "count_avg"], ["json_path", "json_path"], ["max_power", "max_power"], ["query_device", "query_device"]]) - if(null != obj[i[1]]) - document.getElementsByName(i[0])[0].value = obj[i[1]]; +// Inhalt für pm_type aus config laden und in eine Funktion ausgliedern + var e = document.getElementById("pm_type"); + selDelAllOpt(e); +// TODO: übersetzen? + e.appendChild(opt("0", "---")); + e.appendChild(opt("1", "Shelly")); + e.appendChild(opt("2", "Tasmota")); + e.appendChild(opt("3", "Mqtt")); + e.appendChild(opt("4", "Hichi")); + e.appendChild(opt("5", "Tibber")); + for (var i = 0; i < e.options.length; i++) { + if (e.options[i].value == obj.pm_type) { + e.selectedIndex = i; + } + } + + // Tab_Inverters + // - Enabled + for (var inv = 0; inv < maxInv; inv++) { + var e = document.getElementById("invEnabled"+inv); + e.checked = (obj.inverters[inv].enabled); + } + // - InverterId + for (var inv = 0; inv < maxInv; inv++) { + var e = document.getElementById("invId"+inv); + selDelAllOpt(e); + e.appendChild(opt("-1", "---")); + for (var i = 0; i < ivObj.inverter.length; i++) { + e.appendChild(opt((ivObj.inverter[i].id), (ivObj.inverter[i].name))); + } + for (var i = 0; i < (e.length); i++) { + if (e.options[i].value == obj.inverters[inv].id) { + e.selectedIndex = i; + } + } + } + // - Target + for (var inv = 0; inv < maxInv; inv++) { + var e = document.getElementById("invTarget"+inv); + selDelAllOpt(e); +// TODO: übersetzen? + e.appendChild(opt("-1", "---")); + e.appendChild(opt("0", "Sum")); + e.appendChild(opt("1", "L1")); + e.appendChild(opt("2", "L2")); + e.appendChild(opt("3", "L3")); + e.appendChild(opt("4", "L1 + Sum")); + e.appendChild(opt("5", "L2 + Sum")); + e.appendChild(opt("6", "L3 + Sum")); + for (var i = 0; i < e.options.length; i++) { + if (e.options[i].value == obj.inverters[inv].target) { + e.selectedIndex = i; + } + } + } + // - twoPercent + for (var inv = 0; inv < maxInv; inv++) { + var e = document.getElementById("invTwoPercent"+inv); + e.checked = (obj.inverters[inv].twoPercent); + } + // - powerMax + for (var inv = 0; inv < maxInv; inv++) { + var e = document.getElementById("invPowerMax"+inv); + e.value = (obj.inverters[inv].powerMax); + } + + function save() { + var o = new Object(); + o.cmd = "ze_save_group" +// o.token = "*" + // General + o.id = obj.id + o.enabled = document.getElementsByName("enabled")[0].checked; + o.name = document.getElementsByName("name")[0].value; + // Powermeter + //o.pm_type = document.getElementsByName("pm_type")[0].selectedIndex; + var e = document.getElementsByName("pm_type")[0]; + o.pm_type = e.options[e.selectedIndex].value; + o.pm_url = document.getElementsByName("pm_url")[0].value; + o.pm_jsonPath = document.getElementsByName("pm_jsonPath")[0].value; + o.pm_user = document.getElementsByName("pm_user")[0].value; + o.pm_pass = document.getElementsByName("pm_pass")[0].value; + // Inverters + o.invMax = document.getElementById("invMax").value; + o.inverters = []; + for(var inv = 0; inv < o.invMax; inv++) { + var q = new Object(); + q.enabled = document.getElementById("invEnabled"+inv).checked; + var e = document.getElementById("invId"+inv); + q.id = e.options[e.selectedIndex].value; + var e = document.getElementById("invTarget"+inv); + q.target = e.options[e.selectedIndex].value; + q.twoPercent = document.getElementById("invTwoPercent"+inv).checked; + q.powerMax = document.getElementById("invPowerMax"+inv).value; + o.inverters.push(q); + } + // Battery + o.battEnabled = document.getElementsByName("battEnabled")[0].checked; + o.battVoltageOn = document.getElementsByName("battVoltageOn")[0].value; + o.battVoltageOff = document.getElementsByName("battVoltageOff")[0].value; + // Advanced + o.refresh = document.getElementsByName("refresh")[0].value; + o.powerTolerance = document.getElementsByName("powerTolerance")[0].value; + o.powerMax = document.getElementsByName("powerMax")[0].value; + // Global + getAjax("/api/setup", cb, "POST", JSON.stringify(o)); + } + + function cb(obj2) { + var e = document.getElementById("res"); + if(!obj2.success) + e.innerHTML = "{#ERROR}" + obj2.error; + else { + modalClose(); + getAjax("/api/setup", parse); + } + } + + } + + function ZeroExportGroup_Del(obj) { + var html = ml("div", {class: "row"}, [ + ml("div", {class: "col-9"}, "{#ZE_GROUP_DELETE_SURE} (" + obj.name + ")"), + ml("div", {class: "col-3 a-r"}, ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "{#ZE_GROUP_DELETE_BTN_YES}", class: "btn", onclick: function() { del(); }}, null))) + ]); + modal("{#ZE_GROUP_DELETE_MODAL}: " + obj.name, html); + + function del() { +// TODO: Es wäre gut, wenn die Defaultwerte nicht hier sondern wie in der settings.h gesetzt würden. + var o = new Object(); + o.cmd = "ze_save_group"; + // General + o.id = obj.id; + o.enabled = false; + o.name = ""; + // Powermeter + o.pm_type = 0; + o.pm_url = ""; + o.pm_jsonPath = ""; + o.pm_user = ""; + o.pm_pass = ""; + // Inverters + o.invMax = obj.inverters.length; + o.inverters = []; + for(var inv = 0; inv < o.invMax; inv++) { + var q = new Object(); + q.enabled = false; + var e = document.getElementById("invId"+inv); + q.id = -1; + var e = document.getElementById("invTarget"+inv); + q.target = -1; + q.twoPercent = false; + q.powerMax = 0; + o.inverters.push(q); + } + // Battery + o.battEnabled = false; + o.battVoltageOn = 0; + o.battVoltageOff = 0; + // Advanced + o.refresh = 10; + o.powerTolerance = 10; + o.powerMax = 600; + // Global + getAjax("/api/setup", cb, "POST", JSON.stringify(o)); + } + + function cb(obj) { + if(obj.success) { + modalClose(); + getAjax("/api/setup", parse); + } + } - document.getElementsByName("total_power")[0].innerHTML = "Total: " + obj["total_power"].toFixed(2) + "W"; - document.getElementById("Inv_ID").selectedIndex = obj["Iv"]; } - function parseZeroIv(root) - { - for(var i = 0; i < root.inverter.length; i++) - root.inverter[i]; - - select = document.getElementById('Inv_ID'); - parseInt(select.value) - - if(null == root) return; - root = root.inverter; - for(var i = 0; i < root.length; i++) { - inv = root[i]; - var opt = document.createElement('option'); - opt.value = inv.id; - opt.innerHTML = inv.name; - select.appendChild(opt); + // Plugin ZeroExport - Ende + /*ENDIF_PLUGIN_ZEROEXPORT*/ + + function parseZeroExport(obj, type) { + + /*IF_PLUGIN_ZEROEXPORT*/ + // Plugin ZeroExport + + // enabled + document.getElementsByName("ze_enabled")[0].checked = obj["enabled"]; + + // groups + maxGroups = obj["max_groups"]; + + var lines = []; + + lines.push(ml("tr", {}, [ + ml("th", {style: "width: 10%; text-align: center;"}, "{#ZE_GROUP_ENABLED}"), + ml("th", {style: "width: 10%; text-align: center;"}, "{#ZE_GROUP_ID}"), + ml("th", {style: "text-align: center;"}, "{#ZE_GROUP_NAME}"), + ml("th", {style: "width: 10%; text-align: center;"}, "{#ZE_GROUP_POWERTOTAL}"), + ml("th", {style: "width: 10%; text-align: center;"}, "{#ZE_GROUP_EDIT}"), + ml("th", {style: "width: 10%; text-align: center;"}, "{#ZE_GROUP_DELETE}") + ])); + + for(let group = 0; group < obj.groups.length; group++) { + lines.push(ml("tr", {}, [ + ml("td", {style: "text-align: left;", }, badge(obj.groups[group].enabled, (obj.groups[group].enabled) ? "{#ENABLED}" : "{#DISABLED}")), + ml("td", {style: "text-align: center;", }, String(obj.groups[group].id)), + ml("td", {style: "text-align: left;", }, String(obj.groups[group].name)), + ml("td", {style: "text-align: right;", id: "groupPowerTotal"+group}, "n/a"), + ml("td", {style: "text-align: center;", onclick: function() { + function zeroGetIvList(ivObj) { + ZeroExportGroup_Modal(obj.groups[group], ivObj) + } + getAjax("/api/inverter/list", zeroGetIvList) + }}, svg(iconGear, 25, 25, "icon icon-fg pointer")), + ml("td", {style: "text-align: center;", onclick: function() {ZeroExportGroup_Del(obj.groups[group]);}}, svg(iconDel, 25, 25, "icon icon-fg pointer")) + ])); + } + +// TODO: Das Add sollte anders / überhaupt gelöst werden + var add = new Object(); + add.enabled = true; + add.id = obj.groups.length; + add.name = ""; +// add.ch_max_pwr = [400,400,400,400,400,400]; +// add.ch_name = []; +// add.ch_yield_cor = []; +// add.freq = 12; +// add.pa = 30; + + var e = document.getElementById("ze_groups"); + e.innerHTML = ""; // remove all childs + e.append(ml("table", {class: "table"}, ml("tbody", {}, lines))); + if(maxGroups > obj.groups.length) { + e.append(ml("div", {class: "row my-3"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "{#BTN_INV_ADD}", class: "btn", onclick: function() { ZeroExportGroup_Modal(add); }}, null)))); } + +// ivGlob(obj); + + // Plugin ZeroExport - Ende + + /*ELIF_PLUGIN_ZEROEXPORT*/ +if ("ESP32-S3" != type) { +// TODO: entfernen wenn ELIF funktioniert + var e = document.getElementById("zeroExport"); + e.remove(); + + var e = document.getElementById("zeroExport_button"); + e.textContent += " (only for ESP32-S3 available)"; + e.disabled = true; + element.classList.add("disabled"); +// TODO: übersetzen? / Überflüssig? Das Modul ist so immer sichtbar und zeigt was es braucht. +} + /*ENDIF_PLUGIN_ZEROEXPORT*/ + } function parse(root) { @@ -1265,10 +1564,10 @@ /*IF_ESP32*/ parseCmtRadio(root["radioCmt"], root["system"]["esp_type"], root["system"]); /*ENDIF_ESP32*/ - parsezeroExport(root["zeroExport"], root["system"]["esp_type"]); parseSerial(root["serial"]); parseDisplay(root["display"], root["system"]["esp_type"], root["system"]); + parseZeroExport(root["zeroExport"], root["system"]["esp_type"]); getAjax("/api/inverter/list", parseIv); } diff --git a/src/web/lang.json b/src/web/lang.json index 67b08b75..54547531 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -767,6 +767,191 @@ "token": "NO_NETWORK_FOUND", "en": "no network found", "de": "kein Netzwerk gefunden" + }, + { + "token": "ZE", + "en": "Demand-optimized power control", + "de": "Bedarfsorientierte Leistungsregelung" + }, + { + "token": "ZE_ENABLED", + "en": "Enabled", + "de": "Aktiviert" + }, + { + "token": "ZE_GROUP_ENABLED", + "en": "State:", + "de": "Status:" + }, + { + "token": "ZE_GROUP_ID", + "en": "Group:", + "de": "Gruppe:" + }, + { + "token": "ZE_GROUP_NAME", + "en": "Name:", + "de": "Name:" + }, + { + "token": "ZE_GROUP_POWERTOTAL", + "en": "PowerTotal (W):", + "de": "Leistung (W):" + }, + { + "token": "ZE_GROUP_EDIT", + "en": "Edit", + "de": "Bearbeiten" + }, + { + "token": "ZE_GROUP_DELETE", + "en": "Delete", + "de": "Löschen" + }, + { + "token": "ZE_GROUP_EDIT_MODAL", + "en": "Edit group", + "de": "Gruppe bearbeiten" + }, + { + "token": "ZE_GROUP_TAB_GENERAL", + "en": "General:", + "de": "Allgemein:" + }, + { + "token": "ZE_GROUP_TAB_GENERAL_GRUPPE", + "en": "Group:", + "de": "Gruppe:" + }, + { + "token": "ZE_GROUP_TAB_GENERAL_ENABLE", + "en": "Enabled:", + "de": "Aktiviert:" + }, + { + "token": "ZE_GROUP_TAB_GENERAL_NAME", + "en": "Name:", + "de": "Name:" + }, + { + "token": "ZE_GROUP_TAB_POWERMETER", + "en": "Powermeter", + "de": "Leistungsmessung" + }, + { + "token": "ZE_GROUP_TAB_POWERMETER_TYPE", + "en": "Type:", + "de": "Typ:" + }, + { + "token": "ZE_GROUP_TAB_POWERMETER_URL", + "en": "Url:", + "de": "Url:" + }, + { + "token": "ZE_GROUP_TAB_POWERMETER_JSONPATH", + "en": "JSON Path:", + "de": "JSON Pfad:" + }, + { + "token": "ZE_GROUP_TAB_POWERMETER_USER", + "en": "Username:", + "de": "Benutzername:" + }, + { + "token": "ZE_GROUP_TAB_POWERMETER_PASS", + "en": "Password:", + "de": "Passwort:" + }, + { + "token": "ZE_GROUP_TAB_INVERTER", + "en": "Inverter", + "de": "Wechselrichter" + }, + { + "token": "ZE_GROUP_TAB_INVERTER_ENABLED", + "en": "Enabled:", + "de": "Aktiviert:" + }, + { + "token": "ZE_GROUP_TAB_INVERTER_NAME", + "en": "Name", + "de": "Name" + }, + { + "token": "ZE_GROUP_TAB_INVERTER_SUM", + "en": "Sum", + "de": "Gesamt" + }, + { + "token": "ZE_GROUP_TAB_INVERTER_TWOPERCENT", + "en": "2%", + "de": "2%" + }, + { + "token": "ZE_GROUP_TAB_INVERTER_POWERMAX", + "en": "Power (Max)", + "de": "Leistung (Max)" + }, + { + "token": "ZE_GROUP_TAB_BATTERY", + "en": "Battery", + "de": "Batterie" + }, + { + "token": "ZE_GROUP_TAB_BATTERY_BATTENABLED", + "en": "Enabled:", + "de": "Aktiviert:" + }, + { + "token": "ZE_GROUP_TAB_BATTERY_BATTVOLTAGEON", + "en": "Voltage on (Volt):", + "de": "Spannung Ein (Volt):" + }, + { + "token": "ZE_GROUP_TAB_BATTERY_BATTVOLTAGEOFF", + "en": "Voltage off (Volt):", + "de": "Spannung Aus (Volt):" + }, + { + "token": "ZE_GROUP_TAB_ADVANCED", + "en": "Advanced", + "de": "Erweitert" + }, + { + "token": "ZE_GROUP_TAB_ADVANCED_REFRESH", + "en": "Refresh rate (sec):", + "de": "Aktualisierung (sec):" + }, + { + "token": "ZE_GROUP_TAB_ADVANCED_POWERTOLERANCE", + "en": "Power tolerances (Watt):", + "de": "Leistung Toleranz (Watt)" + }, + { + "token": "ZE_GROUP_TAB_ADVANCED_POWERMAX", + "en": "Group Power max (Watt):", + "de": "Gruppe Leistung Max (Watt):" + }, + { + "token": "ZE_GROUP_EDIT_BTN_SAVE", + "en": "save", + "de": "speichern" + }, + { + "token": "ZE_GROUP_DELETE_MODAL", + "en": "Delete group", + "de": "Gruppe löschen " + }, + { + "token": "ZE_GROUP_DELETE_SURE", + "en": "do you really want to delete group?", + "de": "Willst du die Gruppe wirklich löschen?" + }, + { + "token": "ZE_GROUP_DELETE_BTN_YES", + "en": "yes", + "de": "ja" } ] }, diff --git a/src/web/web.h b/src/web/web.h index 0425ff53..c3c866d5 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -549,39 +549,6 @@ class Web { mConfig->mqtt.port = request->arg("mqttPort").toInt(); mConfig->mqtt.interval = request->arg("mqttInterval").toInt(); - // zero-export - #if defined(ESP32) - mConfig->plugin.zexport.enabled = (request->arg("en_zeroexport") == "on"); - mConfig->plugin.zexport.two_percent = (request->arg("two_percent") == "on"); - mConfig->plugin.zexport.Iv = request->arg("Iv").toInt(); - mConfig->plugin.zexport.count_avg = request->arg("count_avg").toInt(); - mConfig->plugin.zexport.max_power = request->arg("max_power").toDouble(); - mConfig->plugin.zexport.power_avg = request->arg("power_avg").toFloat(); - mConfig->plugin.zexport.query_device = request->arg("query_device").toInt(); - mConfig->plugin.zexport.total_power = request->arg("total_power").toDouble(); - - if (request->arg("monitor_url") != "") { - String addr = request->arg("monitor_url"); - addr.trim(); - addr.toCharArray(mConfig->plugin.zexport.monitor_url, ZEXPORT_ADDR_LEN); - } else - mConfig->plugin.zexport.monitor_url[0] = '\0'; - - if (request->arg("json_path") != "") { - String addr = request->arg("json_path"); - addr.trim(); - addr.toCharArray(mConfig->plugin.zexport.json_path, ZEXPORT_ADDR_LEN); - } else - mConfig->plugin.zexport.json_path[0] = '\0'; - - if (request->arg("tibber_pw") != "") { - String addr = request->arg("tibber_pw"); - addr.trim(); - addr.toCharArray(mConfig->plugin.zexport.tibber_pw, 10); - } else - mConfig->plugin.zexport.tibber_pw[0] = '\0'; - #endif - // serial console mConfig->serial.debug = (request->arg("serDbg") == "on"); mConfig->serial.privacyLog = (request->arg("priv") == "on"); @@ -619,6 +586,43 @@ class Web { mConfig->plugin.display.pirPin = (mConfig->plugin.display.screenSaver != DISP_TYPE_T2_SH1106_128X64) ? DEF_PIN_OFF : request->arg("pir_pin").toInt(); // pir pin only for motion screensaver #endif // otherweise default value + // Plugin ZeroExport + #if defined(PLUGIN_ZEROEXPORT) + mConfig->plugin.zeroExport.enabled = (request->arg("ze_enabled") == "on"); +// TODO: sortieren +// mConfig->plugin.zeroExport.enabled = (request->arg("en_zeroexport") == "on"); +// mConfig->plugin.zeroExport.two_percent = (request->arg("two_percent") == "on"); +// mConfig->plugin.zeroExport.Iv = request->arg("Iv").toInt(); +// mConfig->plugin.zeroExport.count_avg = request->arg("count_avg").toInt(); +// mConfig->plugin.zeroExport.max_power = request->arg("max_power").toDouble(); +// mConfig->plugin.zeroExport.power_avg = request->arg("power_avg").toFloat(); +// mConfig->plugin.zeroExport.query_device = request->arg("query_device").toInt(); +// mConfig->plugin.zeroExport.total_power = request->arg("total_power").toDouble(); +/* + if (request->arg("monitor_url") != "") { + String addr = request->arg("monitor_url"); + addr.trim(); + addr.toCharArray(mConfig->plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN); + } else + mConfig->plugin.zeroExport.monitor_url[0] = '\0'; + + if (request->arg("json_path") != "") { + String addr = request->arg("json_path"); + addr.trim(); + addr.toCharArray(mConfig->plugin.zeroExport.json_path, ZEXPORT_ADDR_LEN); + } else + mConfig->plugin.zeroExport.json_path[0] = '\0'; + + if (request->arg("tibber_pw") != "") { + String addr = request->arg("tibber_pw"); + addr.trim(); + addr.toCharArray(mConfig->plugin.zeroExport.tibber_pw, 10); + } else + mConfig->plugin.zeroExport.tibber_pw[0] = '\0'; +*/ + #endif + // Plugin ZeroExport - Ende + mApp->saveSettings((request->arg("reboot") == "on")); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), save_html, save_html_len);