diff --git a/src/app.cpp b/src/app.cpp index 6b8c9826..69626606 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -93,6 +93,11 @@ void app::setup() { mZeroExport.resetWaitRebootAck(iv); }); #endif /*PLUGIN_ZEROEXPORT*/ + #if defined(PLUGIN_ZEROEXPORT) + mCommunication.addNewDataListener([this] (Inverter<> *iv) { + mZeroExport.newDataAvailable(iv); + }); + #endif /*PLUGIN_ZEROEXPORT*/ mSys.setup(&mTimestamp, &mConfig->inst, this); for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { diff --git a/src/defines.h b/src/defines.h index e21af7bd..cdaea069 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 970003 +#define VERSION_PATCH 970004 //------------------------------------- typedef struct { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 629e4363..e6338d25 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -19,6 +19,7 @@ typedef std::function *)> payloadListenerType; typedef std::function *)> powerLimitAckListenerType; typedef std::function *)> powerPowerAckListenerType; typedef std::function *)> powerRebootAckListenerType; +typedef std::function *)> newDataListenerType; typedef std::function *)> alarmListenerType; class Communication : public CommQueue<> { @@ -51,6 +52,10 @@ class Communication : public CommQueue<> { mCbPwrRebootAck = cb; } + void addNewDataListener(newDataListenerType cb) { + mCbNewData = cb; + } + void addAlarmListener(alarmListenerType cb) { mCbAlarm = cb; } @@ -601,6 +606,7 @@ class Communication : public CommQueue<> { for (uint8_t i = 0; i < rec->length; i++) { q->iv->addValue(i, mPayload.data(), rec); } +(mCbNewData)(q->iv); rec->mqttSentStatus = MqttSentStatus::NEW_DATA; q->iv->rssi = rssi; @@ -1057,6 +1063,7 @@ class Communication : public CommQueue<> { powerLimitAckListenerType mCbPwrAck = NULL; powerPowerAckListenerType mCbPwrPowerAck = NULL; powerRebootAckListenerType mCbPwrRebootAck = NULL; + newDataListenerType mCbNewData = NULL; alarmListenerType mCbAlarm = NULL; Heuristic mHeu; uint32_t mLastEmptyQueueMillis = 0; diff --git a/src/plugins/zeroExport/zeroExport.h b/src/plugins/zeroExport/zeroExport.h index 6622d749..e07e187b 100644 --- a/src/plugins/zeroExport/zeroExport.h +++ b/src/plugins/zeroExport/zeroExport.h @@ -9,13 +9,11 @@ #define __ZEROEXPORT__ #include -#include #include +#include #include "AsyncJson.h" - #include "powermeter.h" -//#include "SML.h" template @@ -24,1397 +22,1407 @@ template // TODO: Der Teil der noch in app.pp steckt komplett hier in die Funktion verschieben. class ZeroExport { - - public: - - /** ZeroExport - * Konstruktor - */ - ZeroExport() { - mIsInitialized = false; - for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { - for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { - mIv[group][inv] = nullptr; - } + public: + /** ZeroExport + * Konstruktor + */ + ZeroExport() { + mIsInitialized = false; + for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mIv[group][inv] = nullptr; } } - - /** ~ZeroExport - * Destruktor - */ - ~ZeroExport() {} - - /** setup - * Initialisierung - */ - void setup(zeroExport_t *cfg, HMSYSTEM *sys, settings_t *config, RestApiType *api, PubMqttType *mqtt) { - mCfg = cfg; - mSys = sys; - mConfig = config; - mApi = api; - mMqtt = mqtt; - - mIsInitialized = mPowermeter.setup(mCfg); + } + + /** ~ZeroExport + * Destruktor + */ + ~ZeroExport() {} + + /** setup + * Initialisierung + */ + void setup(zeroExport_t *cfg, HMSYSTEM *sys, settings_t *config, RestApiType *api, PubMqttType *mqtt) { + mCfg = cfg; + mSys = sys; + mConfig = config; + mApi = api; + mMqtt = mqtt; + + mIsInitialized = mPowermeter.setup(mCfg); + } + + /** loop + * Arbeitsschleife + */ + void loop(void) { + if ((!mIsInitialized) || (!mCfg->enabled)) { + return; } - /** loop - * Arbeitsschleife - */ - void loop(void) { - if ((!mIsInitialized) || (!mCfg->enabled)) { - return; + mPowermeter.loop(); + + for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + if (!mCfg->groups[group].enabled) { + continue; } - mPowermeter.loop(); + switch (mCfg->groups[group].state) { + case zeroExportState::INIT: + if (groupInit(group)) sendLog(); + break; + 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 (groupGetInverterData(group)) sendLog(); + break; + case zeroExportState::BATTERYPROTECTION: + if (groupBatteryprotection(group)) sendLog(); + break; + case zeroExportState::GETPOWERMETER: + if (groupGetPowermeter(group)) sendLog(); + break; + 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: + case zeroExportState::ERROR: + default: + mCfg->groups[group].state = zeroExportState::INIT; + mCfg->groups[group].stateNext = zeroExportState::INIT; + if (millis() > (mCfg->groups[group].lastRefresh + (mCfg->groups[group].refresh * 1000UL))) { + mCfg->groups[group].lastRefresh = millis(); + } + break; + } + } + } + + /** tickerSecond + * Zeitimpuls + */ + void tickerSecond() { + // TODO: Warten ob benötigt, ggf ein bit setzen, das in der loop() abgearbeitet wird. + if ((!mIsInitialized) || (!mCfg->enabled)) { + return; + } - for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { - if (!mCfg->groups[group].enabled) { - continue; + // Wait for ACK + for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + // Limit + if (mCfg->groups[group].inverters[inv].waitLimitAck > 0) { + mCfg->groups[group].inverters[inv].waitLimitAck--; } - - switch (mCfg->groups[group].state) { - case zeroExportState::INIT: - if (groupInit(group)) sendLog(); - break; - 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 (groupGetInverterData(group)) sendLog(); - break; - case zeroExportState::BATTERYPROTECTION: - if (groupBatteryprotection(group)) sendLog(); - break; - case zeroExportState::GETPOWERMETER: - if (groupGetPowermeter(group)) sendLog(); - break; - 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(); -// waitForAck fehlt noch -mCfg->groups[group].state = zeroExportState::WAIT; -mCfg->groups[group].stateNext = zeroExportState::WAIT; -mCfg->groups[group].lastRefresh = millis();; - break; - case zeroExportState::SETREBOOT: - if (groupSetReboot(group)) sendLog(); -// waitForAck fehlt noch -mCfg->groups[group].lastRefresh = millis();; -mCfg->groups[group].state = zeroExportState::WAIT; -mCfg->groups[group].stateNext = zeroExportState::WAIT; - break; - case zeroExportState::FINISH: - case zeroExportState::ERROR: - default: - mCfg->groups[group].state = zeroExportState::INIT; - mCfg->groups[group].stateNext = zeroExportState::INIT; - if (millis() > (mCfg->groups[group].lastRefresh + (mCfg->groups[group].refresh * 1000UL))) { - mCfg->groups[group].lastRefresh = millis(); - } - break; + // Power + if (mCfg->groups[group].inverters[inv].waitPowerAck > 0) { + mCfg->groups[group].inverters[inv].waitPowerAck--; + } + // Reboot + if (mCfg->groups[group].inverters[inv].waitRebootAck > 0) { + mCfg->groups[group].inverters[inv].waitRebootAck--; } } } + } + + /** resetWaitLimitAck + * + * @param + */ + void resetWaitLimitAck(Inverter<> *iv) { + if ((!mIsInitialized) || (!mCfg->enabled)) { + return; + } - /** tickerSecond - * Zeitimpuls - */ - void tickerSecond() { -// TODO: Warten ob benötigt, ggf ein bit setzen, das in der loop() abgearbeitet wird. - if ((!mIsInitialized) || (!mCfg->enabled)) { - return; - } - - // Wait for ACK - for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { - for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { - // Limit - if (mCfg->groups[group].inverters[inv].waitLimitAck > 0) { - mCfg->groups[group].inverters[inv].waitLimitAck--; - } - // Power - if (mCfg->groups[group].inverters[inv].waitPowerAck > 0) { - mCfg->groups[group].inverters[inv].waitPowerAck--; - } - // Reboot - if (mCfg->groups[group].inverters[inv].waitRebootAck > 0) { - mCfg->groups[group].inverters[inv].waitRebootAck--; - } + for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + if (iv->id == (uint8_t)mCfg->groups[group].inverters[inv].id) { + unsigned long bTsp = millis(); + + mLog["t"] = "resetWaitLimitAck"; + mLog["g"] = group; + mLog["i"] = inv; + mLog["id"] = iv->id; + mCfg->groups[group].inverters[inv].waitLimitAck = 0; + mLog["w"] = 0; + + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + sendLog(); + return; } } } + } + + /** setPowerAck + * + * @param + */ + void resetWaitPowerAck(Inverter<> *iv) { + if ((!mIsInitialized) || (!mCfg->enabled)) { + return; + } - /** resetWaitLimitAck - * - * @param - */ - void resetWaitLimitAck(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 == (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 = 0; - mLog["inv"] = inv; - mLog["wait"] = 0; - unsigned long eTsp = millis(); - mLog["E"] = eTsp; - mLog["D"] = eTsp - bTsp; - sendLog(); - } + 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) { + unsigned long bTsp = millis(); + + mLog["t"] = "resetWaitPowerAck"; + mLog["g"] = group; + mLog["i"] = inv; + mLog["id"] = iv->id; + mCfg->groups[group].inverters[inv].waitPowerAck = 30; + mLog["w"] = 30; + + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + sendLog(); + return; } } } + } + + /** resetWaitRebootAck + * + * @param + */ + void resetWaitRebootAck(Inverter<> *iv) { + if ((!mIsInitialized) || (!mCfg->enabled)) { + return; + } - /** 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) { - mLog["group"] = group; - mLog["type"] = "resetWaitPowerAck"; - unsigned long bTsp = millis(); - mLog["B"] = bTsp; - mLog["id"] = iv->id; - mCfg->groups[group].inverters[inv].waitPowerAck = 0; - mLog["inv"] = inv; - mLog["wait"] = 0; - unsigned long eTsp = millis(); - mLog["E"] = eTsp; - mLog["D"] = eTsp - bTsp; - sendLog(); - } + 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) { + unsigned long bTsp = millis(); + + mLog["t"] = "resetWaitRebootAck"; + mLog["g"] = group; + mLog["i"] = inv; + mLog["id"] = iv->id; + mCfg->groups[group].inverters[inv].waitRebootAck = 30; + mLog["w"] = 30; + + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + sendLog(); + return; } } } + } + + /** newDataAvailable + * + */ + void newDataAvailable(Inverter<> *iv) { + if ((!mIsInitialized) || (!mCfg->enabled)) { + return; + } - /** 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) { - mLog["group"] = group; - mLog["type"] = "resetWaitRebootAck"; - unsigned long bTsp = millis(); - mLog["B"] = bTsp; - mLog["id"] = iv->id; - mCfg->groups[group].inverters[inv].waitRebootAck = 0; - mLog["inv"] = inv; - mLog["wait"] = 0; - unsigned long eTsp = millis(); - mLog["E"] = eTsp; - mLog["D"] = eTsp - bTsp; - sendLog(); - } + // if (!iv->isAvailable()) { + // return; + // } + + 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) { + unsigned long bTsp = millis(); + + mLog["t"] = "newDataAvailable"; + mLog["g"] = group; + mLog["i"] = inv; + mLog["id"] = iv->id; + mLog["a"] = iv->isAvailable(); + mLog["ivL%"] = iv->actPowerLimit; + mLog["ivPm"] = iv->getMaxPower(); + mLog["ivL"] = (uint16_t)(iv->getMaxPower() / 100 * iv->actPowerLimit); + mLog["zeL"] = (uint16_t)mCfg->groups[group].inverters[inv].limit; + + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + sendLog(); + return; } } } + } + + private: + /** groupInit + * initialisiert die Gruppe und sucht die ivPointer + * @param group + * @returns true/false + * @todo getInverterById statt getInverterByPos, dann würde die Variable *iv und die Schleife nicht gebraucht. + */ + bool groupInit(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + + mLog["t"] = "groupInit"; + mLog["g"] = group; + + 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; + } + // Search/Set ivPointer + JsonArray logArr = mLog.createNestedArray("ix"); + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + JsonObject logObj = logArr.createNestedObject(); + logObj["i"] = inv; - private: - - /** 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; + mIv[group][inv] = nullptr; - mCfg->groups[group].stateLast = zeroExportState::INIT; + // Inverter not enabled -> ignore + if (!mCfg->groups[group].inverters[inv].enabled) { + continue; + } - // Init ivPointer - for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { - mIv[group][inv] = nullptr; - doLog = true; + // Inverter not selected -> ignore + if (mCfg->groups[group].inverters[inv].id <= 0) { + continue; } - // 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) { + // Load Config + Inverter<> *iv; + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { + iv = mSys->getInverterByPos(i); + + // Inverter not configured -> ignore + if (iv == NULL) { continue; } - // Inverter not selected -> ignore - if (mCfg->groups[group].inverters[inv].id <= 0) { + + // Inverter not matching -> ignore + if (iv->id != (uint8_t)mCfg->groups[group].inverters[inv].id) { 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; - } - } - // 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; + // Save Inverter + logObj["pos"] = i; + logObj["id"] = iv->id; + mIv[group][inv] = mSys->getInverterByPos(i); + + doLog = true; } + } + + // Init Acks + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mCfg->groups[group].inverters[inv].waitLimitAck = 0; + mCfg->groups[group].inverters[inv].waitPowerAck = 0; + mCfg->groups[group].inverters[inv].waitRebootAck = 0; + } + + // Next + mCfg->groups[group].state = zeroExportState::WAIT; + mCfg->groups[group].stateNext = zeroExportState::WAIT; + + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + + mCfg->groups[group].lastRun = eTsp; + mCfg->groups[group].lastRefresh = eTsp; - // Next - mCfg->groups[group].state = zeroExportState::WAIT; - mCfg->groups[group].stateNext = zeroExportState::WAIT; - mLog["next"] = "WAIT"; + return doLog; + } - unsigned long eTsp = millis(); - mLog["E"] = eTsp; - mLog["D"] = eTsp - bTsp; - mCfg->groups[group].lastRun = eTsp; - mCfg->groups[group].lastRefresh = eTsp; + /** groupWait + * pausiert die Gruppe + * @param group + * @returns true/false + */ + bool groupWait(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + + mLog["t"] = "groupWait"; + mLog["g"] = group; + + // Wait 60s + if (bTsp <= (mCfg->groups[group].lastRun + 60000UL)) { 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 (bTsp <= (mCfg->groups[group].lastRun + 60000UL)) { - return result; - } + mCfg->groups[group].stateLast = zeroExportState::WAIT; - mCfg->groups[group].stateLast = zeroExportState::WAIT; + doLog = true; - result = true; + // Next + if (mCfg->groups[group].state != mCfg->groups[group].stateNext) { + mCfg->groups[group].state = mCfg->groups[group].stateNext; + } else { + mCfg->groups[group].state = zeroExportState::WAITREFRESH; + mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; + } - // 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["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; - unsigned long eTsp = millis(); - mLog["E"] = eTsp; - mLog["D"] = eTsp - bTsp; - mCfg->groups[group].lastRun = eTsp; - mCfg->groups[group].lastRefresh = eTsp; - return result; - } + mCfg->groups[group].lastRun = eTsp; + mCfg->groups[group].lastRefresh = eTsp; - /** 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 (bTsp <= (mCfg->groups[group].lastRefresh + (mCfg->groups[group].refresh * 1000UL))) { - return result; - } + return doLog; + } - mCfg->groups[group].stateLast = zeroExportState::WAITREFRESH; - - result = true; - - // Next - #if defined(ZEROEXPORT_DEV_POWERMETER) - mCfg->groups[group].state = zeroExportState::GETPOWERMETER; - mCfg->groups[group].stateNext = zeroExportState::GETPOWERMETER; - mLog["next"] = "WAITREFRESH"; - #else - mCfg->groups[group].state = zeroExportState::GETINVERTERACKS; - mCfg->groups[group].stateNext = zeroExportState::GETINVERTERACKS; - mLog["next"] = "GETINVERTERACKS"; - #endif - - 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 doLog = false; + unsigned long bTsp = millis(); + + mLog["t"] = "groupWaitRefresh"; + mLog["g"] = group; + + // Wait Refreshtime + if (bTsp <= (mCfg->groups[group].lastRefresh + (mCfg->groups[group].refresh * 1000UL))) { + return doLog; } - /** 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 250ms - if (mCfg->groups[group].stateLast == zeroExportState::GETINVERTERACKS) { - if (bTsp <= (mCfg->groups[group].lastRun + 250UL)) { - return doLog; - } + mCfg->groups[group].stateLast = zeroExportState::WAITREFRESH; + + doLog = true; + + // Next +#if defined(ZEROEXPORT_DEV_POWERMETER) + mCfg->groups[group].state = zeroExportState::GETPOWERMETER; + mCfg->groups[group].stateNext = zeroExportState::GETPOWERMETER; +#else + mCfg->groups[group].state = zeroExportState::GETINVERTERACKS; + mCfg->groups[group].stateNext = zeroExportState::GETINVERTERACKS; +#endif + + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + + mCfg->groups[group].lastRun = eTsp; + + return doLog; + } + + /** 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["t"] = "groupGetInverterAcks"; + mLog["g"] = group; + + // Wait 250ms + if (mCfg->groups[group].stateLast == zeroExportState::GETINVERTERACKS) { + if (bTsp <= (mCfg->groups[group].lastRun + 250UL)) { + return doLog; } + } - mCfg->groups[group].stateLast = zeroExportState::GETINVERTERACKS; + mCfg->groups[group].stateLast = zeroExportState::GETINVERTERACKS; - doLog = true; + 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; + // Wait Acks + JsonArray logArr = mLog.createNestedArray("ix"); + bool wait = false; + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + JsonObject logObj = logArr.createNestedObject(); + logObj["i"] = 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 > 0) { - logObj["limit"] = mCfg->groups[group].inverters[inv].waitLimitAck; - wait = true; - } - // waitPowerAck - if (mCfg->groups[group].inverters[inv].waitPowerAck > 0) { - logObj["power"] = mCfg->groups[group].inverters[inv].waitPowerAck; - wait = true; - } - // waitRebootAck - if (mCfg->groups[group].inverters[inv].waitRebootAck > 0) { - logObj["reboot"] = mCfg->groups[group].inverters[inv].waitRebootAck; - wait = true; - } + // 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["a"] = false; + wait = true; + } + // waitLimitAck + if (mCfg->groups[group].inverters[inv].waitLimitAck > 0) { + logObj["wL"] = mCfg->groups[group].inverters[inv].waitLimitAck; + wait = true; } + // waitPowerAck + if (mCfg->groups[group].inverters[inv].waitPowerAck > 0) { + logObj["wP"] = mCfg->groups[group].inverters[inv].waitPowerAck; + wait = true; + } + // waitRebootAck + if (mCfg->groups[group].inverters[inv].waitRebootAck > 0) { + logObj["wR"] = mCfg->groups[group].inverters[inv].waitRebootAck; + wait = true; + } + } -// if (wait) { -// if (mCfg->groups[group].lastRun > (millis() - 30000UL)) { -// wait = false; -// } -// } + mLog["w"] = wait; - mLog["wait"] = wait; + // Next + if (!wait) { + mCfg->groups[group].state = zeroExportState::GETINVERTERDATA; + mCfg->groups[group].stateNext = zeroExportState::GETINVERTERDATA; + } - // Next - if (!wait) { - mCfg->groups[group].state = zeroExportState::GETINVERTERDATA; - mCfg->groups[group].stateNext = zeroExportState::GETINVERTERDATA; + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + + mCfg->groups[group].lastRun = eTsp; + + return doLog; + } + + /** groupGetInverterData + * + * @param group + * @returns true/false + */ + bool groupGetInverterData(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + + mLog["t"] = "groupGetInverterData"; + mLog["g"] = group; + + mCfg->groups[group].stateLast = zeroExportState::GETINVERTERDATA; + + doLog = true; + + // Get Data + JsonArray logArr = mLog.createNestedArray("ix"); + bool wait = false; + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + JsonObject logObj = logArr.createNestedObject(); + logObj["i"] = 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; } - unsigned long eTsp = millis(); - mLog["E"] = eTsp; - mLog["D"] = eTsp - bTsp; - mCfg->groups[group].lastRun = eTsp; - return doLog; + if (!mIv[group][inv]->isAvailable()) { + 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 + + // Get Limit + // uint16_t Limit = mIv[group][inv]->actPowerLimit; } - /** 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; + // Next + mCfg->groups[group].state = zeroExportState::BATTERYPROTECTION; + mCfg->groups[group].stateNext = zeroExportState::BATTERYPROTECTION; - mCfg->groups[group].stateLast = zeroExportState::GETINVERTERDATA; + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; - doLog = true; + mCfg->groups[group].lastRun = eTsp; + + return doLog; + } + + /** groupBatteryprotection + * Batterieschutzfunktion + * @param group + * @returns true/false + */ + bool groupBatteryprotection(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + + mLog["t"] = "groupBatteryprotection"; + mLog["g"] = group; + + mCfg->groups[group].stateLast = zeroExportState::BATTERYPROTECTION; + mCfg->groups[group].state = zeroExportState::GETPOWERMETER; + mCfg->groups[group].stateNext = zeroExportState::GETPOWERMETER; + + 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; + } + + int8_t id = 0; + float U = 0; - // 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; + zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[inv]; - // Inverter not enabled -> ignore - if (!mCfg->groups[group].inverters[inv].enabled) { + // Ignore disabled Inverter + if (!cfgGroupInv->enabled) { continue; } - // Inverter not selected -> ignore - if (mCfg->groups[group].inverters[inv].id <= 0) { + if (cfgGroupInv->id <= 0) { continue; } - if (!mIv[group][inv]->isAvailable()) - { + if (!mIv[group][inv]->isAvailable()) { + if (U > 0) { + continue; + } + U = 0; + id = cfgGroupInv->id; continue; } - // Get Pac + // Get U 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 - + 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; + } } - // Next - mCfg->groups[group].state = zeroExportState::BATTERYPROTECTION; - mCfg->groups[group].stateNext = zeroExportState::BATTERYPROTECTION; + mLog["i"] = id; + mLog["U"] = U; - unsigned long eTsp = millis(); - mLog["E"] = eTsp; - mLog["D"] = eTsp - bTsp; - mCfg->groups[group].lastRun = eTsp; - return doLog; - } - - /** 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; - - mCfg->groups[group].stateLast = zeroExportState::BATTERYPROTECTION; - mCfg->groups[group].state = zeroExportState::GETPOWERMETER; - mCfg->groups[group].stateNext = zeroExportState::GETPOWERMETER; + // Switch to ON + if (U > mCfg->groups[group].battVoltageOn) { + mCfg->groups[group].battSwitch = true; + mLog["act"] = "On"; - doLog = true; + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + // zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[inv]; - // Protection - if (mCfg->groups[group].battEnabled) { - mLog["en"] = true; + // 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; + } - // 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; - } + // Abbruch weil Inverter nicht verfügbar + if (!mIv[group][inv]->isAvailable()) { + mLog["err"] = "is not Available"; + continue; + } - // 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; - } + // Nothing todo + if (mCfg->groups[group].battSwitch == mIv[group][inv]->isProducing()) { + mLog["err"] = "battSwitch " + String(mCfg->groups[group].battSwitch) + " == isProducing()" + String(mIv[group][inv]->isProducing()); + continue; + } - // 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; + mCfg->groups[group].state = zeroExportState::SETPOWER; + mCfg->groups[group].stateNext = zeroExportState::SETPOWER; } + } - uint8_t id = 0; - float U = 0; + // Switch to OFF + if (U < mCfg->groups[group].battVoltageOff) { + mCfg->groups[group].battSwitch = false; + mLog["act"] = "Off"; for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { - zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[inv]; + // zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[inv]; - // Ignore disabled Inverter - if (!cfgGroupInv->enabled) { + // Inverter not enabled -> ignore + if (!mCfg->groups[group].inverters[inv].enabled) { continue; } - if (cfgGroupInv->id <= 0) { + // Inverter not selected -> ignore + if (mCfg->groups[group].inverters[inv].id <= 0) { continue; } -if (!mIv[group][inv]->isAvailable()) { - if (U > 0) { - continue; - } - U = 0; - id = cfgGroupInv->id; + // Abbruch weil Inverter nicht verfügbar + if (!mIv[group][inv]->isAvailable()) { + mLog["err"] = "is not Available"; 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; + // Nothing todo + if (mCfg->groups[group].battSwitch == mIv[group][inv]->isProducing()) { + mLog["err"] = "battSwitch " + String(mCfg->groups[group].battSwitch) + " == isProducing()" + String(mIv[group][inv]->isProducing()); + continue; } - } - mLog["inv"] = id; - mLog["U"] = U; - - // Switch to ON - if (U > mCfg->groups[group].battVoltageOn) { - mCfg->groups[group].battSwitch = true; - mLog["action"] = "On"; mCfg->groups[group].state = zeroExportState::SETPOWER; mCfg->groups[group].stateNext = zeroExportState::SETPOWER; } + } + } else { + mLog["en"] = false; - // Switch to OFF - if (U < mCfg->groups[group].battVoltageOff) { - mCfg->groups[group].battSwitch = false; - mLog["action"] = "Off"; - mCfg->groups[group].state = zeroExportState::SETPOWER; - mCfg->groups[group].stateNext = zeroExportState::SETPOWER; - } - } else { - mLog["en"] = false; + mCfg->groups[group].battSwitch = true; + } - mCfg->groups[group].battSwitch = true; - } + mLog["sw"] = mCfg->groups[group].battSwitch; - mLog["sw"] = mCfg->groups[group].battSwitch; + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; -// // Next -// mCfg->groups[group].state = zeroExportState::GETPOWERMETER; -// mCfg->groups[group].stateNext = zeroExportState::GETPOWERMETER; + mCfg->groups[group].lastRun = eTsp; - unsigned long eTsp = millis(); - mLog["E"] = eTsp; - mLog["D"] = eTsp - bTsp; - mCfg->groups[group].lastRun = eTsp; - return doLog; - } + return doLog; + } - /** groupGetPowermeter - * Holt die Daten vom Powermeter - * @param group - * @returns true/false - */ - bool groupGetPowermeter(uint8_t group) { - bool doLog = false; - unsigned long bTsp = millis(); - mLog["group"] = group; - mLog["type"] = "groupGetPowermeter"; - mLog["B"] = bTsp; + /** groupGetPowermeter + * Holt die Daten vom Powermeter + * @param group + * @returns true/false + */ + bool groupGetPowermeter(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); - mCfg->groups[group].stateLast = zeroExportState::GETPOWERMETER; + mLog["t"] = "groupGetPowermeter"; + mLog["g"] = group; - doLog = true; + mCfg->groups[group].stateLast = zeroExportState::GETPOWERMETER; - bool result = false; - result = mPowermeter.getData(mLog, group); + doLog = true; -// TODO: eventuell muss hier geprüft werden ob die Daten vom Powermeter plausibel sind. + bool result = false; + result = mPowermeter.getData(mLog, group); + if ( + (mCfg->groups[group].pmPower == 0) && + (mCfg->groups[group].pmPower == 0) && + (mCfg->groups[group].pmPower == 0) && + (mCfg->groups[group].pmPower == 0)) { + return doLog; + } - // Next - #if defined(ZEROEXPORT_DEV_POWERMETER) + // TODO: eventuell muss hier geprüft werden ob die Daten vom Powermeter plausibel sind. + + // Next +#if defined(ZEROEXPORT_DEV_POWERMETER) + mCfg->groups[group].state = zeroExportState::WAITREFRESH; + mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; + mCfg->groups[group].lastRefresh = millis(); +#else + if (result) { + mCfg->groups[group].state = zeroExportState::CONTROLLER; + mCfg->groups[group].stateNext = zeroExportState::CONTROLLER; + } +#endif + + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + + mCfg->groups[group].lastRun = eTsp; + + return doLog; + } + + /** groupController + * PID-Regler + * @param group + * @returns true/false + */ + bool groupController(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); + + mLog["t"] = "groupController"; + mLog["g"] = group; + + mCfg->groups[group].stateLast = zeroExportState::GETPOWERMETER; + + doLog = true; + + // 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; + + if ( + (e < mCfg->groups[group].powerTolerance) && (e > -mCfg->groups[group].powerTolerance) && + (e1 < mCfg->groups[group].powerTolerance) && (e1 > -mCfg->groups[group].powerTolerance) && + (e2 < mCfg->groups[group].powerTolerance) && (e2 > -mCfg->groups[group].powerTolerance) && + (e3 < mCfg->groups[group].powerTolerance) && (e3 > -mCfg->groups[group].powerTolerance)) { mCfg->groups[group].state = zeroExportState::WAITREFRESH; mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; - mCfg->groups[group].lastRefresh = millis();; - #else - if (result) { - mCfg->groups[group].state = zeroExportState::CONTROLLER; - mCfg->groups[group].stateNext = zeroExportState::CONTROLLER; - } - #endif - - unsigned long eTsp = millis(); - mLog["E"] = eTsp; - mLog["D"] = eTsp - bTsp; - mCfg->groups[group].lastRun = eTsp; + mCfg->groups[group].lastRun = bTsp; + mLog["tol"] = mCfg->groups[group].powerTolerance; return doLog; } - /** groupController - * PID-Regler - * @param group - * @returns true/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].stateLast = zeroExportState::GETPOWERMETER; - - doLog = true; + // Regler + // TODO: Regelparameter unter Advanced konfigurierbar? Aber erst wenn Regler komplett ingegriert. + float Kp = mCfg->groups[group].Kp; + float Ki = mCfg->groups[group].Ki; + float Kd = mCfg->groups[group].Kd; + unsigned long Ta = bTsp - mCfg->groups[group].lastRefresh; + mLog["Kp"] = Kp; + mLog["Ki"] = Ki; + mLog["Kd"] = Kd; + mLog["Ta"] = Ta; + // - P-Anteil + float yP = Kp * e; + float yP1 = Kp * e1; + float yP2 = Kp * e2; + float yP3 = Kp * e3; + mLog["yP"] = yP; + mLog["yP1"] = yP1; + mLog["yP2"] = yP2; + mLog["yP3"] = yP3; + // - I-Anteil + mCfg->groups[group].eSum += e; + mCfg->groups[group].eSum1 += e1; + mCfg->groups[group].eSum2 += e2; + mCfg->groups[group].eSum3 += e3; + mLog["esum"] = mCfg->groups[group].eSum; + mLog["esum1"] = mCfg->groups[group].eSum1; + mLog["esum2"] = mCfg->groups[group].eSum2; + mLog["esum3"] = mCfg->groups[group].eSum3; + float yI = Ki * Ta * mCfg->groups[group].eSum; + float yI1 = Ki * Ta * mCfg->groups[group].eSum1; + float yI2 = Ki * Ta * mCfg->groups[group].eSum2; + float yI3 = Ki * Ta * mCfg->groups[group].eSum3; + mLog["yI"] = yI; + mLog["yI1"] = yI1; + mLog["yI2"] = yI2; + mLog["yI3"] = yI3; + // - D-Anteil + mLog["ealt"] = mCfg->groups[group].eOld; + mLog["ealt1"] = mCfg->groups[group].eOld1; + mLog["ealt2"] = mCfg->groups[group].eOld2; + mLog["ealt3"] = mCfg->groups[group].eOld3; + float yD = Kd * (e - mCfg->groups[group].eOld) / Ta; + float yD1 = Kd * (e1 - mCfg->groups[group].eOld1) / Ta; + float yD2 = Kd * (e2 - mCfg->groups[group].eOld2) / Ta; + float yD3 = Kd * (e3 - mCfg->groups[group].eOld3) / Ta; + mLog["yD"] = yD; + mLog["yD1"] = yD1; + mLog["yD2"] = yD2; + mLog["yD3"] = yD3; + mCfg->groups[group].eOld = e; + mCfg->groups[group].eOld1 = e1; + mCfg->groups[group].eOld2 = e2; + mCfg->groups[group].eOld3 = e3; + // - PID-Anteil + float y = yP + yI + yD; + float y1 = yP1 + yI1 + yD1; + float y2 = yP2 + yI2 + yD2; + float y3 = yP3 + yI3 + yD3; + + // Regelbegrenzung + // TODO: Hier könnte man den maximalen Sprung begrenzen + + mLog["y"] = y; + mLog["y1"] = y1; + mLog["y2"] = y2; + mLog["y3"] = y3; + + mCfg->groups[group].grpPower = y; + mCfg->groups[group].grpPowerL1 = y1; + mCfg->groups[group].grpPowerL2 = y2; + mCfg->groups[group].grpPowerL3 = y3; + + // Next + mCfg->groups[group].state = zeroExportState::PROGNOSE; + mCfg->groups[group].stateNext = zeroExportState::PROGNOSE; + + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + 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["t"] = "groupPrognose"; + mLog["g"] = group; + + mCfg->groups[group].stateLast = zeroExportState::PROGNOSE; + + result = true; + + // Next + mCfg->groups[group].state = zeroExportState::AUFTEILEN; + mCfg->groups[group].stateNext = zeroExportState::AUFTEILEN; + + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + 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["t"] = "groupAufteilen"; + mLog["g"] = group; + + 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; + } - // 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. - float Kp = mCfg->groups[group].Kp; - float Ki = mCfg->groups[group].Ki; - float Kd = mCfg->groups[group].Kd; - unsigned long Ta = bTsp - mCfg->groups[group].lastRefresh; - mLog["Kp"] = Kp; - mLog["Ki"] = Ki; - mLog["Kd"] = Kd; - mLog["Ta"] = Ta; - // - P-Anteil - float yP = Kp * e; - float yP1 = Kp * e1; - float yP2 = Kp * e2; - float yP3 = Kp * e3; - mLog["yP"] = yP; - mLog["yP1"] = yP1; - mLog["yP2"] = yP2; - mLog["yP3"] = yP3; - // - I-Anteil - mCfg->groups[group].eSum += e; - mCfg->groups[group].eSum1 += e1; - mCfg->groups[group].eSum2 += e2; - mCfg->groups[group].eSum3 += e3; - mLog["esum"] = mCfg->groups[group].eSum; - mLog["esum1"] = mCfg->groups[group].eSum1; - mLog["esum2"] = mCfg->groups[group].eSum2; - mLog["esum3"] = mCfg->groups[group].eSum3; - float yI = Ki * Ta * mCfg->groups[group].eSum; - float yI1 = Ki * Ta * mCfg->groups[group].eSum1; - float yI2 = Ki * Ta * mCfg->groups[group].eSum2; - float yI3 = Ki * Ta * mCfg->groups[group].eSum3; - mLog["yI"] = yI; - mLog["yI1"] = yI1; - mLog["yI2"] = yI2; - mLog["yI3"] = yI3; - // - D-Anteil - mLog["ealt"] = mCfg->groups[group].eOld; - mLog["ealt1"] = mCfg->groups[group].eOld1; - mLog["ealt2"] = mCfg->groups[group].eOld2; - mLog["ealt3"] = mCfg->groups[group].eOld3; - float yD = Kd * (e - mCfg->groups[group].eOld) / Ta; - float yD1 = Kd * (e1 - mCfg->groups[group].eOld1) / Ta; - float yD2 = Kd * (e2 - mCfg->groups[group].eOld2) / Ta; - float yD3 = Kd * (e3 - mCfg->groups[group].eOld3) / Ta; - mLog["yD"] = yD; - mLog["yD1"] = yD1; - mLog["yD2"] = yD2; - mLog["yD3"] = yD3; - mCfg->groups[group].eOld = e; - mCfg->groups[group].eOld1 = e1; - mCfg->groups[group].eOld2 = e2; - mCfg->groups[group].eOld3 = e3; - // - 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; + // 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]); } - /** 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; + for (int8_t i = (7 - 1); 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; + } - mCfg->groups[group].stateLast = zeroExportState::PROGNOSE; + mLog["dP"] = *deltaP; + + // Leistung erhöhen + if (*deltaP > 0) { + zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[ivId_Pmin[i]]; + cfgGroupInv->limitNew = cfgGroupInv->limit + *deltaP; + // 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) { + zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[ivId_Pmax[i]]; + cfgGroupInv->limitNew = cfgGroupInv->limit + *deltaP; + // 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 + mCfg->groups[group].state = zeroExportState::SETLIMIT; + mCfg->groups[group].stateNext = zeroExportState::SETLIMIT; -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["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; + mCfg->groups[group].lastRun = eTsp; + return result; + } - 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(); - /** 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}; + mLog["t"] = "groupSetLimit"; + mLog["g"] = group; - for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { - zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[inv]; + mCfg->groups[group].stateLast = zeroExportState::SETLIMIT; - if (!cfgGroupInv->enabled) { - continue; - } + // Set limit + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mLog["i"] = inv; -// 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 - } + // Inverter not enabled -> ignore + if (!mCfg->groups[group].inverters[inv].enabled) { + continue; } - 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]); + // Inverter not selected -> ignore + if (mCfg->groups[group].inverters[inv].id <= 0) { + continue; } - - for (int8_t i = (7 - 1); 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 < (float)mCfg->groups[group].powerTolerance) { - continue; - } - mLog["+deltaP"] = *deltaP; - zeroExportGroupInverter_t *cfgGroupInv = &mCfg->groups[group].inverters[ivId_Pmin[i]]; - cfgGroupInv->limitNew = cfgGroupInv->limit + *deltaP; -// 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 = cfgGroupInv->limit + *deltaP; -// 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; - } + // 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; + } - // 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; -// } + // Abbruch weil Inverter produziert nicht + if (!mIv[group][inv]->isProducing()) { + continue; + } + // do + + // 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; + } - unsigned long eTsp = millis(); - mLog["E"] = eTsp; - mLog["D"] = eTsp - bTsp; - mCfg->groups[group].lastRun = eTsp; - return result; - } + // Reject limit if difference < 5 W + // if ((cfgGroupInv->limitNew > cfgGroupInv->limit + 5) && (cfgGroupInv->limitNew < cfgGroupInv->limit - 5)) { + // objLog["err"] = "Diff < 5W"; + // return false; + // } - /** 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; + } - // Nothing todo - if (mCfg->groups[group].inverters[inv].limit == mCfg->groups[group].inverters[inv].limitNew) { - continue; - } + doLog = true; - // Abbruch weil Inverter nicht verfügbar - if (!mIv[group][inv]->isAvailable()) { - continue; - } + mCfg->groups[group].inverters[inv].limit = mCfg->groups[group].inverters[inv].limitNew; + mLog["zeL"] = (uint16_t)mCfg->groups[group].inverters[inv].limit; - // Abbruch weil Inverter produziert nicht - if (!mIv[group][inv]->isProducing()) { - continue; - } + // wait for Ack + mCfg->groups[group].inverters[inv].waitLimitAck = 60; + mLog["wL"] = mCfg->groups[group].inverters[inv].waitLimitAck; - // do -// wait = true; + // send Command + DynamicJsonDocument doc(512); + JsonObject obj = doc.to(); + obj["val"] = (uint16_t)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); - // 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; - } - } -*/ + mLog["d"] = obj; + } - // 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; - } + // Next + mCfg->groups[group].state = zeroExportState::WAITREFRESH; + mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; - // 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; - } + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; - // 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].lastRun = eTsp; - // Nothing todo - if (mCfg->groups[group].inverters[inv].limit == mCfg->groups[group].inverters[inv].limitNew) { - continue; - } + return doLog; + } - doLog = true; + /** groupSetPower + * + * @param group + * @returns true/false + */ + bool groupSetPower(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); - mCfg->groups[group].inverters[inv].limit = mCfg->groups[group].inverters[inv].limitNew; - mLog["limit"] = (uint16_t)mCfg->groups[group].inverters[inv].limit; + mLog["t"] = "groupSetPower"; + mLog["g"] = group; - // wait for Ack - mCfg->groups[group].inverters[inv].waitLimitAck = 60; - mLog["wait"] = mCfg->groups[group].inverters[inv].waitLimitAck; + mCfg->groups[group].stateLast = zeroExportState::SETPOWER; - // send Command - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); - obj["val"] = (uint16_t)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); + // Set power + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mLog["i"] = inv; - mLog["data"] = obj; + // 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; } - // 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; - // do - doLog = true; -// wait = true; + // Abbruch weil Inverter nicht verfügbar + if (!mIv[group][inv]->isAvailable()) { + mLog["err"] = "is not Available"; + continue; + } - mLog["action"] = String("switch to: ") + String(mCfg->groups[group].battSwitch); + // Nothing todo + if (mCfg->groups[group].battSwitch == mIv[group][inv]->isProducing()) { + mLog["err"] = "battSwitch " + String(mCfg->groups[group].battSwitch) + " == isProducing()" + String(mIv[group][inv]->isProducing()); + continue; + } - // wait for Ack - mCfg->groups[group].inverters[inv].waitPowerAck = 120; - mLog["wait"] = mCfg->groups[group].inverters[inv].waitPowerAck; + mLog["act"] = String("switch to: ") + String(mCfg->groups[group].battSwitch); - // 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); + // wait for Ack + mCfg->groups[group].inverters[inv].waitPowerAck = 120; + mLog["wP"] = mCfg->groups[group].inverters[inv].waitPowerAck; - mLog["data"] = obj; - } + // send Command + DynamicJsonDocument doc(512); + JsonObject obj = doc.to(); + obj["val"] = mCfg->groups[group].battSwitch; + obj["id"] = mCfg->groups[group].inverters[inv].id; + obj["path"] = "ctrl"; + obj["cmd"] = "power"; + mApi->ctrlRequest(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; + mLog["d"] = obj; } - /** 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; + // Next + mCfg->groups[group].state = zeroExportState::WAITREFRESH; + mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; + mCfg->groups[group].lastRefresh = millis(); - // Abbruch weil Inverter nicht verfügbar -// if (!mIv[group][inv]->isAvailable()) { -// continue; -// } + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; - // do - doLog = true; -// wait = true; + mCfg->groups[group].lastRun = eTsp; - mLog["action"] = String("reboot"); + return doLog; + } - // wait for Ack - mCfg->groups[group].inverters[inv].waitRebootAck = 120; - mLog["wait"] = mCfg->groups[group].inverters[inv].waitRebootAck; + /** groupSetReboot + * + * @param group + * @returns true/false + */ + bool groupSetReboot(uint8_t group) { + bool doLog = false; + unsigned long bTsp = millis(); - // send Command - DynamicJsonDocument doc(512); - JsonObject obj = doc.to(); - obj["id"] = inv; - obj["path"] = "ctrl"; - obj["cmd"] = "restart"; - mApi->ctrlRequest(obj); + mLog["t"] = "groupSetReboot"; + mLog["g"] = group; - mLog["data"] = obj; - } + mCfg->groups[group].stateLast = zeroExportState::SETREBOOT; - // 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; - } + // Set reboot + for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { + mLog["i"] = inv; - /** sendLog - * Sendet das Log über Webserial und/oder MQTT - */ - void sendLog(void) { - if (mCfg->log_over_webserial) { - DBGPRINTLN(String("ze: ") + mDocLog.as()); + // Inverter not enabled -> ignore + if (!mCfg->groups[group].inverters[inv].enabled) { + continue; } - - if (mCfg->log_over_mqtt) { - if (mMqtt->isConnected()) { - mMqtt->publish("ze", mDocLog.as().c_str(), false); - } + // Inverter not selected -> ignore + if (mCfg->groups[group].inverters[inv].id <= 0) { + continue; } - 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; - } + // Abbruch weil Inverter nicht verfügbar + // if (!mIv[group][inv]->isAvailable()) { + // continue; + // } - if(String(mCfg->monitor_url).endsWith("data.json?node_id=1")){ - httpClient.setAuthorization("admin", mCfg->tibber_pw); - } + // do + doLog = true; - 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())); - } + mLog["act"] = String("reboot"); - // 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; - } + // wait for Ack + mCfg->groups[group].inverters[inv].waitRebootAck = 120; + mLog["wR"] = mCfg->groups[group].inverters[inv].waitRebootAck; - void tibber_parse() - { + // send Command + DynamicJsonDocument doc(512); + JsonObject obj = doc.to(); + obj["id"] = mCfg->groups[group].inverters[inv].id; + obj["path"] = "ctrl"; + obj["cmd"] = "restart"; + mApi->ctrlRequest(obj); + mLog["d"] = obj; } -*/ -/* -// TODO: Vorlage für Berechnung - Inverter<> *iv = mSys.getInverterByPos(mConfig->plugin.zeroExport.Iv); + // Next + mCfg->groups[group].state = zeroExportState::WAITREFRESH; + mCfg->groups[group].stateNext = zeroExportState::WAITREFRESH; + mCfg->groups[group].lastRefresh = millis(); - DynamicJsonDocument doc(512); - JsonObject object = doc.to(); + unsigned long eTsp = millis(); + mLog["B"] = bTsp; + mLog["E"] = eTsp; + mLog["D"] = eTsp - bTsp; - double nValue = round(mZeroExport.getPowertoSetnewValue()); - double twoPerVal = nValue <= (iv->getMaxPower() / 100 * 2 ); + mCfg->groups[group].lastRun = eTsp; - if(mConfig->plugin.zeroExport.two_percent && (nValue <= twoPerVal)) - nValue = twoPerVal; + return doLog; + } - if(mConfig->plugin.zeroExport.max_power <= nValue) - nValue = mConfig->plugin.zeroExport.max_power; + /** sendLog + * Sendet das Log über Webserial und/oder MQTT + */ + void sendLog(void) { + if (mCfg->log_over_webserial) { + DBGPRINTLN(String("ze: ") + mDocLog.as()); + } - if(iv->actPowerLimit == nValue) { - mConfig->plugin.zeroExport.lastTime = millis(); // set last timestamp - return; // if PowerLimit same as befor, then skip + if (mCfg->log_over_mqtt) { + if (mMqtt->isConnected()) { + mMqtt->publish("ze", mDocLog.as().c_str(), false); + } } - object["val"] = nValue; - object["id"] = mConfig->plugin.zeroExport.Iv; - object["path"] = "ctrl"; - object["cmd"] = "limit_nonpersistent_absolute"; - - String data; - serializeJsonPretty(object, data); - DPRINTLN(DBG_INFO, data); - mApi.ctrlRequest(object); - - mConfig->plugin.zeroExport.lastTime = millis(); // set last timestamp -*/ - - // private member variables - bool mIsInitialized = false; - zeroExport_t *mCfg; - settings_t *mConfig; - HMSYSTEM *mSys; - RestApiType *mApi; - StaticJsonDocument<5000> mDocLog; - JsonObject mLog = mDocLog.to(); - PubMqttType *mMqtt; - powermeter mPowermeter; - - Inverter<> *mIv[ZEROEXPORT_MAX_GROUPS][ZEROEXPORT_GROUP_MAX_INVERTERS]; + mDocLog.clear(); + } + + // private member variables + bool mIsInitialized = false; + zeroExport_t *mCfg; + settings_t *mConfig; + HMSYSTEM *mSys; + RestApiType *mApi; + StaticJsonDocument<5000> mDocLog; + JsonObject mLog = mDocLog.to(); + PubMqttType *mMqtt; + powermeter mPowermeter; + + Inverter<> *mIv[ZEROEXPORT_MAX_GROUPS][ZEROEXPORT_GROUP_MAX_INVERTERS]; }; #endif /*__ZEROEXPORT__*/ diff --git a/src/web/html/setup.html b/src/web/html/setup.html index e11510ba..bae845ea 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -1363,7 +1363,8 @@ divRow("{#ZE_GROUP_TAB_POWERMETER_TYPE}", ml("select", {name: "pm_type", class: "text", id: "pm_type"}, null), ), - divRow("{#ZE_GROUP_TAB_POWERMETER_URL}", [ +// TODO: URL -> IP + divRow("{#ZE_GROUP_TAB_POWERMETER_IP}", [ ml("input", {name: "pm_url", class: "text", type: "text", value: obj.pm_url, maxlength: "100"}, null), ]), divRow("{#ZE_GROUP_TAB_POWERMETER_JSONPATH}", [ diff --git a/src/web/lang.json b/src/web/lang.json index dfb4a59e..15c1aec9 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -854,9 +854,9 @@ "de": "Typ:" }, { - "token": "ZE_GROUP_TAB_POWERMETER_URL", - "en": "Url:", - "de": "Url:" + "token": "ZE_GROUP_TAB_POWERMETER_IP", + "en": "IP:", + "de": "IP:" }, { "token": "ZE_GROUP_TAB_POWERMETER_JSONPATH",