You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1420 lines
51 KiB

#if defined(PLUGIN_ZEROEXPORT)
#ifndef __ZEROEXPORT__
#define __ZEROEXPORT__
#include <HTTPClient.h>
#include <string.h>
#include <base64.h>
#include "AsyncJson.h"
#include "powermeter.h"
//#include "SML.h"
template <class HMSYSTEM>
// TODO: Anbindung an MQTT für Logausgabe.
// TODO: Powermeter erweitern
// 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;
}
}
}
/** ~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);
// TODO: Sicherheitsreturn weil noch Sicherheitsfunktionen fehlen.
mIsInitialized = false;
}
/** 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;
}
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;
}
}
}
/** 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--;
}
}
}
}
/** 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();
}
}
}
}
/** 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();
}
}
}
}
/** 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();
}
}
}
}
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;
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 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;
}
}
// 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;
}
// 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;
mCfg->groups[group].lastRefresh = 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 (bTsp <= (mCfg->groups[group].lastRun + 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;
mCfg->groups[group].lastRefresh = 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 (bTsp <= (mCfg->groups[group].lastRefresh + (mCfg->groups[group].refresh * 1000UL))) {
return result;
}
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;
}
/** 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::GETINVERTERACKS;
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 > 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;
}
}
// if (wait) {
// if (mCfg->groups[group].lastRun > (millis() - 30000UL)) {
// wait = false;
// }
// }
mLog["wait"] = wait;
// Next
if (!wait) {
mCfg->groups[group].state = zeroExportState::GETINVERTERDATA;
mCfg->groups[group].stateNext = zeroExportState::GETINVERTERDATA;
}
unsigned long eTsp = millis();
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["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;
}
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
}
// 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;
}
/** 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;
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;
}
if (!mIv[group][inv]->isAvailable()) {
if (U > 0) {
continue;
}
U = 0;
id = cfgGroupInv->id;
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["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;
}
// 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;
}
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 groupGetPowermeter(uint8_t group) {
bool doLog = false;
unsigned long bTsp = millis();
mLog["group"] = group;
mLog["type"] = "groupGetPowermeter";
mLog["B"] = bTsp;
mCfg->groups[group].stateLast = zeroExportState::GETPOWERMETER;
doLog = true;
bool result = false;
result = mPowermeter.getData(mLog, group);
// 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["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["group"] = group;
mLog["type"] = "groupController";
mLog["B"] = bTsp;
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;
// 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;
}
/** 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 < (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;
}
}
// 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
// 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;
// }
// Nothing todo
if (mCfg->groups[group].inverters[inv].limit == mCfg->groups[group].inverters[inv].limitNew) {
continue;
}
doLog = true;
mCfg->groups[group].inverters[inv].limit = mCfg->groups[group].inverters[inv].limitNew;
mLog["limit"] = (uint16_t)mCfg->groups[group].inverters[inv].limit;
// wait for Ack
mCfg->groups[group].inverters[inv].waitLimitAck = 60;
mLog["wait"] = mCfg->groups[group].inverters[inv].waitLimitAck;
// send Command
DynamicJsonDocument doc(512);
JsonObject obj = doc.to<JsonObject>();
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);
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 = 120;
mLog["wait"] = mCfg->groups[group].inverters[inv].waitPowerAck;
// send Command
DynamicJsonDocument doc(512);
JsonObject obj = doc.to<JsonObject>();
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 = 120;
mLog["wait"] = mCfg->groups[group].inverters[inv].waitRebootAck;
// send Command
DynamicJsonDocument doc(512);
JsonObject obj = doc.to<JsonObject>();
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<String>());
}
if (mCfg->log_over_mqtt) {
if (mMqtt->isConnected()) {
mMqtt->publish("ze", mDocLog.as<std::string>().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()
{
}
*/
/*
// TODO: Vorlage für Berechnung
Inverter<> *iv = mSys.getInverterByPos(mConfig->plugin.zeroExport.Iv);
DynamicJsonDocument doc(512);
JsonObject object = doc.to<JsonObject>();
double nValue = round(mZeroExport.getPowertoSetnewValue());
double twoPerVal = nValue <= (iv->getMaxPower() / 100 * 2 );
if(mConfig->plugin.zeroExport.two_percent && (nValue <= twoPerVal))
nValue = twoPerVal;
if(mConfig->plugin.zeroExport.max_power <= nValue)
nValue = mConfig->plugin.zeroExport.max_power;
if(iv->actPowerLimit == nValue) {
mConfig->plugin.zeroExport.lastTime = millis(); // set last timestamp
return; // if PowerLimit same as befor, then skip
}
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<JsonObject>();
PubMqttType *mMqtt;
powermeter mPowermeter;
Inverter<> *mIv[ZEROEXPORT_MAX_GROUPS][ZEROEXPORT_GROUP_MAX_INVERTERS];
};
#endif /*__ZEROEXPORT__*/
#endif /* #if defined(PLUGIN_ZEROEXPORT) */