diff --git a/src/app.cpp b/src/app.cpp index f110398d..ae400c94 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -73,9 +73,16 @@ void app::setup() { mCommunication.setup(&mTimestamp, &mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace); mCommunication.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2)); - #if defined(ENABLE_MQTT) - mCommunication.addPowerLimitAckListener([this] (Inverter<> *iv) { mMqtt.setPowerLimitAck(iv); }); - #endif + #if defined(PLUGIN_ZEROEXPORT) || defined(ENABLE_MQTT) + mCommunication.addPowerLimitAckListener([this] (Inverter<> *iv) { + #if defined(PLUGIN_ZEROEXPORT) + mZeroExport.resetWaitLimitAck(iv); + #endif /*PLUGIN_ZEROEXPORT*/ + #if defined(ENABLE_MQTT) + mMqtt.setPowerLimitAck(iv); + #endif + }); + #endif /*defined(PLUGIN_ZEROEXPORT) || defined(ENABLE_MQTT)*/ mSys.setup(&mTimestamp, &mConfig->inst, this); for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { initInverter(i); @@ -124,10 +131,7 @@ void app::setup() { // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) -// TODO: aufräumen -// if (mConfig->plugin.zeroExport.enabled) { - mZeroExport.setup(&mConfig->plugin.zeroExport, &mSys, mConfig, &mApi, &mMqtt); -// } + mZeroExport.setup(&mConfig->plugin.zeroExport, &mSys, mConfig, &mApi, &mMqtt); #endif // Plugin ZeroExport - Ende @@ -216,10 +220,7 @@ void app::regularTickers(void) { // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) -// TODO: aufräumen -// if (mConfig->plugin.zeroExport.enabled) { - everySec(std::bind(&ZeroExportType::tickerSecond, &mZeroExport), "ZeroExport"); -// } + everySec(std::bind(&ZeroExportType::tickerSecond, &mZeroExport), "ZeroExport"); #endif // Plugin ZeroExport - Ende @@ -479,13 +480,13 @@ void app::tickSend(void) { }); // Plugin ZeroExport - #if defined(PLUGIN_ZEROEXPORT) +// #if defined(PLUGIN_ZEROEXPORT) // TODO: aufräumen - if(mConfig->nrf.enabled || mConfig->cmt.enabled) { - mZeroExport.loop(); +// if(mConfig->nrf.enabled || mConfig->cmt.enabled) { +// mZeroExport.loop(); // zeroexport(); - } - #endif +// } +// #endif // Plugin ZeroExport - Ende } } @@ -548,7 +549,7 @@ 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); + snprintf(mVersion, sizeof(mVersion), "%d.%d.%d-zero", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); #else snprintf(mVersion, sizeof(mVersion), "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); #endif diff --git a/src/config/settings.h b/src/config/settings.h index 214f42f2..75753076 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -186,6 +186,8 @@ typedef struct { #endif // Plugin ZeroExport +#if defined(PLUGIN_ZEROEXPORT) + #define ZEROEXPORT_MAX_GROUPS 6 #define ZEROEXPORT_GROUP_MAX_LEN_NAME 25 #define ZEROEXPORT_GROUP_MAX_LEN_PM_URL 100 @@ -196,9 +198,22 @@ typedef struct { #define ZEROEXPORT_POWERMETER_MAX_ERRORS 5 #define ZEROEXPORT_DEF_INV_WAITINGTIME_MS 10000 -#if defined(PLUGIN_ZEROEXPORT) enum class zeroExportState : uint8_t { - RESET, GETPOWERMETER, GETINVERTERDATA, BATTERYPROTECTION, CONTROL, SETCONTROL, FINISH + INIT, + WAIT, + WAITREFRESH, + GETINVERTERACKS, + GETINVERTERDATA, + BATTERYPROTECTION, + GETPOWERMETER, + CONTROLLER, + PROGNOSE, + AUFTEILEN, + SETLIMIT, + SETPOWER, + SETREBOOT, + FINISH, + ERROR }; typedef enum { @@ -246,6 +261,9 @@ typedef struct { float power; uint16_t limit; uint16_t limitNew; + bool waitLimitAck; + bool waitPowerAck; + bool waitRebootAck; unsigned long limitTsp; float dcVoltage; bool state; @@ -274,9 +292,13 @@ typedef struct { uint16_t powerMax; // + zeroExportState stateLast; zeroExportState state; zeroExportState stateNext; unsigned long lastRun; + unsigned long lastRefresh; +// bool waitForAck; + float pmPower; float pmPowerL1; float pmPowerL2; @@ -298,6 +320,8 @@ typedef struct { typedef struct { bool enabled; + bool log_over_webserial; + bool log_over_mqtt; zeroExportGroup_t groups[ZEROEXPORT_MAX_GROUPS]; @@ -314,6 +338,7 @@ typedef struct { // double max_power; // bool two_percent; // ask if not go lower then 2% } zeroExport_t; + #endif // Plugin ZeroExport - Ende @@ -624,6 +649,8 @@ class settings { // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) mCfg.plugin.zeroExport.enabled = false; + mCfg.plugin.zeroExport.log_over_webserial = false; + mCfg.plugin.zeroExport.log_over_mqtt = false; for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { // General mCfg.plugin.zeroExport.groups[group].enabled = false; @@ -641,6 +668,10 @@ class settings { mCfg.plugin.zeroExport.groups[group].inverters[inv].target = -1; mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMin = 10; mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax = 600; + // + mCfg.plugin.zeroExport.groups[group].inverters[inv].waitLimitAck = false; + mCfg.plugin.zeroExport.groups[group].inverters[inv].waitPowerAck = false; + mCfg.plugin.zeroExport.groups[group].inverters[inv].waitRebootAck = false; } // Battery mCfg.plugin.zeroExport.groups[group].battEnabled = false; @@ -651,9 +682,10 @@ class settings { 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].state = zeroExportState::INIT; mCfg.plugin.zeroExport.groups[group].lastRun = 0; + mCfg.plugin.zeroExport.groups[group].lastRefresh = 0; mCfg.plugin.zeroExport.groups[group].pmPower = 0; mCfg.plugin.zeroExport.groups[group].pmPowerL1 = 0; mCfg.plugin.zeroExport.groups[group].pmPowerL2 = 0; @@ -1017,6 +1049,8 @@ class settings { void jsonZeroExport(JsonObject obj, bool set = false) { if(set) { obj[F("enabled")] = mCfg.plugin.zeroExport.enabled; + obj[F("log_over_webserial")] = mCfg.plugin.zeroExport.log_over_webserial; + obj[F("log_over_mqtt")] = mCfg.plugin.zeroExport.log_over_mqtt; JsonArray grpArr = obj.createNestedArray(F("groups")); for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { jsonZeroExportGroup(grpArr.createNestedObject(), group, set); @@ -1026,6 +1060,10 @@ class settings { { if (obj.containsKey(F("enabled"))) getVal(obj, F("enabled"), &mCfg.plugin.zeroExport.enabled); + if (obj.containsKey(F("log_over_webserial"))) + getVal(obj, F("log_over_webserial"), &mCfg.plugin.zeroExport.log_over_webserial); + if (obj.containsKey(F("log_over_mqtt"))) + getVal(obj, F("log_over_mqtt"), &mCfg.plugin.zeroExport.log_over_mqtt); if (obj.containsKey(F("groups"))) { for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { jsonZeroExportGroup(obj[F("groups")][group], group, set); diff --git a/src/defines.h b/src/defines.h index 8d477375..67d5d10e 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 910007 +#define VERSION_PATCH 910008 //------------------------------------- typedef struct { diff --git a/src/plugins/zeroExport/zeroExport.h b/src/plugins/zeroExport/zeroExport.h index e778abfa..730a1c57 100644 --- a/src/plugins/zeroExport/zeroExport.h +++ b/src/plugins/zeroExport/zeroExport.h @@ -19,8 +19,8 @@ class ZeroExport { public: - /** - * ZeroExport + /** ZeroExport + * Konstruktor */ ZeroExport() { mIsInitialized = false; @@ -31,13 +31,13 @@ class ZeroExport { } } - /** - * ~ZeroExport + /** ~ZeroExport + * Destruktor */ ~ZeroExport() {} - /** - * setup + /** setup + * Initialisierung */ void setup(zeroExport_t *cfg, HMSYSTEM *sys, settings_t *config, RestApiType *api, PubMqttType *mqtt) { mCfg = cfg; @@ -46,123 +46,83 @@ class ZeroExport { mApi = api; mMqtt = mqtt; -// TODO: Sicherheitsreturn weil noch Sicherheitsfunktionen fehlen. // mIsInitialized = true; +// TODO: Sicherheitsreturn weil noch Sicherheitsfunktionen fehlen. + mIsInitialized = false; } - /** - * loop + /** loop + * Arbeitsschleife */ void loop(void) { if ((!mIsInitialized) || (!mCfg->enabled)) { return; } -// TODO: Sicherheitsreturn weil noch Sicherheitsfunktionen fehlen. -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: - mCfg->groups[group].lastRun = millis(); - mCfg->groups[group].state = zeroExportState::GETPOWERMETER; + case zeroExportState::INIT: + if (groupInit(group)) sendLog(); break; - - case zeroExportState::GETPOWERMETER: - if ((millis() - mCfg->groups[group].lastRun) > (mCfg->groups[group].refresh * 1000UL)) { - if (getPowermeterWatts(group)) { - mCfg->groups[group].state = zeroExportState::FINISH; - mCfg->groups[group].stateNext = zeroExportState::GETINVERTERDATA; - } else { - // Wartezeit wenn Keine Daten vom Powermeter - mCfg->groups[group].lastRun = (millis() - (mCfg->groups[group].refresh * 100UL)); - mCfg->groups[group].state = zeroExportState::FINISH; - mCfg->groups[group].stateNext = zeroExportState::RESET; - } - } + case zeroExportState::WAIT: + if (groupWait(group)) sendLog(); + break; + case zeroExportState::WAITREFRESH: + if (groupWaitRefresh(group)) sendLog(); + break; + case zeroExportState::GETINVERTERACKS: + if (groupGetInverterAcks(group)) sendLog(); break; - case zeroExportState::GETINVERTERDATA: - if ((millis() - mCfg->groups[group].lastRun) > (mCfg->groups[group].refresh * 1000UL)) { - if (getInverterData(group)) { - mCfg->groups[group].state = zeroExportState::FINISH; - mCfg->groups[group].stateNext = zeroExportState::BATTERYPROTECTION; - } else { - // Wartezeit wenn Keine Daten von Wechselrichtern - mCfg->groups[group].lastRun = (millis() - (mCfg->groups[group].refresh * 100UL)); - mCfg->groups[group].state = zeroExportState::FINISH; - mCfg->groups[group].stateNext = zeroExportState::RESET; - } - } + if (groupGetInverterData(group)) sendLog(); break; - case zeroExportState::BATTERYPROTECTION: - if ((millis() - mCfg->groups[group].lastRun) > (mCfg->groups[group].refresh * 1000UL)) { - if (batteryProtection(group)) { - mCfg->groups[group].state = zeroExportState::FINISH; - mCfg->groups[group].stateNext = zeroExportState::CONTROL; - } else { - // Wartezeit - mCfg->groups[group].lastRun = (millis() - (mCfg->groups[group].refresh * 100UL)); - mCfg->groups[group].state = zeroExportState::FINISH; - mCfg->groups[group].stateNext = zeroExportState::RESET; - } - } + if (groupBatteryprotection(group)) sendLog(); break; - - case zeroExportState::CONTROL: - if ((millis() - mCfg->groups[group].lastRun) > (mCfg->groups[group].refresh * 1000UL)) { - if (controller(group)) { - mCfg->groups[group].state = zeroExportState::FINISH; - mCfg->groups[group].stateNext = zeroExportState::SETCONTROL; - } else { - // Wartezeit - mCfg->groups[group].lastRun = (millis() - (mCfg->groups[group].refresh * 100UL)); - mCfg->groups[group].state = zeroExportState::FINISH; - mCfg->groups[group].stateNext = zeroExportState::RESET; - } - } + case zeroExportState::GETPOWERMETER: + if (groupGetPowermeter(group)) sendLog(); break; - - case zeroExportState::SETCONTROL: - if ((millis() - mCfg->groups[group].lastRun) > (mCfg->groups[group].refresh * 1000UL)) { - if (setControl(group)) { - mCfg->groups[group].state = zeroExportState::FINISH; - mCfg->groups[group].stateNext = zeroExportState::RESET; - } else { - // Wartezeit - mCfg->groups[group].lastRun = (millis() - (mCfg->groups[group].refresh * 100UL)); - mCfg->groups[group].state = zeroExportState::FINISH; - mCfg->groups[group].stateNext = zeroExportState::RESET; - } - } + case zeroExportState::CONTROLLER: + if (groupController(group)) sendLog(); + break; + case zeroExportState::PROGNOSE: + if (groupPrognose(group)) sendLog(); + break; + case zeroExportState::AUFTEILEN: + if (groupAufteilen(group)) sendLog(); + break; + case zeroExportState::SETLIMIT: + if (groupSetLimit(group)) sendLog(); + break; + case zeroExportState::SETPOWER: + if (groupSetPower(group)) sendLog(); + break; + case zeroExportState::SETREBOOT: + if (groupSetReboot(group)) sendLog(); + break; + case zeroExportState::FINISH: + mCfg->groups[group].state = zeroExportState::WAITREFRESH; + mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; +mCfg->groups[group].lastRefresh = millis();; + break; + case zeroExportState::ERROR: + mCfg->groups[group].state = zeroExportState::INIT; + mCfg->groups[group].stateNext = zeroExportState::INIT; break; - default: -// TODO: Debug Webserial. Deaktiviert um CPU-Last zu verringern. - DBGPRINTLN(String("ze: ")); -// DBGPRINTLN(mDocLog.as()); - if (mMqtt->isConnected()) { - mMqtt->publish("ze", mDocLog.as().c_str(), false); - } - mDocLog.clear(); - if (mCfg->groups[group].stateNext != mCfg->groups[group].state) { - mCfg->groups[group].state = mCfg->groups[group].stateNext; - } else { - mCfg->groups[group].state = zeroExportState::RESET; - mCfg->groups[group].stateNext = zeroExportState::RESET; - } + mCfg->groups[group].state = zeroExportState::INIT; + mCfg->groups[group].stateNext = zeroExportState::INIT; break; } } } - /** - * tickerSecond + /** tickerSecond + * Zeitimpuls */ void tickerSecond() { // TODO: Warten ob benötigt, ggf ein bit setzen, das in der loop() abgearbeitet wird. @@ -171,132 +131,1158 @@ return; } } + /** resetWaitLimitAck + * + * @param + */ + void resetWaitLimitAck(Inverter<> *iv) { + for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { +// bool doLog = false; + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + if (iv->id == (uint8_t)mCfg->groups[group].inverters[inv].id) { + mLog["group"] = group; + mLog["type"] = "resetWaitLimitAck"; + unsigned long bTsp = millis(); + mLog["B"] = bTsp; + mLog["id"] = iv->id; + mCfg->groups[group].inverters[inv].waitLimitAck = false; + mLog["iv"] = inv; + mLog["wait"] = false; +// doLog = true; + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; +// if (doLog) { + sendLog(); +// } + } + } + } + } + + /** setPowerAck + * + * @param + */ + void resetWaitPowerAck(Inverter<> *iv) { + for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + if (iv->id == mCfg->groups[group].inverters[inv].id) { + mCfg->groups[group].inverters[inv].waitPowerAck = false; + } + } + } + } + + /** resetWaitRebootAck + * + * @param + */ + void resetWaitRebootAck(Inverter<> *iv) { + for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + if (iv->id == mCfg->groups[group].inverters[inv].id) { + mCfg->groups[group].inverters[inv].waitRebootAck = false; + } + } + } + } + + private: -/* -// TODO: Vorlage für nachfolgende Funktion getPowermeterWatts. Funktionen erst zusammenführen, wenn keine weiteren Powermeter mehr kommen. - //C2T2-B91B - HTTPClient httpClient; - // 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); - httpClient.setTimeout(1000); - httpClient.addHeader("Content-Type", "application/json"); - httpClient.addHeader("Accept", "application/json"); + /** groupInit + * initialisiert die Gruppe und sucht die ivPointer + * @param group + * @returns true/false + */ + bool groupInit(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupInit"; + mLog["B"] = bTsp; - if (!httpClient.begin(mCfg->monitor_url)) { - DPRINTLN(DBG_INFO, "httpClient.begin failed"); - httpClient.end(); - return false; + mCfg->groups[group].stateLast = zeroExportState::INIT; + + // Init ivPointer + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mIv[group][inv] = nullptr; + doLog = true; } - if(String(mCfg->monitor_url).endsWith("data.json?node_id=1")){ - httpClient.setAuthorization("admin", mCfg->tibber_pw); + // Search ivPointer + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + // Init ivPointer + mIv[group][inv] = nullptr; + // Inverter not enabled -> ignore + if (!mCfg->groups[group].inverters[inv].enabled) { + continue; + } + // Inverter not selected -> ignore + if (mCfg->groups[group].inverters[inv].id <= 0) { + continue; + } + // + Inverter<> *iv; +// TODO: getInverterById, dann würde die Variable *iv und die Schleife nicht gebraucht. + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { + iv = mSys->getInverterByPos(i); + // Inverter not configured -> ignore + if (iv == NULL) { + continue; + } + // Inverter not matching -> ignore + if (iv->id != (uint8_t)mCfg->groups[group].inverters[inv].id) { + continue; + } + // Save Inverter + mIv[group][inv] = mSys->getInverterByPos(i); + doLog = true; + } } - int httpCode = httpClient.GET(); - if (httpCode == HTTP_CODE_OK) - { - String responseBody = httpClient.getString(); - DynamicJsonDocument json(2048); - DeserializationError err = deserializeJson(json, responseBody); + // Init Acks + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mCfg->groups[group].inverters[inv].waitLimitAck = false; + mCfg->groups[group].inverters[inv].waitPowerAck = false; + mCfg->groups[group].inverters[inv].waitRebootAck = false; + } - // Parse succeeded? - if (err) { - DPRINTLN(DBG_INFO, (F("ZeroExport() JSON error returned: "))); - DPRINTLN(DBG_INFO, String(err.f_str())); + // Next + mCfg->groups[group].state = zeroExportState::WAIT; + mCfg->groups[group].stateNext = zeroExportState::WAIT; + mLog["next"] = "WAIT"; + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return doLog; + } + + /** groupWait + * pausiert die Gruppe + * @param group + * @returns true/false + */ + bool groupWait(uint8_t group) { + bool result = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupWait"; + mLog["B"] = bTsp; + + // Wait 60s + if (mCfg->groups[group].lastRun >= (bTsp - 60000UL)) { + return result; + } + + mCfg->groups[group].stateLast = zeroExportState::WAIT; + + result = true; + + // Next + if (mCfg->groups[group].state != mCfg->groups[group].stateNext) { + mCfg->groups[group].state = mCfg->groups[group].stateNext; + mLog["next"] = "unknown"; + } else { + mCfg->groups[group].state = zeroExportState::WAITREFRESH; + mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; + mLog["next"] = "WAITREFRESH"; + } + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return result; + } + + /** groupWaitRefresh + * pausiert die Gruppe + * @param group + * @returns true/false + */ + bool groupWaitRefresh(uint8_t group) { + bool result = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupWaitRefresh"; + mLog["B"] = bTsp; + + // Wait Refreshtime + if (mCfg->groups[group].lastRefresh >= (bTsp - (mCfg->groups[group].refresh * 1000UL))) { + return result; + } + + mCfg->groups[group].stateLast = zeroExportState::WAITREFRESH; + + result = true; + + // Next + mCfg->groups[group].state = zeroExportState::GETINVERTERACKS; + mCfg->groups[group].stateNext = zeroExportState::GETINVERTERACKS; + mLog["next"] = "GETINVERTERACKS"; + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; +// mCfg->groups[group].lastRefresh = eTsp; + mCfg->groups[group].lastRun = eTsp; + return result; + } + + /** groupGetInverterAcks + * aktualisiert die Gruppe mit den ACKs der Inverter für neue Limits + * @param group + * @returns true/false + */ + bool groupGetInverterAcks(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupGetInverterAcks"; + mLog["B"] = bTsp; + + // Wait 100ms + if (mCfg->groups[group].stateLast == zeroExportState::GETINVERTERACKS) { + if (mCfg->groups[group].lastRun >= (bTsp - 100UL)) { + return doLog; } + } - // check if it HICHI - if(json.containsKey(F("StatusSNS")) ) { - int index = responseBody.indexOf(String(mCfg->json_path)); // find first match position - responseBody = responseBody.substring(index); // cut it and store it in value - index = responseBody.indexOf(","); // find the first seperation - Bingo!? + mCfg->groups[group].stateLast = zeroExportState::GETINVERTERACKS; - mCfg->total_power = responseBody.substring(responseBody.indexOf(":"), index).toDouble(); - } else if(json.containsKey(F("emeters"))) { - mCfg->total_power = (double)json[F("total_power")]; - } else if(String(mCfg->monitor_url).endsWith("data.json?node_id=1") ) { - tibber_parse(); - } else { - DPRINTLN(DBG_INFO, (F("ZeroExport() json error: cant find value in this query: ") + responseBody)); - return false; + doLog = true; + + // Wait Acks + JsonArray logArr = mLog.createNestedArray("iv"); + bool wait = false; + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + JsonObject logObj = logArr.createNestedObject(); + logObj["id"] = inv; + + // Inverter not enabled -> ignore + if (!mCfg->groups[group].inverters[inv].enabled) { + continue; + } + // Inverter not selected -> ignore + if (mCfg->groups[group].inverters[inv].id <= 0) { + continue; + } + // Inverter is not available -> wait + if (!mIv[group][inv]->isAvailable()) { + logObj["notAvail"] = true; + wait = true; + } + // waitLimitAck + if (mCfg->groups[group].inverters[inv].waitLimitAck) { + logObj["limit"] = true; + wait = true; + } + // waitPowerAck + if (mCfg->groups[group].inverters[inv].waitPowerAck) { + logObj["power"] = true; + wait = true; + } + // waitRebootAck + if (mCfg->groups[group].inverters[inv].waitRebootAck) { + logObj["reboot"] = true; + wait = true; } } - else - { - DPRINTLN(DBG_INFO, F("ZeroExport(): Error ") + String(httpCode)); - return false; + + mLog["wait"] = wait; + + // Next + if (!wait) { + mCfg->groups[group].state = zeroExportState::GETINVERTERDATA; + mCfg->groups[group].stateNext = zeroExportState::GETINVERTERDATA; } - httpClient.end(); - return true; + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return doLog; } - void tibber_parse() - { + /** groupGetInverterData + * + * @param group + * @returns true/false + */ + bool groupGetInverterData(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupGetInverterData"; + mLog["B"] = bTsp; + + mCfg->groups[group].stateLast = zeroExportState::GETINVERTERDATA; + doLog = true; + + // Get Data + JsonArray logArr = mLog.createNestedArray("iv"); + bool wait = false; + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + JsonObject logObj = logArr.createNestedObject(); + logObj["id"] = inv; + + // Inverter not enabled -> ignore + if (!mCfg->groups[group].inverters[inv].enabled) { + continue; + } + // Inverter not selected -> ignore + if (mCfg->groups[group].inverters[inv].id <= 0) { + continue; + } + + // Get Pac + record_t<> *rec; + rec = mIv[group][inv]->getRecordStruct(RealTimeRunData_Debug); + float p = mIv[group][inv]->getChannelFieldValue(CH0, FLD_PAC, rec); + +// TODO: Save Hole Power für die Webanzeige + + } + + // Next + mCfg->groups[group].state = zeroExportState::BATTERYPROTECTION; + mCfg->groups[group].stateNext = zeroExportState::BATTERYPROTECTION; + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return doLog; } -*/ - // Powermeter + /** groupBatteryprotection + * Batterieschutzfunktion + * @param group + * @returns true/false + */ + bool groupBatteryprotection(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupBatteryprotection"; + mLog["B"] = bTsp; - /** - * getPowermeterWatts + mCfg->groups[group].stateLast = zeroExportState::BATTERYPROTECTION; + + doLog = true; + + // Protection + if (mCfg->groups[group].battEnabled) { + mLog["en"] = true; + + // Config - parameter check + if (mCfg->groups[group].battVoltageOn <= mCfg->groups[group].battVoltageOff) { + mCfg->groups[group].battSwitch = false; + mLog["err"] = "Config - battVoltageOn(" + (String)mCfg->groups[group].battVoltageOn + ") <= battVoltageOff(" + (String)mCfg->groups[group].battVoltageOff + ")"; + return doLog; + } + + // Config - parameter check + if (mCfg->groups[group].battVoltageOn <= (mCfg->groups[group].battVoltageOff + 1)) { + mCfg->groups[group].battSwitch = false; + mLog["err"] = "Config - battVoltageOn(" + (String)mCfg->groups[group].battVoltageOn + ") <= battVoltageOff(" + (String)mCfg->groups[group].battVoltageOff + " + 1V)"; + return doLog; + } + + // Config - parameter check + if (mCfg->groups[group].battVoltageOn <= 22) { + mCfg->groups[group].battSwitch = false; + mLog["err"] = "Config - battVoltageOn(" + (String)mCfg->groups[group].battVoltageOn + ") <= 22V)"; + return doLog; + } + + uint8_t id = 0; + float U = 0; + + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[inv]; + + // Ignore disabled Inverter + if (!cfgGroupInv->enabled) { + continue; + } + if (cfgGroupInv->id <= 0) { + continue; + } + + // Get U + record_t<> *rec; + rec = mIv[group][inv]->getRecordStruct(RealTimeRunData_Debug); + float tmp = mIv[group][inv]->getChannelFieldValue(CH1, FLD_UDC, rec); + if (U == 0) { + U = tmp; + id = cfgGroupInv->id; + } + if (U > tmp) { + U = tmp; + id = cfgGroupInv->id; + } + } + + mLog["iv"] = id; + mLog["U"] = U; + + // Switch to ON + if (U > mCfg->groups[group].battVoltageOn) { + mCfg->groups[group].battSwitch = true; + mLog["action"] = "On"; + } + + // Switch to OFF + if (U < mCfg->groups[group].battVoltageOff) { + mCfg->groups[group].battSwitch = false; + mLog["action"] = "Off"; + } + } else { + mLog["en"] = false; + + mCfg->groups[group].battSwitch = true; + } + + mLog["sw"] = mCfg->groups[group].battSwitch; + + // Next + mCfg->groups[group].state = zeroExportState::GETPOWERMETER; + mCfg->groups[group].stateNext = zeroExportState::GETPOWERMETER; + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return doLog; + } + + /** groupGetPowermeter + * Holt die Daten vom Powermeter * @param group * @returns true/false */ - bool getPowermeterWatts(uint8_t group) { - zeroExportGroup_t *cfgGroup = &mCfg->groups[group]; + bool groupGetPowermeter(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupGetPowermeter"; + mLog["B"] = bTsp; - JsonObject logObj = mLog.createNestedObject("pm"); - logObj["grp"] = group; + mCfg->groups[group].stateLast = zeroExportState::GETPOWERMETER; - bool result = false; + doLog = true; - switch (cfgGroup->pm_type) { + bool result = false; + switch (mCfg->groups[group].pm_type) { case 1: - result = getPowermeterWattsShelly(logObj, group); + result = getPowermeterWattsShelly(mLog, group); break; case 2: - result = getPowermeterWattsTasmota(logObj, group); + result = getPowermeterWattsTasmota(mLog, group); break; case 3: - result = getPowermeterWattsMqtt(logObj, group); + result = getPowermeterWattsMqtt(mLog, group); break; case 4: - result = getPowermeterWattsHichi(logObj, group); + result = getPowermeterWattsHichi(mLog, group); break; case 5: - result = getPowermeterWattsTibber(logObj, group); + result = getPowermeterWattsTibber(mLog, group); break; } - if (!result) { - logObj["error"] = "type: " + String(cfgGroup->pm_type); + mLog["err"] = "type: " + String(mCfg->groups[group].pm_type); } - return result; +// TODO: eventuell muss hier geprüft werden ob die Daten vom Powermeter plausibel sind. + + // Next + if (result) { + mCfg->groups[group].state = zeroExportState::CONTROLLER; + mCfg->groups[group].stateNext = zeroExportState::CONTROLLER; + } + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return doLog; } - /** - * getPowermeterWattsShelly + /** groupController + * PID-Regler + * @param group + * @returns true/false */ - bool getPowermeterWattsShelly(JsonObject logObj, uint8_t group) { - bool result = false; + bool groupController(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupController"; + mLog["B"] = bTsp; - mCfg->groups[group].pmPower = 0; - mCfg->groups[group].pmPowerL1 = 0; - mCfg->groups[group].pmPowerL2 = 0; - mCfg->groups[group].pmPowerL3 = 0; + mCfg->groups[group].stateLast = zeroExportState::GETPOWERMETER; - long int bTsp = millis(); + doLog = true; - HTTPClient http; - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - http.setUserAgent("Ahoy-Agent"); + // Führungsgröße w in Watt + float w = mCfg->groups[group].setPoint; + + mLog["w"] = w; + + // Regelgröße x in Watt + float x = mCfg->groups[group].pmPower; + float x1 = mCfg->groups[group].pmPowerL1; + float x2 = mCfg->groups[group].pmPowerL2; + float x3 = mCfg->groups[group].pmPowerL3; + + mLog["x"] = x; + mLog["x1"] = x1; + mLog["x2"] = x2; + mLog["x3"] = x3; + + // Regelabweichung e in Watt + float e = w - x; + float e1 = w - x1; + float e2 = w - x2; + float e3 = w - x3; + + mLog["e"] = e; + mLog["e1"] = e1; + mLog["e2"] = e2; + mLog["e3"] = e3; + + // Regler +// TODO: Regelparameter unter Advanced konfigurierbar? Aber erst wenn Regler komplett ingegriert. + const float Kp = -1; + const float Ki = -1; + const float Kd = -1; + // - P-Anteil + float yP = Kp * e; + float yP1 = Kp * e1; + float yP2 = Kp * e2; + float yP3 = Kp * e3; + // - I-Anteil +// float esum = esum + e; +// float yI = Ki * Ta * esum; + float yI = 0; + float yI1 = 0; + float yI2 = 0; + float yI3 = 0; + // - D-Anteil +// float yD = Kd * (e -ealt) / Ta; +// float ealt = e; + float yD = 0; + float yD1 = 0; + float yD2 = 0; + float yD3 = 0; + // - PID-Anteil + float yPID = yP + yI + yD; + float yPID1 = yP1 + yI1 + yD1; + float yPID2 = yP2 + yI2 + yD2; + float yPID3 = yP3 + yI3 + yD3; + + // Regelbegrenzung +// TODO: Hier könnte man den maximalen Sprung begrenzen + + mLog["yPID"] = yPID; + mLog["yPID1"] = yPID1; + mLog["yPID2"] = yPID2; + mLog["yPID3"] = yPID3; + + mCfg->groups[group].grpPower = yPID; + mCfg->groups[group].grpPowerL1 = yPID1; + mCfg->groups[group].grpPowerL2 = yPID2; + mCfg->groups[group].grpPowerL3 = yPID3; + + // Next + mCfg->groups[group].state = zeroExportState::PROGNOSE; + mCfg->groups[group].stateNext = zeroExportState::PROGNOSE; + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + mCfg->groups[group].lastRefresh = eTsp; + return doLog; + } + + /** groupPrognose + * + * @param group + * @returns true/false + */ + bool groupPrognose(uint8_t group) { + bool result = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupPrognose"; + mLog["B"] = bTsp; + + mCfg->groups[group].stateLast = zeroExportState::PROGNOSE; + + + +result = true; + // Next +// if (!mCfg->groups[group].waitForAck) { + mCfg->groups[group].state = zeroExportState::AUFTEILEN; + mCfg->groups[group].stateNext = zeroExportState::AUFTEILEN; +// } else { +// mCfg->groups[group].state = zeroExportState::GETINVERTERACKS; +// mCfg->groups[group].stateNext = zeroExportState::GETINVERTERACKS; +// } + + + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return result; + } + + /** groupAufteilen + * + * @param group + * @returns true/false + */ + bool groupAufteilen(uint8_t group) { + bool result = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupAufteilen"; + mLog["B"] = bTsp; + + mCfg->groups[group].stateLast = zeroExportState::AUFTEILEN; + + result = true; + + float y = mCfg->groups[group].grpPower; + float y1 = mCfg->groups[group].grpPowerL1; + float y2 = mCfg->groups[group].grpPowerL2; + float y3 = mCfg->groups[group].grpPowerL3; + + mLog["y"] = y; + mLog["y1"] = y1; + mLog["y2"] = y2; + mLog["y3"] = y3; + + bool grpTarget[7] = {false, false, false, false, false, false, false}; + uint8_t ivId_Pmin[7] = {0, 0, 0, 0, 0, 0, 0}; + uint8_t ivId_Pmax[7] = {0, 0, 0, 0, 0, 0, 0}; + uint16_t ivPmin[7] = {65535, 65535, 65535, 65535, 65535, 65535, 65535}; + uint16_t ivPmax[7] = {0, 0, 0, 0, 0, 0, 0}; + + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[inv]; + + if (!cfgGroupInv->enabled) { + continue; + } + +// if ((cfgGroup->battSwitch) && (!cfgGroupInv->state)) { +// setPower(&logObj, group, inv, true); +// continue; +// } + +// if ((!cfgGroup->battSwitch) && (cfgGroupInv->state)) { +// setPower(&logObj, group, inv, false); +// continue; +// } + +// if (!cfgGroupInv->state) { +// continue; +// } + record_t<> *rec; + rec = mIv[group][inv]->getRecordStruct(RealTimeRunData_Debug); + cfgGroupInv->power = mIv[group][inv]->getChannelFieldValue(CH0, FLD_PAC, rec); + + if ((uint16_t)cfgGroupInv->power < ivPmin[cfgGroupInv->target]) { + grpTarget[cfgGroupInv->target] = true; + ivPmin[cfgGroupInv->target] = (uint16_t)cfgGroupInv->power; + ivId_Pmin[cfgGroupInv->target] = inv; + // Hier kein return oder continue sonst dauerreboot + } + if ((uint16_t)cfgGroupInv->power > ivPmax[cfgGroupInv->target]) { + grpTarget[cfgGroupInv->target] = true; + ivPmax[cfgGroupInv->target] = (uint16_t)cfgGroupInv->power; + ivId_Pmax[cfgGroupInv->target] = inv; + // Hier kein return oder continue sonst dauerreboot + } + } + for (uint8_t i = 0; i < 7; i++) { + mLog[String(i)] = String(i) + String(" grpTarget: ") + String(grpTarget[i]) + String(": ivPmin: ") + String(ivPmin[i]) + String(": ivPmax: ") + String(ivPmax[i]) + String(": ivId_Pmin: ") + String(ivId_Pmin[i]) + String(": ivId_Pmax: ") + String(ivId_Pmax[i]); + } + + for (uint8_t i = 7; i > 0; --i) { + if (!grpTarget[i]) { + continue; + } + mLog[String(String("10")+String(i))] = String(i); + + float *deltaP; + switch(i) { + case 6: + case 3: + deltaP = &mCfg->groups[group].grpPowerL3; + break; + case 5: + case 2: + deltaP = &mCfg->groups[group].grpPowerL2; + break; + case 4: + case 1: + deltaP = &mCfg->groups[group].grpPowerL1; + break; + case 0: + deltaP = &mCfg->groups[group].grpPower; + break; + } + + // Leistung erhöhen + if (*deltaP > 0) { + // Toleranz + if (*deltaP < mCfg->groups[group].powerTolerance) { + continue; + } + mLog["+deltaP"] = *deltaP; + zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[ivId_Pmin[i]]; + cfgGroupInv->limitNew = (uint16_t)((float)cfgGroupInv->limit + *deltaP); +// if (i != 0) { +// cfgGroup->grpPower - *deltaP; +// } + *deltaP = 0; +// if (cfgGroupInv->limitNew > cfgGroupInv->powerMax) { +// *deltaP = cfgGroupInv->limitNew - cfgGroupInv->powerMax; +// cfgGroupInv->limitNew = cfgGroupInv->powerMax; +// if (i != 0) { +// cfgGroup->grpPower + *deltaP; +// } +// } +/// setLimit(&mLog, group, ivId_Pmin[i]); +/// mCfg->groups[group].waitForAck = true; +// TODO: verschieben in anderen Step. + continue; + } + + // Leistung reduzieren + if (*deltaP < 0) { + // Toleranz + if (*deltaP > -mCfg->groups[group].powerTolerance) { + continue; + } + mLog["-deltaP"] = *deltaP; + zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[ivId_Pmax[i]]; + cfgGroupInv->limitNew = (uint16_t)((float)cfgGroupInv->limit + *deltaP); +// if (i != 0) { +// cfgGroup->grpPower - *deltaP; +// } + *deltaP = 0; +// if (cfgGroupInv->limitNew < cfgGroupInv->powerMin) { +// *deltaP = cfgGroupInv->limitNew - cfgGroupInv->powerMin; +// cfgGroupInv->limitNew = cfgGroupInv->powerMin; +// if (i != 0) { +// cfgGroup->grpPower + *deltaP; +// } +// } +/// setLimit(&mLog, group, ivId_Pmax[i]); +/// mCfg->groups[group].waitForAck = true; +// TODO: verschieben in anderen Step. + continue; + } + } + + + // Next +// if (!mCfg->groups[group].waitForAck) { + mCfg->groups[group].state = zeroExportState::SETLIMIT; + mCfg->groups[group].stateNext = zeroExportState::SETLIMIT; +// } else { +// mCfg->groups[group].state = zeroExportState::GETINVERTERACKS; +// mCfg->groups[group].stateNext = zeroExportState::GETINVERTERACKS; +// } + + + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return result; + } + + /** groupSetLimit + * + * @param group + * @returns true/false + */ + bool groupSetLimit(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupSetLimit"; + mLog["B"] = bTsp; + + // Wait 5s +// if (mCfg->groups[group].stateLast == zeroExportState::SETPOWER) { +// if (mCfg->groups[group].lastRun >= (bTsp - 5000UL)) { +// return doLog; +// } +// } + + mCfg->groups[group].stateLast = zeroExportState::SETLIMIT; + + // Set limit +// bool wait = false; + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mLog["inv"] = inv; + + // Nothing todo + if (mCfg->groups[group].inverters[inv].limit == mCfg->groups[group].inverters[inv].limitNew) { + continue; + } + + // Abbruch weil Inverter nicht verfügbar + if (!mIv[group][inv]->isAvailable()) { + continue; + } + + // Abbruch weil Inverter produziert nicht + if (!mIv[group][inv]->isProducing()) { + continue; + } + + // do + doLog = true; +// wait = true; + + // Set Power on/off +// TODO: testen +/* + if (mCfg->groups[group].inverters[inv].limitNew < mCfg->groups[group].inverters[inv].powerMin) { + if (mIv[group][inv]->isProducing()) { + mCfg->groups[group].inverters[inv].switch = false; + mCfg->groups[group].stateNext = zeroExportState::SETPOWER; + } + } else { + if (!mIv[group][inv]->isProducing()) { + mCfg->groups[group].inverters[inv].switch = true; + mCfg->groups[group].stateNext = zeroExportState::SETPOWER; + } + } +*/ + + // Restriction LimitNew >= Pmin + if (mCfg->groups[group].inverters[inv].limitNew < mCfg->groups[group].inverters[inv].powerMin) { + mCfg->groups[group].inverters[inv].limitNew = mCfg->groups[group].inverters[inv].powerMin; + } + + // Restriction LimitNew <= Pmax + if (mCfg->groups[group].inverters[inv].limitNew > mCfg->groups[group].inverters[inv].powerMax) { + mCfg->groups[group].inverters[inv].limitNew = mCfg->groups[group].inverters[inv].powerMax; + } + + // Reject limit if difference < 5 W +// if ((cfgGroupInv->limitNew > cfgGroupInv->limit + 5) && (cfgGroupInv->limitNew < cfgGroupInv->limit - 5)) { +// objLog["err"] = "Diff < 5W"; +// return false; +// } + + mCfg->groups[group].inverters[inv].limit = mCfg->groups[group].inverters[inv].limitNew; + mLog["limit"] = mCfg->groups[group].inverters[inv].limit; + + // wait for Ack + mCfg->groups[group].inverters[inv].waitLimitAck = true; + + // send Command + DynamicJsonDocument doc(512); + JsonObject obj = doc.to(); + obj["val"] = mCfg->groups[group].inverters[inv].limit; + obj["id"] = mCfg->groups[group].inverters[inv].id; + obj["path"] = "ctrl"; + obj["cmd"] = "limit_nonpersistent_absolute"; + mApi->ctrlRequest(obj); + + mLog["data"] = obj; + } + + // Next +// if (!wait) { +// if (mCfg->groups[group].state != mCfg->groups[group].stateNext) { +// mCfg->groups[group].state = mCfg->groups[group].stateNext; +// } else { +// mLog["action"] = "-"; + mCfg->groups[group].state = zeroExportState::WAITREFRESH; + mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; +// } +// } + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return doLog; + } + + /** groupSetPower + * + * @param group + * @returns true/false + */ + bool groupSetPower(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupSetPower"; + mLog["B"] = bTsp; + + // Wait 5s +// if (mCfg->groups[group].stateLast == zeroExportState::SETPOWER) { +// if (mCfg->groups[group].lastRun >= (bTsp - 5000UL)) { +// return doLog; +// } +// } + + mCfg->groups[group].stateLast = zeroExportState::SETPOWER; + + // Set power +// bool wait = false; + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mLog["inv"] = inv; + + // Nothing todo + if (mCfg->groups[group].battSwitch == mIv[group][inv]->isProducing()) { + continue; + } + + // Abbruch weil Inverter nicht verfügbar + if (!mIv[group][inv]->isAvailable()) { + continue; + } + + // do + doLog = true; +// wait = true; + + mLog["action"] = String("switch to: ") + String(mCfg->groups[group].battSwitch); + + // wait for Ack + mCfg->groups[group].inverters[inv].waitPowerAck = true; + + // send Command + DynamicJsonDocument doc(512); + JsonObject obj = doc.to(); + obj["val"] = mCfg->groups[group].battSwitch; + obj["id"] = inv; + obj["path"] = "ctrl"; + obj["cmd"] = "power"; + mApi->ctrlRequest(obj); + + mLog["data"] = obj; + } + + // Next +// if (!wait) { +// if (mCfg->groups[group].state != mCfg->groups[group].stateNext) { +// mCfg->groups[group].state = mCfg->groups[group].stateNext; +// } else { +// mLog["action"] = "-"; + mCfg->groups[group].state = zeroExportState::WAITREFRESH; + mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; +// } +// } + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return doLog; + } + + /** groupSetReboot + * + * @param group + * @returns true/false + */ + bool groupSetReboot(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + mLog["group"] = group; + mLog["type"] = "groupSetReboot"; + mLog["B"] = bTsp; + +// // Wait 5s +// if (mCfg->groups[group].stateLast == zeroExportState::SETREBOOT) { +// if (mCfg->groups[group].lastRun >= (bTsp - 5000UL)) { +// return doLog; +// } +// } + + mCfg->groups[group].stateLast = zeroExportState::SETREBOOT; + + // Set reboot +// bool wait = false; + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mLog["inv"] = inv; + + // Abbruch weil Inverter nicht verfügbar +// if (!mIv[group][inv]->isAvailable()) { +// continue; +// } + + // do + doLog = true; +// wait = true; + + mLog["action"] = String("reboot"); + + // wait for Ack + mCfg->groups[group].inverters[inv].waitRebootAck = true; + + // send Command + DynamicJsonDocument doc(512); + JsonObject obj = doc.to(); + obj["id"] = inv; + obj["path"] = "ctrl"; + obj["cmd"] = "restart"; + mApi->ctrlRequest(obj); + + mLog["data"] = obj; + } + + // Next +// if (!wait) { +// if (mCfg->groups[group].state != mCfg->groups[group].stateNext) { +// mCfg->groups[group].state = mCfg->groups[group].stateNext; +// } else { +// mLog["action"] = "-"; + mCfg->groups[group].state = zeroExportState::WAITREFRESH; + mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; +// } +// } + + unsigned long eTsp = millis(); + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return doLog; + } + + /** sendLog + * Sendet das Log über Webserial und/oder MQTT + */ + void sendLog(void) { + if (mCfg->log_over_webserial) { + DBGPRINTLN(String("ze: ") + mDocLog.as()); + } + + if (mCfg->log_over_mqtt) { + if (mMqtt->isConnected()) { + mMqtt->publish("ze", mDocLog.as().c_str(), false); + } + } + + mDocLog.clear(); + } + + + + + +/* +// TODO: Vorlage für nachfolgende Funktion getPowermeterWatts. Funktionen erst zusammenführen, wenn keine weiteren Powermeter mehr kommen. + //C2T2-B91B + HTTPClient httpClient; + + // 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); + httpClient.setTimeout(1000); + httpClient.addHeader("Content-Type", "application/json"); + httpClient.addHeader("Accept", "application/json"); + + if (!httpClient.begin(mCfg->monitor_url)) { + DPRINTLN(DBG_INFO, "httpClient.begin failed"); + httpClient.end(); + return false; + } + + if(String(mCfg->monitor_url).endsWith("data.json?node_id=1")){ + httpClient.setAuthorization("admin", mCfg->tibber_pw); + } + + int httpCode = httpClient.GET(); + if (httpCode == HTTP_CODE_OK) + { + String responseBody = httpClient.getString(); + DynamicJsonDocument json(2048); + DeserializationError err = deserializeJson(json, responseBody); + + // Parse succeeded? + if (err) { + DPRINTLN(DBG_INFO, (F("ZeroExport() JSON error returned: "))); + DPRINTLN(DBG_INFO, String(err.f_str())); + } + + // check if it HICHI + if(json.containsKey(F("StatusSNS")) ) { + int index = responseBody.indexOf(String(mCfg->json_path)); // find first match position + responseBody = responseBody.substring(index); // cut it and store it in value + index = responseBody.indexOf(","); // find the first seperation - Bingo!? + + mCfg->total_power = responseBody.substring(responseBody.indexOf(":"), index).toDouble(); + } else if(json.containsKey(F("emeters"))) { + mCfg->total_power = (double)json[F("total_power")]; + } else if(String(mCfg->monitor_url).endsWith("data.json?node_id=1") ) { + tibber_parse(); + } else { + DPRINTLN(DBG_INFO, (F("ZeroExport() json error: cant find value in this query: ") + responseBody)); + return false; + } + } + else + { + DPRINTLN(DBG_INFO, F("ZeroExport(): Error ") + String(httpCode)); + return false; + } + httpClient.end(); + return true; + } + + void tibber_parse() + { + + } +*/ + + // Powermeter + + /** + * getPowermeterWattsShelly + */ + bool getPowermeterWattsShelly(JsonObject logObj, uint8_t group) { + bool result = false; + + mCfg->groups[group].pmPower = 0; + mCfg->groups[group].pmPowerL1 = 0; + mCfg->groups[group].pmPowerL2 = 0; + mCfg->groups[group].pmPowerL3 = 0; + + long int bTsp = millis(); + + HTTPClient http; + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setUserAgent("Ahoy-Agent"); // TODO: Ahoy-0.8.850024-zero http.setConnectTimeout(500); http.setTimeout(500); @@ -400,695 +1386,135 @@ return; } } - http.end(); - - long int eTsp = millis(); - logObj["b"] = bTsp; - logObj["e"] = eTsp; - logObj["d"] = eTsp - bTsp; - logObj["P"] = mCfg->groups[group].pmPower; - logObj["P1"] = mCfg->groups[group].pmPowerL1; - logObj["P2"] = mCfg->groups[group].pmPowerL2; - logObj["P3"] = mCfg->groups[group].pmPowerL3; - - return result; - } - - /** - * getPowermeterWattsTasmota - */ - bool getPowermeterWattsTasmota(JsonObject logObj, uint8_t group) { -// TODO: nicht komplett - bool ret = false; - - HTTPClient http; - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - http.setUserAgent("Ahoy-Agent"); -// TODO: Ahoy-0.8.850024-zero - http.setConnectTimeout(500); - http.setTimeout(500); - http.addHeader("Content-Type", "application/json"); - http.addHeader("Accept", "application/json"); -// TODO: Timeout von 1000 reduzieren? - 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) - { - logObj["error"] = "deserializeJson() failed: " + String(error.c_str()); - return ret; - } - -// TODO: Sum - ret = true; - -// TODO: L1 - -// TODO: L2 - -// TODO: L3 - -/* - 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; - */ - - logObj["P"] = mCfg->groups[group].pmPower; - logObj["P1"] = mCfg->groups[group].pmPowerL1; - logObj["P2"] = mCfg->groups[group].pmPowerL2; - logObj["P3"] = mCfg->groups[group].pmPowerL3; - } - http.end(); - - return ret; - } - - /** - * getPowermeterWattsMqtt - */ - bool getPowermeterWattsMqtt(JsonObject logObj, uint8_t group) { -// TODO: nicht komplett - bool ret = false; - - - -// logObj["P"] = mCfg->groups[group].pmPower; -// logObj["P1"] = mCfg->groups[group].pmPowerL1; -// logObj["P2"] = mCfg->groups[group].pmPowerL2; -// logObj["P3"] = mCfg->groups[group].pmPowerL3; -// } -// http.end(); - - return ret; - } - - /** - * getPowermeterWattsHichi - */ - bool getPowermeterWattsHichi(JsonObject logObj, uint8_t group) { -// TODO: nicht komplett - bool ret = false; - - - -// logObj["P"] = mCfg->groups[group].pmPower; -// logObj["P1"] = mCfg->groups[group].pmPowerL1; -// logObj["P2"] = mCfg->groups[group].pmPowerL2; -// logObj["P3"] = mCfg->groups[group].pmPowerL3; -// } -// http.end(); - - return ret; - } - - /** - * getPowermeterWattsTibber - */ - bool getPowermeterWattsTibber(JsonObject logObj, uint8_t group) { -// TODO: nicht komplett - bool ret = false; - - - -// logObj["P"] = mCfg->groups[group].pmPower; -// logObj["P1"] = mCfg->groups[group].pmPowerL1; -// logObj["P2"] = mCfg->groups[group].pmPowerL2; -// logObj["P3"] = mCfg->groups[group].pmPowerL3; -// } -// http.end(); - - return ret; - } - - // Inverter - - /** - * getInverterData - */ - bool getInverterData(uint8_t group) { - zeroExportGroup_t *cfgGroup = &mCfg->groups[group]; - - JsonObject logObj = mLog.createNestedObject("iv"); - logObj["grp"] = group; - - bool ret = false; - - long int bTsp = millis(); - - JsonArray logArrInv = logObj.createNestedArray("iv"); - - for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { - zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[inv]; - - JsonObject logObjInv = logArrInv.createNestedObject(); - logObjInv["iv"] = inv; - - // Wenn Inverter deaktiviert -> Eintrag ignorieren - logObjInv["en"] = cfgGroupInv->enabled; - if (!cfgGroupInv->enabled) { - continue; - } - if (cfgGroupInv->id <= 0) { - logObjInv["err"] = "WR aktiviert aber nicht ausgewählt"; - return false; - } - - // 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)cfgGroupInv->id) { - continue; - } - - // wenn Inverter deaktiviert -> Daten ignorieren -// logObjInv["cfgEnabled"] = iv->enabled(); -// if (!iv->enabled()) { -// continue; -// } - // wenn Inverter nicht verfügbar -> Daten ignorieren - logObjInv["Available"] = iv->isAvailable(); - if (!iv->isAvailable()) { - continue;; - } - // wenn Inverter nicht produziert -> Daten ignorieren - cfgGroupInv->state = iv->isProducing(); - logObjInv["Producing"] = iv->isProducing(); -// if (!iv->isProducing()) { -// 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 - - cfgGroupInv->power = iv->getChannelFieldValue(CH0, FLD_PAC, rec); - logObjInv["P_ac"] = cfgGroupInv->power; - - cfgGroupInv->dcVoltage = iv->getChannelFieldValue(CH1, FLD_UDC, rec); - logObjInv["U_dc"] = cfgGroupInv->dcVoltage; -// TODO: Eingang muss konfigurierbar sein - - // ACK - if (iv->powerLimitAck) { - iv->powerLimitAck = false; - cfgGroupInv->limitTsp = 0; - } - if (cfgGroupInv->limitTsp > 0) { - if ((millis() + 10000) > cfgGroupInv->limitTsp) { - cfgGroupInv->limitTsp = 0; - } - } - ret = true; - } - } - - long int eTsp = millis(); - logObj["b"] = bTsp; - logObj["e"] = eTsp; - logObj["d"] = eTsp - bTsp; - - return ret; - } - - // Battery - - /** - * batteryProtection - * @param uint8_t group - * @returns bool true - */ - bool batteryProtection(uint8_t group) { - zeroExportGroup_t *cfgGroup = &mCfg->groups[group]; -// TODO: Wenn kein WR gefunden wird, wird nicht abgeschaltet!!! -// TODO: Es fehlt die Möglichkeit manuell einzuschalten - JsonObject logObj = mLog.createNestedObject("bp"); - logObj["grp"] = group; - - long int bTsp = millis(); - - if (cfgGroup->battEnabled) { - logObj["enabled"] = 1; - - // Config - parameter check - if (cfgGroup->battVoltageOn <= cfgGroup->battVoltageOff) { - cfgGroup->battSwitch = false; - logObj["err"] = "Config - battVoltageOn(" + (String)cfgGroup->battVoltageOn + ") <= battVoltageOff(" + (String)cfgGroup->battVoltageOff + ")"; - return true; - } - - // Config - parameter check - if (cfgGroup->battVoltageOn <= (cfgGroup->battVoltageOff + 1)) { - cfgGroup->battSwitch = false; - logObj["err"] = "Config - battVoltageOn(" + (String)cfgGroup->battVoltageOn + ") <= battVoltageOff(" + (String)cfgGroup->battVoltageOff + " + 1V)"; - return true; - } - - // Config - parameter check - if (cfgGroup->battVoltageOn <= 22) { - cfgGroup->battSwitch = false; - logObj["err"] = "Config - battVoltageOn(" + (String)cfgGroup->battVoltageOn + ") <= 22V)"; - return true; - } - - JsonArray logArrInv = logObj.createNestedArray("iv"); - - for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { - zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[inv]; - - JsonObject logObjInv = logArrInv.createNestedObject(); - logObjInv["iv"] = inv; - - // Ignore disabled Inverter - if (!cfgGroupInv->enabled) { - continue; - } - if (cfgGroupInv->id <= 0) { - continue; - } - - logObjInv["battU"] = cfgGroupInv->dcVoltage; - - // Switch to ON - if (cfgGroupInv->dcVoltage > cfgGroup->battVoltageOn) { - cfgGroup->battSwitch = true; - continue; - } - - // Switch to OFF - if (cfgGroupInv->dcVoltage < cfgGroup->battVoltageOff) { - cfgGroup->battSwitch = false; - return true; - } - - } - } else { - logObj["enabled"] = 0; - - cfgGroup->battSwitch = true; - } - - logObj["switch"] = cfgGroup->battSwitch; - - long int eTsp = millis(); - logObj["b"] = bTsp; - logObj["e"] = eTsp; - logObj["d"] = eTsp - bTsp; - - return true; - } - - // Controller - - /** - * controller - * @param group - * @returns true/false - * Die Funktion berechnet alle Regelabweichungen und speichert die nötigen Korrekturen - */ - bool controller(uint8_t group) { - zeroExportGroup_t *cfgGroup = &mCfg->groups[group]; - - JsonObject logObj = mLog.createNestedObject("co"); - logObj["grp"] = group; - - long int bTsp = millis(); - - // Führungsgröße w in Watt - float w = cfgGroup->setPoint; - - logObj["w"] = w; - - // Regelgröße x in Watt - float x_Sum = cfgGroup->pmPower; - float x_L1 = cfgGroup->pmPowerL1; - float x_L2 = cfgGroup->pmPowerL2; - float x_L3 = cfgGroup->pmPowerL3; - - logObj["x_P"] = x_Sum; - logObj["x_P1"] = x_L1; - logObj["x_P2"] = x_L2; - logObj["x_P3"] = x_L3; - - // Regelabweichung e in Watt - float e_Sum = w - x_Sum; - float e_L1 = w - x_L1; - float e_L2 = w - x_L2; - float e_L3 = w - x_L3; - - logObj["e_P"] = e_Sum; - logObj["e_P1"] = e_L1; - logObj["e_P2"] = e_L2; - logObj["e_P3"] = e_L3; - - // Regler -// TODO: Regelparameter unter Advanced konfigurierbar? Aber erst wenn Regler komplett ingegriert. - const float Kp = -1; - const float Ki = -1; - const float Kd = -1; - - // - P-Anteil - float yP_Sum = Kp * e_Sum; - float yP_L1 = Kp * e_L1; - float yP_L2 = Kp * e_L2; - float yP_L3 = Kp * e_L3; - // - I-Anteil -// float esum = esum + e; -// float yI = Ki * Ta * esum; - float yI_Sum = 0; - float yI_L1 = 0; - float yI_L2 = 0; - float yI_L3 = 0; - // - D-Anteil -// float yD = Kd * (e -ealt) / Ta; -// float ealt = e; - float yD_Sum = 0; - float yD_L1 = 0; - float yD_L2 = 0; - float yD_L3 = 0; - // - PID-Anteil - float yPID_Sum = yP_Sum + yI_Sum + yD_Sum; - float yPID_L1 = yP_L1 + yI_L1 + yD_L1; - float yPID_L2 = yP_L2 + yI_L2 + yD_L2; - float yPID_L3 = yP_L3 + yI_L3 + yD_L3; - - // Regelbegrenzung -// TODO: Hier könnte man den maximalen Sprung begrenzen - - logObj["yPID_P"] = yPID_Sum; - logObj["yPID_P1"] = yPID_L1; - logObj["yPID_P2"] = yPID_L2; - logObj["yPID_P3"] = yPID_L3; - - cfgGroup->grpPower = yPID_Sum; - cfgGroup->grpPowerL1 = yPID_L1; - cfgGroup->grpPowerL2 = yPID_L2; - cfgGroup->grpPowerL3 = yPID_L3; + http.end(); long int eTsp = millis(); logObj["b"] = bTsp; logObj["e"] = eTsp; logObj["d"] = eTsp - bTsp; + logObj["P"] = mCfg->groups[group].pmPower; + logObj["P1"] = mCfg->groups[group].pmPowerL1; + logObj["P2"] = mCfg->groups[group].pmPowerL2; + logObj["P3"] = mCfg->groups[group].pmPowerL3; - return true; + return result; } /** - * setControl - * @param group - * @returns true/false - * Die Funktion liest alle gespeicherten Aufgaben und arbeitet diese in der korrekten Reihenfolge ab. + * getPowermeterWattsTasmota */ - bool setControl(uint8_t group) { - - zeroExportGroup_t *cfgGroup = &mCfg->groups[group]; - - JsonObject logObj = mLog.createNestedObject("sl"); - logObj["grp"] = group; - - long int bTsp = millis(); - - float deltaY_Sum = cfgGroup->grpPower; - float deltaY_L1 = cfgGroup->grpPowerL1; - float deltaY_L2 = cfgGroup->grpPowerL2; - float deltaY_L3 = cfgGroup->grpPowerL3; - - logObj["y_P"] = cfgGroup->grpPower; - logObj["y_P1"] = cfgGroup->grpPowerL1; - logObj["y_P2"] = cfgGroup->grpPowerL2; - logObj["y_P3"] = cfgGroup->grpPowerL3; - - bool grpTarget[7] = {false, false, false, false, false, false, false}; - uint8_t ivId_Pmin[7] = {0, 0, 0, 0, 0, 0, 0}; - uint8_t ivId_Pmax[7] = {0, 0, 0, 0, 0, 0, 0}; - uint16_t ivPmin[7] = {65535, 65535, 65535, 65535, 65535, 65535, 65535}; - uint16_t ivPmax[7] = {0, 0, 0, 0, 0, 0, 0}; - - for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { - zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[inv]; - - if (!cfgGroupInv->enabled) { - continue; - } - -// TODO: überspringen wenn keine Freigabe? - if (cfgGroupInv->limitTsp != 0) { - continue; - } + bool getPowermeterWattsTasmota(JsonObject logObj, uint8_t group) { +// TODO: nicht komplett + bool ret = false; - if ((cfgGroup->battSwitch) && (!cfgGroupInv->state)) { - setPower(&logObj, group, inv, true); - continue; - } + HTTPClient http; + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setUserAgent("Ahoy-Agent"); +// TODO: Ahoy-0.8.850024-zero + http.setConnectTimeout(500); + http.setTimeout(500); + http.addHeader("Content-Type", "application/json"); + http.addHeader("Accept", "application/json"); +// TODO: Timeout von 1000 reduzieren? + http.begin(mCfg->groups[group].pm_url); + if (http.GET() == HTTP_CODE_OK) + { - if ((!cfgGroup->battSwitch) && (cfgGroupInv->state)) { - setPower(&logObj, group, inv, false); - continue; + // Parsing + DynamicJsonDocument doc(2048); + DeserializationError error = deserializeJson(doc, http.getString()); + if (error) + { + logObj["error"] = "deserializeJson() failed: " + String(error.c_str()); + return ret; } - if (!cfgGroupInv->state) { - continue; - } +// TODO: Sum + ret = true; - if (cfgGroupInv->power < ivPmin[cfgGroupInv->target]) { - grpTarget[cfgGroupInv->target] = true; - ivPmin[cfgGroupInv->target] = cfgGroupInv->power; - ivId_Pmin[cfgGroupInv->target] = inv; - // Hier kein return oder continue sonst dauerreboot - } - if (cfgGroupInv->power > ivPmax[cfgGroupInv->target]) { - grpTarget[cfgGroupInv->target] = true; - ivPmax[cfgGroupInv->target] = cfgGroupInv->power; - ivId_Pmax[cfgGroupInv->target] = inv; - // Hier kein return oder continue sonst dauerreboot - } - } - for (uint8_t i = 0; i < 7; i++) { - logObj[String(i)] = String(i) + String(" grpTarget: ") + String(grpTarget[i]) + String(": ivPmin: ") + String(ivPmin[i]) + String(": ivPmax: ") + String(ivPmax[i]) + String(": ivId_Pmin: ") + String(ivId_Pmin[i]) + String(": ivId_Pmax: ") + String(ivId_Pmax[i]); - } +// TODO: L1 - for (uint8_t i = 7; i > 0; --i) { - if (!grpTarget[i]) { - continue; - } - logObj[String(String("10")+String(i))] = String(i) + String(" grpTarget: " + String(grpTarget[i])); +// TODO: L2 - float *deltaP; - switch(i) { - case 6: - case 3: - deltaP = &cfgGroup->grpPowerL3; - break; - case 5: - case 2: - deltaP = &cfgGroup->grpPowerL2; - break; - case 4: - case 1: - deltaP = &cfgGroup->grpPowerL1; - break; - case 0: - deltaP = &cfgGroup->grpPower; - break; - } +// TODO: L3 - // Leistung erhöhen - if (*deltaP > 0) { - // Toleranz - if (*deltaP < cfgGroup->powerTolerance) { - continue; - } - logObj["+deltaP"] = *deltaP; - zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[ivId_Pmin[i]]; - cfgGroupInv->limitNew = (uint16_t)((float)cfgGroupInv->limit + *deltaP); -// if (i != 0) { -// cfgGroup->grpPower - *deltaP; -// } - *deltaP = 0; -// if (cfgGroupInv->limitNew > cfgGroupInv->powerMax) { -// *deltaP = cfgGroupInv->limitNew - cfgGroupInv->powerMax; -// cfgGroupInv->limitNew = cfgGroupInv->powerMax; -// if (i != 0) { -// cfgGroup->grpPower + *deltaP; -// } -// } - setLimit(&logObj, group, ivId_Pmin[i]); - continue; - } +/* + 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; + */ - // Leistung reduzieren - if (*deltaP < 0) { - // Toleranz - if (*deltaP > -cfgGroup->powerTolerance) { - continue; - } - logObj["-deltaP"] = *deltaP; - zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[ivId_Pmax[i]]; - cfgGroupInv->limitNew = (uint16_t)((float)cfgGroupInv->limit + *deltaP); -// if (i != 0) { -// cfgGroup->grpPower - *deltaP; -// } - *deltaP = 0; -// if (cfgGroupInv->limitNew < cfgGroupInv->powerMin) { -// *deltaP = cfgGroupInv->limitNew - cfgGroupInv->powerMin; -// cfgGroupInv->limitNew = cfgGroupInv->powerMin; -// if (i != 0) { -// cfgGroup->grpPower + *deltaP; -// } -// } - setLimit(&logObj, group, ivId_Pmax[i]); - continue; - } + logObj["P"] = mCfg->groups[group].pmPower; + logObj["P1"] = mCfg->groups[group].pmPowerL1; + logObj["P2"] = mCfg->groups[group].pmPowerL2; + logObj["P3"] = mCfg->groups[group].pmPowerL3; } + http.end(); - long int eTsp = millis(); - logObj["b"] = bTsp; - logObj["e"] = eTsp; - logObj["d"] = eTsp - bTsp; - - return true; + return ret; } - // Funktionen - /** - * setLimit - * @param objLog - * @param group - * @param inv - * @returns true/false + * getPowermeterWattsMqtt */ - bool setLimit(JsonObject *objlog, uint8_t group, uint8_t inv) { - zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[inv]; - - JsonObject objLog = objlog->createNestedObject("setLimit"); - objLog["grp"] = group; - objLog["iv"] = inv; - - // Restriction LimitNew >= Pmin - if (cfgGroupInv->limitNew < cfgGroupInv->powerMin) { - cfgGroupInv->limitNew = cfgGroupInv->powerMin; - } - - // Restriction LimitNew <= Pmax - if (cfgGroupInv->limitNew > cfgGroupInv->powerMax) { - cfgGroupInv->limitNew = cfgGroupInv->powerMax; - } - - // Reject limit if difference < 5 W - if ((cfgGroupInv->limitNew > cfgGroupInv->limit + 5) && (cfgGroupInv->limitNew < cfgGroupInv->limit - 5)) { - objLog["err"] = "Diff < 5W"; - return false; - } - - cfgGroupInv->limit = cfgGroupInv->limitNew; - cfgGroupInv->limitTsp = millis(); + bool getPowermeterWattsMqtt(JsonObject logObj, uint8_t group) { +// TODO: nicht komplett + bool ret = false; - objLog["P"] = cfgGroupInv->limit; - objLog["tsp"] = cfgGroupInv->limitTsp; - // Limit übergeben - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); - obj["val"] = cfgGroupInv->limit; - obj["id"] = cfgGroupInv->id; - obj["path"] = "ctrl"; - obj["cmd"] = "limit_nonpersistent_absolute"; - mApi->ctrlRequest(obj); - objLog["data"] = obj; +// logObj["P"] = mCfg->groups[group].pmPower; +// logObj["P1"] = mCfg->groups[group].pmPowerL1; +// logObj["P2"] = mCfg->groups[group].pmPowerL2; +// logObj["P3"] = mCfg->groups[group].pmPowerL3; +// } +// http.end(); - return true; + return ret; } /** - * setPower - * @param objLog - * @param group - * @param inv - * @param state - * @returns true/false + * getPowermeterWattsHichi */ - bool setPower(JsonObject *objlog, uint8_t group, uint8_t inv, bool state) { - zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[inv]; - - JsonObject objLog = objlog->createNestedObject("setPower"); - objLog["grp"] = group; - objLog["iv"] = inv; - -// cfgGroupInv->limit = cfgGroupInv->limitNew; - cfgGroupInv->limitTsp = millis() + 30000; + bool getPowermeterWattsHichi(JsonObject logObj, uint8_t group) { +// TODO: nicht komplett + bool ret = false; -// objLog["P"] = cfgGroupInv->limit; - objLog["tsp"] = cfgGroupInv->limitTsp; - // State übergeben - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); - obj["val"] = state; - obj["id"] = cfgGroupInv->id; - obj["path"] = "ctrl"; - obj["cmd"] = "power"; - mApi->ctrlRequest(obj); - objLog["data"] = obj; +// logObj["P"] = mCfg->groups[group].pmPower; +// logObj["P1"] = mCfg->groups[group].pmPowerL1; +// logObj["P2"] = mCfg->groups[group].pmPowerL2; +// logObj["P3"] = mCfg->groups[group].pmPowerL3; +// } +// http.end(); - return false; + return ret; } /** - * setReboot - * @param objLog - * @param group - * @param inv - * @returns true/false + * getPowermeterWattsTibber */ - bool setReboot(JsonObject *objlog, uint8_t group, uint8_t inv) { - zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[inv]; - - JsonObject objLog = objlog->createNestedObject("setReboot"); - objLog["grp"] = group; - objLog["iv"] = inv; - -// cfgGroupInv->limit = cfgGroupInv->limitNew; - cfgGroupInv->limitTsp = millis() + 30000; + bool getPowermeterWattsTibber(JsonObject logObj, uint8_t group) { +// TODO: nicht komplett + bool ret = false; -// objLog["P"] = cfgGroupInv->limit; - objLog["tsp"] = cfgGroupInv->limitTsp; - // Reboot übergeben - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); -// obj["val"] = cfgGroupInv->limit; - obj["id"] = cfgGroupInv->id; - obj["path"] = "ctrl"; - obj["cmd"] = "restart"; - mApi->ctrlRequest(obj); - objLog["data"] = obj; +// logObj["P"] = mCfg->groups[group].pmPower; +// logObj["P1"] = mCfg->groups[group].pmPowerL1; +// logObj["P2"] = mCfg->groups[group].pmPowerL2; +// logObj["P3"] = mCfg->groups[group].pmPowerL3; +// } +// http.end(); - return false; + return ret; } /* diff --git a/src/web/RestApi.h b/src/web/RestApi.h index c1409f04..2377e5fa 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -718,6 +718,8 @@ class RestApi { #if defined(PLUGIN_ZEROEXPORT) void getZeroExport(JsonObject obj) { obj[F("enabled")] = (bool) mConfig->plugin.zeroExport.enabled; + obj[F("log_over_webserial")] = (bool) mConfig->plugin.zeroExport.log_over_webserial; + obj[F("log_over_mqtt")] = (bool) mConfig->plugin.zeroExport.log_over_mqtt; // Groups obj[F("max_groups")] = ZEROEXPORT_MAX_GROUPS; JsonArray arrGroup = obj.createNestedArray(F("groups")); diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 4e27c4f7..ac1c969b 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -314,6 +314,10 @@
{#ZE_ENABLED}
+
{#ZE_LOG_OVER_WEBSERIAL}
+
+
{#ZE_LOG_OVER_MQTT}
+
@@ -1563,7 +1567,6 @@ } } - /*ENDIF_PLUGIN_ZEROEXPORT*/ function parseZeroExport(obj, type) { @@ -1573,6 +1576,12 @@ // enabled document.getElementsByName("ze_enabled")[0].checked = obj["enabled"]; + // log_over_webserial + document.getElementsByName("ze_log_over_webserial")[0].checked = obj["log_over_webserial"]; + + // log_over_mqtt + document.getElementsByName("ze_log_over_mqtt")[0].checked = obj["log_over_mqtt"]; + // groups maxGroups = obj["max_groups"]; diff --git a/src/web/lang.json b/src/web/lang.json index f74b3650..035b2b8f 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -778,6 +778,16 @@ "en": "Enabled", "de": "Aktiviert" }, + { + "token": "ZE_LOG_OVER_WEBSERIAL", + "en": "Log over Webserial", + "de": "Log in Webserial" + }, + { + "token": "ZE_LOG_OVER_MQTT", + "en": "Log over MQTT", + "de": "Log in MQTT" + }, { "token": "ZE_GROUP_ENABLED", "en": "State:", diff --git a/src/web/web.h b/src/web/web.h index c3c866d5..9c8d4521 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -589,6 +589,8 @@ class Web { // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) mConfig->plugin.zeroExport.enabled = (request->arg("ze_enabled") == "on"); + mConfig->plugin.zeroExport.log_over_webserial = (request->arg("ze_log_over_webserial") == "on"); + mConfig->plugin.zeroExport.log_over_mqtt = (request->arg("ze_log_over_mqtt") == "on"); // TODO: sortieren // mConfig->plugin.zeroExport.enabled = (request->arg("en_zeroexport") == "on"); // mConfig->plugin.zeroExport.two_percent = (request->arg("two_percent") == "on");