mirror of https://github.com/lumapu/ahoy.git
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.
1754 lines
61 KiB
1754 lines
61 KiB
//-----------------------------------------------------------------------------
|
|
// 2024 Ahoy, https://ahoydtu.de
|
|
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#if defined(PLUGIN_ZEROEXPORT)
|
|
|
|
#ifndef __ZEROEXPORT__
|
|
#define __ZEROEXPORT__
|
|
|
|
#include <HTTPClient.h>
|
|
#include <base64.h>
|
|
#include <string.h>
|
|
|
|
#include "AsyncJson.h"
|
|
#include "powermeter.h"
|
|
|
|
template <class HMSYSTEM>
|
|
|
|
// TODO: groupAufteilen und groupSetLimit ... 4W Regel? für alle Parameter
|
|
|
|
class ZeroExport {
|
|
public:
|
|
/** ZeroExport
|
|
* constructor
|
|
*/
|
|
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
|
|
* destructor
|
|
*/
|
|
~ZeroExport() {}
|
|
|
|
/** setup
|
|
* Initialisierung
|
|
* @param *cfg
|
|
* @param *sys
|
|
* @param *config
|
|
* @param *api
|
|
* @param *mqtt
|
|
* @returns void
|
|
*/
|
|
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, &mLog);
|
|
}
|
|
|
|
/** loop
|
|
* Arbeitsschleife
|
|
* @param void
|
|
* @returns void
|
|
* @todo emergency
|
|
*/
|
|
void loop(void) {
|
|
mqttInitTopic();
|
|
|
|
if ((!mIsInitialized) || (!mCfg->enabled)) return;
|
|
|
|
bool DoLog = false;
|
|
unsigned long Tsp = millis();
|
|
|
|
mPowermeter.loop(&Tsp, &DoLog);
|
|
if (DoLog) sendLog();
|
|
clearLog();
|
|
|
|
DoLog = false;
|
|
|
|
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
|
|
// enabled
|
|
if (!cfgGroup->enabled) continue;
|
|
|
|
// wait
|
|
if (Tsp <= (cfgGroup->lastRun + cfgGroup->wait)) continue;
|
|
cfgGroup->wait = 0;
|
|
|
|
// log
|
|
mLog["g"] = group;
|
|
mLog["s"] = (uint8_t)cfgGroup->state;
|
|
|
|
// Statemachine
|
|
switch (cfgGroup->state) {
|
|
case zeroExportState::INIT:
|
|
if (groupInit(group, &Tsp, &DoLog)) {
|
|
cfgGroup->state = zeroExportState::WAITREFRESH;
|
|
cfgGroup->wait = 1000;
|
|
} else {
|
|
cfgGroup->state = zeroExportState::INIT;
|
|
cfgGroup->wait = 60000;
|
|
#if defined(ZEROEXPORT_DEV_POWERMETER)
|
|
cfgGroup->state = zeroExportState::WAITREFRESH;
|
|
cfgGroup->wait = 1000;
|
|
#endif
|
|
}
|
|
break;
|
|
case zeroExportState::WAITREFRESH:
|
|
if (groupWaitRefresh(group, &Tsp, &DoLog)) {
|
|
cfgGroup->state = zeroExportState::GETINVERTERDATA;
|
|
if (mCfg->sleep) {
|
|
cfgGroup->state = zeroExportState::SETPOWER;
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
cfgGroup->inverters[inv].limitNew = -99;
|
|
}
|
|
}
|
|
#if defined(ZEROEXPORT_DEV_POWERMETER)
|
|
cfgGroup->state = zeroExportState::GETPOWERMETER;
|
|
#endif
|
|
}
|
|
break;
|
|
case zeroExportState::GETINVERTERDATA:
|
|
if (groupGetInverterData(group, &Tsp, &DoLog)) {
|
|
cfgGroup->state = zeroExportState::BATTERYPROTECTION;
|
|
} else {
|
|
cfgGroup->wait = 500;
|
|
}
|
|
break;
|
|
case zeroExportState::BATTERYPROTECTION:
|
|
if (groupBatteryprotection(group, &Tsp, &DoLog)) {
|
|
cfgGroup->state = zeroExportState::GETPOWERMETER;
|
|
} else {
|
|
cfgGroup->wait = 1000;
|
|
}
|
|
break;
|
|
case zeroExportState::GETPOWERMETER:
|
|
if (groupGetPowermeter(group, &Tsp, &DoLog)) {
|
|
cfgGroup->state = zeroExportState::CONTROLLER;
|
|
#if defined(ZEROEXPORT_DEV_POWERMETER)
|
|
cfgGroup->lastRefresh = millis();
|
|
cfgGroup->state = zeroExportState::WAITREFRESH;
|
|
#endif
|
|
} else {
|
|
cfgGroup->wait = 3000;
|
|
}
|
|
break;
|
|
case zeroExportState::CONTROLLER:
|
|
if (groupController(group, &Tsp, &DoLog)) {
|
|
cfgGroup->lastRefresh = Tsp;
|
|
cfgGroup->state = zeroExportState::PROGNOSE;
|
|
} else {
|
|
cfgGroup->state = zeroExportState::WAITREFRESH;
|
|
}
|
|
break;
|
|
case zeroExportState::PROGNOSE:
|
|
if (groupPrognose(group, &Tsp, &DoLog)) {
|
|
cfgGroup->state = zeroExportState::AUFTEILEN;
|
|
} else {
|
|
cfgGroup->wait = 500;
|
|
}
|
|
break;
|
|
case zeroExportState::AUFTEILEN:
|
|
if (groupAufteilen(group, &Tsp, &DoLog)) {
|
|
cfgGroup->state = zeroExportState::SETREBOOT;
|
|
} else {
|
|
cfgGroup->wait = 500;
|
|
}
|
|
break;
|
|
case zeroExportState::SETREBOOT:
|
|
if (groupSetReboot(group, &Tsp, &DoLog)) {
|
|
cfgGroup->state = zeroExportState::SETPOWER;
|
|
} else {
|
|
cfgGroup->wait = 1000;
|
|
}
|
|
break;
|
|
case zeroExportState::SETPOWER:
|
|
if (groupSetPower(group, &Tsp, &DoLog)) {
|
|
cfgGroup->lastRefresh = Tsp;
|
|
cfgGroup->state = zeroExportState::SETLIMIT;
|
|
} else {
|
|
cfgGroup->wait = 1000;
|
|
}
|
|
break;
|
|
case zeroExportState::SETLIMIT:
|
|
if (groupSetLimit(group, &Tsp, &DoLog)) {
|
|
cfgGroup->state = zeroExportState::WAITREFRESH;
|
|
} else {
|
|
cfgGroup->wait = 1000;
|
|
if (mCfg->sleep) {
|
|
cfgGroup->wait = 60000;
|
|
}
|
|
}
|
|
break;
|
|
case zeroExportState::EMERGENCY:
|
|
if (groupEmergency(group, &Tsp, &DoLog)) {
|
|
cfgGroup->lastRefresh = Tsp;
|
|
cfgGroup->state = zeroExportState::INIT;
|
|
//} else {
|
|
// TODO: fehlt
|
|
}
|
|
break;
|
|
case zeroExportState::FINISH:
|
|
case zeroExportState::ERROR:
|
|
default:
|
|
cfgGroup->lastRun = Tsp;
|
|
cfgGroup->lastRefresh = Tsp;
|
|
cfgGroup->wait = 1000;
|
|
cfgGroup->state = zeroExportState::INIT;
|
|
break;
|
|
}
|
|
|
|
if (mCfg->debug) {
|
|
unsigned long eTsp = millis();
|
|
mLog["B"] = Tsp;
|
|
mLog["E"] = eTsp;
|
|
mLog["D"] = eTsp - Tsp;
|
|
}
|
|
|
|
if (DoLog) sendLog();
|
|
clearLog();
|
|
DoLog = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/** tickSecond
|
|
* Time pulse every second
|
|
* @param void
|
|
* @returns void
|
|
* @todo Eventuell ein waitAck für alle 3 Set-Befehle
|
|
* @todo Eventuell ein waitAck für alle Inverter einer Gruppe
|
|
*/
|
|
void tickSecond() {
|
|
if ((!mIsInitialized) || (!mCfg->enabled)) return;
|
|
|
|
// Reduce WaitAck every second
|
|
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
// WaitAckSetLimit
|
|
if (mCfg->groups[group].inverters[inv].waitAckSetLimit > 0) {
|
|
mCfg->groups[group].inverters[inv].waitAckSetLimit--;
|
|
}
|
|
// WaitAckSetPower
|
|
if (mCfg->groups[group].inverters[inv].waitAckSetPower > 0) {
|
|
mCfg->groups[group].inverters[inv].waitAckSetPower--;
|
|
}
|
|
// WaitAckSetReboot
|
|
if (mCfg->groups[group].inverters[inv].waitAckSetReboot > 0) {
|
|
mCfg->groups[group].inverters[inv].waitAckSetReboot--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** tickerMidnight
|
|
* Time pulse Midnicht
|
|
* @param void
|
|
* @returns void
|
|
* @todo Reboot der Inverter um Mitternacht in Ahoy selbst verschieben mit separater Config-Checkbox
|
|
* @todo Ahoy Config-Checkbox Reboot Inverter at Midnight beim groupInit() automatisch setzen.
|
|
*/
|
|
void tickMidnight(void) {
|
|
if (!mIsInitialized) return;
|
|
|
|
// Select all Inverter to reboot
|
|
// shutdown for clean start environment
|
|
//@Todo: move to ahoy!
|
|
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
mCfg->groups[group].inverters[inv].doReboot = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** eventAckSetLimit
|
|
* Reset waiting time limit
|
|
* @param iv
|
|
* @returns void
|
|
*/
|
|
void eventAckSetLimit(Inverter<> *iv) {
|
|
if ((!mIsInitialized) || (!mCfg->enabled)) return;
|
|
|
|
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
|
|
bool DoLog = false;
|
|
unsigned long bTsp = millis();
|
|
|
|
mLog["g"] = group;
|
|
mLog["s"] = (uint8_t)zeroExportState::SETLIMIT;
|
|
if (mCfg->debug) mLog["t"] = "eventAckSetLimit";
|
|
|
|
JsonArray logArr = mLog.createNestedArray("ix");
|
|
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
if (iv->id == (uint8_t)mCfg->groups[group].inverters[inv].id) {
|
|
JsonObject logObj = logArr.createNestedObject();
|
|
|
|
logObj["i"] = inv;
|
|
if (mCfg->debug) logObj["id"] = iv->id;
|
|
mCfg->groups[group].inverters[inv].waitAckSetLimit = 0;
|
|
logObj["wL"] = mCfg->groups[group].inverters[inv].waitAckSetLimit;
|
|
|
|
DoLog = true;
|
|
}
|
|
}
|
|
|
|
if (mCfg->debug) {
|
|
unsigned long eTsp = millis();
|
|
mLog["B"] = bTsp;
|
|
mLog["E"] = eTsp;
|
|
mLog["D"] = eTsp - bTsp;
|
|
}
|
|
|
|
if (DoLog) sendLog();
|
|
clearLog();
|
|
DoLog = false;
|
|
}
|
|
}
|
|
|
|
/** eventAckSetPower
|
|
* Reset waiting time power
|
|
* @param iv
|
|
* @returns void
|
|
*/
|
|
void eventAckSetPower(Inverter<> *iv) {
|
|
if ((!mIsInitialized) || (!mCfg->enabled)) return;
|
|
|
|
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
|
|
bool DoLog = false;
|
|
unsigned long bTsp = millis();
|
|
|
|
mLog["g"] = group;
|
|
mLog["s"] = (uint8_t)zeroExportState::SETPOWER;
|
|
if (mCfg->debug) mLog["t"] = "eventAckSetPower";
|
|
|
|
JsonArray logArr = mLog.createNestedArray("ix");
|
|
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
if (iv->id == mCfg->groups[group].inverters[inv].id) {
|
|
JsonObject logObj = logArr.createNestedObject();
|
|
|
|
logObj["i"] = inv;
|
|
logObj["id"] = iv->id;
|
|
mCfg->groups[group].inverters[inv].waitAckSetPower = 30;
|
|
logObj["wP"] = mCfg->groups[group].inverters[inv].waitAckSetPower;
|
|
|
|
DoLog = true;
|
|
}
|
|
}
|
|
|
|
if (mCfg->debug) {
|
|
unsigned long eTsp = millis();
|
|
mLog["B"] = bTsp;
|
|
mLog["E"] = eTsp;
|
|
mLog["D"] = eTsp - bTsp;
|
|
}
|
|
|
|
if (DoLog) sendLog();
|
|
clearLog();
|
|
DoLog = false;
|
|
}
|
|
}
|
|
|
|
/** eventAckSetReboot
|
|
* Reset waiting time reboot
|
|
* @param iv
|
|
* @returns void
|
|
*/
|
|
void eventAckSetReboot(Inverter<> *iv) {
|
|
if ((!mIsInitialized) || (!mCfg->enabled)) return;
|
|
|
|
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
|
|
bool DoLog = false;
|
|
unsigned long bTsp = millis();
|
|
|
|
mLog["g"] = group;
|
|
mLog["s"] = (uint8_t)zeroExportState::SETREBOOT;
|
|
if (mCfg->debug) mLog["t"] = "eventAckSetReboot";
|
|
|
|
JsonArray logArr = mLog.createNestedArray("ix");
|
|
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
if (iv->id == mCfg->groups[group].inverters[inv].id) {
|
|
JsonObject logObj = logArr.createNestedObject();
|
|
|
|
logObj["i"] = inv;
|
|
logObj["id"] = iv->id;
|
|
mCfg->groups[group].inverters[inv].waitAckSetReboot = 30;
|
|
logObj["wR"] = mCfg->groups[group].inverters[inv].waitAckSetReboot;
|
|
|
|
DoLog = true;
|
|
}
|
|
}
|
|
|
|
if (mCfg->debug) {
|
|
unsigned long eTsp = millis();
|
|
mLog["B"] = bTsp;
|
|
mLog["E"] = eTsp;
|
|
mLog["D"] = eTsp - bTsp;
|
|
}
|
|
|
|
if (DoLog) sendLog();
|
|
clearLog();
|
|
DoLog = false;
|
|
}
|
|
}
|
|
|
|
/** eventNewDataAvailable
|
|
*
|
|
* @param iv
|
|
* @returns void
|
|
*/
|
|
void eventNewDataAvailable(Inverter<> *iv) {
|
|
if ((!mIsInitialized) || (!mCfg->enabled)) return;
|
|
|
|
if (!iv->isAvailable()) return;
|
|
|
|
if (!iv->isProducing()) return;
|
|
|
|
if (iv->actPowerLimit == 65535) return;
|
|
|
|
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
// Wrong Inverter -> ignore
|
|
if (iv->id != mCfg->groups[group].inverters[inv].id) continue;
|
|
|
|
// Keine Datenübernahme wenn nicht enabled
|
|
if (!mCfg->groups[group].inverters[inv].enabled) continue;
|
|
|
|
// Keine Datenübernahme wenn setReboot, setPower, setLimit läuft
|
|
if (mCfg->groups[group].inverters[inv].waitAckSetReboot > 0) continue;
|
|
if (mCfg->groups[group].inverters[inv].waitAckSetPower > 0) continue;
|
|
if (mCfg->groups[group].inverters[inv].waitAckSetLimit > 0) continue;
|
|
|
|
// Calculate
|
|
int32_t ivLp = iv->actPowerLimit;
|
|
int32_t ivPm = iv->getMaxPower();
|
|
|
|
int32_t ivL = (ivPm * ivLp) / 100;
|
|
int32_t zeL = mCfg->groups[group].inverters[inv].limit;
|
|
|
|
// Keine Datenübernahme wenn zeL gleich ivL
|
|
if (zeL == ivL) continue;
|
|
|
|
unsigned long bTsp = millis();
|
|
|
|
mLog["t"] = "newDataAvailable";
|
|
mLog["g"] = group;
|
|
mLog["i"] = inv;
|
|
mLog["id"] = iv->id;
|
|
mLog["ivL%"] = ivLp;
|
|
mLog["ivPm"] = ivPm;
|
|
mLog["ivL"] = ivL;
|
|
mLog["zeL"] = zeL;
|
|
mCfg->groups[group].inverters[inv].limit = ivL;
|
|
|
|
if (mCfg->debug) {
|
|
unsigned long eTsp = millis();
|
|
mLog["B"] = bTsp;
|
|
mLog["E"] = eTsp;
|
|
mLog["D"] = eTsp - bTsp;
|
|
}
|
|
sendLog();
|
|
clearLog();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** onMqttMessage
|
|
* Subscribe section
|
|
* @param
|
|
* @returns void
|
|
*/
|
|
void onMqttMessage(JsonObject obj) {
|
|
if (!mIsInitialized) return;
|
|
|
|
String topic = String(obj["topic"]);
|
|
if (!topic.indexOf("/zero/set/")) return;
|
|
|
|
mLog["t"] = "onMqttMessage";
|
|
|
|
if (obj["path"] == "zero" && obj["cmd"] == "set") {
|
|
int8_t topicGroup = getGroupFromTopic(topic.c_str());
|
|
mLog["topicGroup"] = topicGroup;
|
|
int8_t topicInverter = getInverterFromTopic(topic.c_str());
|
|
mLog["topicInverter"] = topicInverter;
|
|
|
|
if ((topicGroup == -1) && (topicInverter == -1)) {
|
|
// "topic":"???/zero/set/enabled"
|
|
if (topic.indexOf("/zero/set/enabled") != -1) {
|
|
mCfg->enabled = (bool)obj["val"];
|
|
mLog["mCfg->enabled"] = mCfg->enabled;
|
|
// Initialize groups
|
|
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
|
|
mCfg->groups[group].state = zeroExportState::INIT;
|
|
mCfg->groups[group].wait = 0;
|
|
}
|
|
}
|
|
// "topic":"???/zero/set/sleep"
|
|
if (topic.indexOf("/zero/set/sleep") != -1) {
|
|
mCfg->sleep = (bool)obj["val"];
|
|
mLog["mCfg->sleep"] = mCfg->sleep;
|
|
}
|
|
} else if ((topicGroup != -1) && (topicInverter == -1)) {
|
|
// "topic":"???/zero/set/groups/0/???"
|
|
mLog["g"] = topicGroup;
|
|
// "topic":"???/zero/set/groups/0/enabled"
|
|
if (topic.indexOf("enabled") != -1) {
|
|
mCfg->groups[topicGroup].enabled = (bool)obj["val"];
|
|
// Initialize group
|
|
mCfg->groups[topicGroup].state = zeroExportState::INIT;
|
|
mCfg->groups[topicGroup].wait = 0;
|
|
}
|
|
// "topic":"???/zero/set/groups/0/sleep"
|
|
if (topic.indexOf("sleep") != -1) {
|
|
mCfg->groups[topicGroup].sleep = (bool)obj["val"];
|
|
}
|
|
// "topic":"???/zero/set/groups/0/battery/switch"
|
|
if (topic.indexOf("battery/switch") != -1) {
|
|
mCfg->groups[topicGroup].battSwitch = (bool)obj["val"];
|
|
}
|
|
// "topic":"???/zero/set/groups/0/advanced/setPoint"
|
|
if (topic.indexOf("advanced/setPoint") != -1) {
|
|
mCfg->groups[topicGroup].setPoint = (int16_t)obj["val"];
|
|
}
|
|
// "topic":"???/zero/set/groups/0/advanced/powerTolerance"
|
|
if (topic.indexOf("advanced/powerTolerance") != -1) {
|
|
mCfg->groups[topicGroup].powerTolerance = (uint8_t)obj["val"];
|
|
}
|
|
// "topic":"???/zero/set/groups/0/advanced/powerMax"
|
|
if (topic.indexOf("advanced/powerMax") != -1) {
|
|
mCfg->groups[topicGroup].powerMax = (uint16_t)obj["val"];
|
|
}
|
|
} else if ((topicGroup != -1) && (topicInverter != -1)) {
|
|
// "topic":"???/zero/set/groups/0/inverter/0/enabled"
|
|
if (topic.indexOf("enabled") != -1) {
|
|
mCfg->groups[topicGroup].inverters[topicInverter].enabled = (bool)obj["val"];
|
|
}
|
|
// "topic":"???/zero/set/groups/0/inverter/0/powerMin"
|
|
if (topic.indexOf("powerMin") != -1) {
|
|
mCfg->groups[topicGroup].inverters[topicInverter].powerMin = (uint16_t)obj["val"];
|
|
}
|
|
// "topic":"???/zero/set/groups/0/inverter/0/powerMax"
|
|
if (topic.indexOf("powerMax") != -1) {
|
|
mCfg->groups[topicGroup].inverters[topicInverter].powerMax = (uint16_t)obj["val"];
|
|
}
|
|
}
|
|
}
|
|
|
|
// topic for powermeter?
|
|
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++)
|
|
{
|
|
if(mCfg->groups[group].pm_type == zeroExportPowermeterType_t::Mqtt)
|
|
{
|
|
mLog["mqttDevice"] = "topicInverter";
|
|
if(!topic.equals(mCfg->groups[group].pm_jsonPath)) return;
|
|
mCfg->groups[group].pm_P = (int32_t)obj["val"];
|
|
}
|
|
}
|
|
|
|
if (mCfg->debug) mLog["Msg"] = obj;
|
|
sendLog();
|
|
clearLog();
|
|
return;
|
|
}
|
|
|
|
private:
|
|
/** NotEnabledOrNotSelected
|
|
* Inverter not enabled -> ignore || Inverter not selected -> ignore
|
|
* @param group
|
|
* @param inv
|
|
* @returns true/false
|
|
*/
|
|
bool NotEnabledOrNotSelected(uint8_t group, uint8_t inv) {
|
|
return ((!mCfg->groups[group].inverters[inv].enabled) || (mCfg->groups[group].inverters[inv].id < 0));
|
|
}
|
|
|
|
/** getGroupFromTopic
|
|
*
|
|
* @param
|
|
* @returns
|
|
*/
|
|
int8_t getGroupFromTopic(const char *topic) {
|
|
const char *pGroupSection = strstr(topic, "groups/");
|
|
if (pGroupSection == NULL) return -1;
|
|
pGroupSection += 7;
|
|
char strGroup[3];
|
|
uint8_t digitsCopied = 0;
|
|
while (*pGroupSection != '/' && digitsCopied < 2) strGroup[digitsCopied++] = *pGroupSection++;
|
|
strGroup[digitsCopied] = '\0';
|
|
int8_t group = atoi(strGroup);
|
|
return group;
|
|
}
|
|
|
|
/** getInverterFromTopic
|
|
*
|
|
* @param
|
|
* @returns
|
|
*/
|
|
int8_t getInverterFromTopic(const char *topic) {
|
|
const char *pInverterSection = strstr(topic, "inverters/");
|
|
if (pInverterSection == NULL) return -1;
|
|
pInverterSection += 10;
|
|
char strInverter[3];
|
|
uint8_t digitsCopied = 0;
|
|
while (*pInverterSection != '/' && digitsCopied < 2) strInverter[digitsCopied++] = *pInverterSection++;
|
|
strInverter[digitsCopied] = '\0';
|
|
int8_t inverter = atoi(strInverter);
|
|
return inverter;
|
|
}
|
|
|
|
/** groupInit
|
|
* Initialize the group and search the InverterPointer
|
|
* @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, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
bool result = false;
|
|
|
|
mCfg->groups[group].lastRun = *tsp;
|
|
|
|
if (mCfg->debug) {
|
|
mLog["t"] = "groupInit";
|
|
}
|
|
|
|
*doLog = true;
|
|
|
|
// Clear Inverter-Pointer
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
mIv[group][inv] = nullptr;
|
|
}
|
|
|
|
// Search/Get Inverter-Pointer
|
|
JsonArray logArr = mLog.createNestedArray("ix");
|
|
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
JsonObject logObj = logArr.createNestedObject();
|
|
logObj["i"] = inv;
|
|
|
|
// Inverter-Config not enabled or not selected -> ignore
|
|
if (NotEnabledOrNotSelected(group, inv)) continue;
|
|
|
|
// Load Inverter-Config
|
|
Inverter<> *iv;
|
|
for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
|
|
iv = mSys->getInverterByPos(i);
|
|
|
|
// Inverter not configured/matching -> ignore
|
|
if (iv == NULL) continue;
|
|
if (iv->id != (uint8_t)mCfg->groups[group].inverters[inv].id) continue;
|
|
|
|
// Save Inverter-Pointer
|
|
logObj["pos"] = i;
|
|
logObj["id"] = iv->id;
|
|
mIv[group][inv] = mSys->getInverterByPos(i);
|
|
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
// Init waitAcks
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
mCfg->groups[group].inverters[inv].waitAckSetLimit = 0;
|
|
mCfg->groups[group].inverters[inv].waitAckSetPower = 0;
|
|
mCfg->groups[group].inverters[inv].waitAckSetReboot = 0;
|
|
}
|
|
|
|
// Init lastRefresh for waitRefresh
|
|
mCfg->groups[group].lastRefresh = *tsp;
|
|
|
|
return result;
|
|
}
|
|
|
|
/** groupWaitRefresh
|
|
* Pauses the group until the wait time since the lastRefresh has expired.
|
|
* @param group
|
|
* @returns true/false
|
|
*/
|
|
bool groupWaitRefresh(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
|
|
mCfg->groups[group].lastRun = *tsp;
|
|
|
|
// Wait Refreshtime
|
|
if (*tsp <= (mCfg->groups[group].lastRefresh + (mCfg->groups[group].refresh * 1000UL))) return false;
|
|
|
|
if (mCfg->debug) {
|
|
mLog["t"] = "groupWaitRefresh";
|
|
*doLog = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** groupGetInverterData
|
|
*
|
|
* @param group
|
|
* @returns true/false
|
|
*/
|
|
bool groupGetInverterData(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
bool result = true;
|
|
|
|
cfgGroup->lastRun = *tsp;
|
|
|
|
if (mCfg->debug) {
|
|
mLog["t"] = "groupGetInverterData";
|
|
*doLog = true;
|
|
}
|
|
|
|
// Get Data
|
|
JsonArray logArr = mLog.createNestedArray("ix");
|
|
int32_t power = 0;
|
|
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[inv];
|
|
|
|
JsonObject logObj = logArr.createNestedObject();
|
|
logObj["i"] = inv;
|
|
|
|
// Inverter not enabled or not selected -> ignore
|
|
if (NotEnabledOrNotSelected(group, inv)) continue;
|
|
|
|
if (!mIv[group][inv]->isAvailable()) {
|
|
result = false;
|
|
continue;
|
|
}
|
|
|
|
record_t<> *rec;
|
|
rec = mIv[group][inv]->getRecordStruct(RealTimeRunData_Debug);
|
|
|
|
// Get Pac
|
|
cfgGroupInv->power = mIv[group][inv]->getChannelFieldValue(CH0, FLD_PAC, rec);
|
|
logObj["P"] = cfgGroupInv->power;
|
|
power += cfgGroupInv->power;
|
|
|
|
// Get dcVoltage
|
|
float U = mIv[group][inv]->getChannelFieldValue(CH1, FLD_UDC, rec);
|
|
if (U == 0) {
|
|
result = false;
|
|
continue;
|
|
}
|
|
cfgGroupInv->dcVoltage = U;
|
|
logObj["U"] = cfgGroupInv->dcVoltage;
|
|
}
|
|
|
|
cfgGroup->power = power;
|
|
mLog["P"] = cfgGroup->power;
|
|
|
|
if ((cfgGroup->battEnabled) && (cfgGroup->power > 0))
|
|
cfgGroup->battSwitch = true;
|
|
|
|
return result;
|
|
}
|
|
|
|
/** groupBatteryprotection
|
|
* Batterieschutzfunktion
|
|
* @param group
|
|
* @returns true/false
|
|
* @todo Inverter mit permanentem Limit versehen
|
|
* @todo BMS auslesen hinzufügen
|
|
*/
|
|
bool groupBatteryprotection(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
|
|
cfgGroup->lastRun = *tsp;
|
|
|
|
if (mCfg->debug) {
|
|
mLog["t"] = "groupBatteryprotection";
|
|
*doLog = true;
|
|
}
|
|
|
|
// Protection
|
|
if (cfgGroup->battEnabled) {
|
|
mLog["en"] = true;
|
|
|
|
// Config - parameter check
|
|
if (cfgGroup->battVoltageOn <= cfgGroup->battVoltageOff) {
|
|
cfgGroup->battSwitch = false;
|
|
mLog["err"] = "Config - battVoltageOn(" + (String)cfgGroup->battVoltageOn + ") <= battVoltageOff(" + (String)cfgGroup->battVoltageOff + ")";
|
|
*doLog = true;
|
|
return false;
|
|
}
|
|
|
|
// Config - parameter check
|
|
if (cfgGroup->battVoltageOn <= (cfgGroup->battVoltageOff + 1)) {
|
|
cfgGroup->battSwitch = false;
|
|
mLog["err"] = "Config - battVoltageOn(" + (String)cfgGroup->battVoltageOn + ") <= battVoltageOff(" + (String)cfgGroup->battVoltageOff + " + 1V)";
|
|
*doLog = true;
|
|
return false;
|
|
}
|
|
|
|
// Config - parameter check
|
|
if (cfgGroup->battVoltageOn <= 22) {
|
|
cfgGroup->battSwitch = false;
|
|
mLog["err"] = "Config - battVoltageOn(" + (String)cfgGroup->battVoltageOn + ") <= 22V)";
|
|
*doLog = true;
|
|
return false;
|
|
}
|
|
|
|
// Check
|
|
JsonArray logArr = mLog.createNestedArray("ix");
|
|
|
|
if (!cfgGroup->battSwitch) {
|
|
// is off turn on
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
// Inverter not enabled or not selected -> ignore
|
|
if (NotEnabledOrNotSelected(group, inv)) continue;
|
|
|
|
JsonObject logObj = logArr.createNestedObject();
|
|
logObj["i"] = inv;
|
|
logObj["U"] = cfgGroup->inverters[inv].dcVoltage;
|
|
|
|
if (cfgGroup->inverters[inv].dcVoltage < cfgGroup->battVoltageOn) continue;
|
|
cfgGroup->battSwitch = true;
|
|
*doLog = true;
|
|
}
|
|
} else {
|
|
// is on turn off
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
// Inverter not enabled or not selected -> ignore
|
|
if (NotEnabledOrNotSelected(group, inv)) continue;
|
|
|
|
JsonObject logObj = logArr.createNestedObject();
|
|
logObj["i"] = inv;
|
|
logObj["U"] = cfgGroup->inverters[inv].dcVoltage;
|
|
|
|
if (cfgGroup->inverters[inv].dcVoltage > cfgGroup->battVoltageOff) continue;
|
|
cfgGroup->battSwitch = false;
|
|
*doLog = true;
|
|
}
|
|
}
|
|
// Battery
|
|
String gr = "zero/state/groups/" + String(group) + "/battery";
|
|
mqttObj["enabled"] = cfgGroup->battEnabled;
|
|
mqttObj["voltageOn"] = cfgGroup->battVoltageOn;
|
|
mqttObj["voltageOff"] = cfgGroup->battVoltageOff;
|
|
mqttObj["switch"] = cfgGroup->battSwitch;
|
|
mqttPublish(gr.c_str(), mqttDoc.as<std::string>().c_str());
|
|
mqttDoc.clear();
|
|
} else {
|
|
mLog["en"] = false;
|
|
|
|
cfgGroup->battSwitch = true;
|
|
}
|
|
|
|
mLog["sw"] = cfgGroup->battSwitch;
|
|
|
|
return true;
|
|
}
|
|
|
|
/** groupGetPowermeter
|
|
* Holt die Daten vom Powermeter
|
|
* @param group
|
|
* @returns true/false
|
|
* @todo Eventuell muss am Ende geprüft werden ob die Daten vom Powermeter plausibel sind.
|
|
*/
|
|
bool groupGetPowermeter(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
|
|
mCfg->groups[group].lastRun = *tsp;
|
|
|
|
if (mCfg->debug) mLog["t"] = "groupGetPowermeter";
|
|
*doLog = true;
|
|
|
|
mCfg->groups[group].pm_P = mPowermeter.getDataAVG(group).P;
|
|
mCfg->groups[group].pm_P1 = mPowermeter.getDataAVG(group).P1;
|
|
mCfg->groups[group].pm_P2 = mPowermeter.getDataAVG(group).P2;
|
|
mCfg->groups[group].pm_P3 = mPowermeter.getDataAVG(group).P3;
|
|
|
|
if (
|
|
(mCfg->groups[group].pm_P == 0) && (mCfg->groups[group].pm_P1 == 0) &&
|
|
(mCfg->groups[group].pm_P2 == 0) && (mCfg->groups[group].pm_P3 == 0)) {
|
|
return false;
|
|
}
|
|
|
|
mLog["P"] = mCfg->groups[group].pm_P;
|
|
mLog["P1"] = mCfg->groups[group].pm_P1;
|
|
mLog["P2"] = mCfg->groups[group].pm_P2;
|
|
mLog["P3"] = mCfg->groups[group].pm_P3;
|
|
|
|
// MQTT - Powermeter
|
|
if(mMqtt->isConnected())
|
|
{
|
|
mqttObj["Sum"] = mCfg->groups[group].pm_P;
|
|
mqttObj["L1"] = mCfg->groups[group].pm_P1;
|
|
mqttObj["L2"] = mCfg->groups[group].pm_P2;
|
|
mqttObj["L3"] = mCfg->groups[group].pm_P3;
|
|
mMqtt->publish(String("zero/state/groups/" + String(group) + "/powermeter/P").c_str(), mqttDoc.as<std::string>().c_str(), false);
|
|
mqttDoc.clear();
|
|
}
|
|
|
|
// if (cfgGroup->pm_Publish_W) {
|
|
// cfgGroup->pm_Publish_W = false;
|
|
// obj["todo"] = "true";
|
|
// obj["Sum"] = cfgGroup->pm_P;
|
|
// obj["L1"] = cfgGroup->pm_P1;
|
|
// obj["L2"] = cfgGroup->pm_P2;
|
|
// obj["L2"] = cfgGroup->pm_P3;
|
|
// mMqtt->publish("zero/powermeter/W", doc.as<std::string>().c_str(), false);
|
|
// doc.clear();
|
|
// }
|
|
|
|
return true;
|
|
}
|
|
|
|
/** groupController
|
|
* PID-Regler
|
|
* @param group
|
|
* @param *tsp
|
|
* @param *doLog
|
|
* @returns true/false
|
|
*/
|
|
bool groupController(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
|
|
cfgGroup->lastRun = *tsp;
|
|
|
|
// Führungsgröße w in Watt
|
|
int32_t w = cfgGroup->setPoint;
|
|
|
|
// Regelgröße x in Watt
|
|
int32_t x = cfgGroup->pm_P;
|
|
int32_t x1 = cfgGroup->pm_P1;
|
|
int32_t x2 = cfgGroup->pm_P2;
|
|
int32_t x3 = cfgGroup->pm_P3;
|
|
|
|
// Regelabweichung e in Watt
|
|
int32_t e = w - x;
|
|
int32_t e1 = w - x1;
|
|
int32_t e2 = w - x2;
|
|
int32_t e3 = w - x3;
|
|
|
|
// Regler
|
|
float Kp = cfgGroup->Kp;
|
|
float Ki = cfgGroup->Ki;
|
|
float Kd = cfgGroup->Kd;
|
|
unsigned long Ta = *tsp - mCfg->groups[group].lastRefresh;
|
|
|
|
// - P-Anteil
|
|
int32_t yP = Kp * e;
|
|
int32_t yP1 = Kp * e1;
|
|
int32_t yP2 = Kp * e2;
|
|
int32_t yP3 = Kp * e3;
|
|
|
|
// - I-Anteil
|
|
cfgGroup->eSum += e;
|
|
cfgGroup->eSum1 += e1;
|
|
cfgGroup->eSum2 += e2;
|
|
cfgGroup->eSum3 += e3;
|
|
int32_t yI = Ki * Ta * cfgGroup->eSum;
|
|
int32_t yI1 = Ki * Ta * cfgGroup->eSum1;
|
|
int32_t yI2 = Ki * Ta * cfgGroup->eSum2;
|
|
int32_t yI3 = Ki * Ta * cfgGroup->eSum3;
|
|
|
|
// - D-Anteil
|
|
int32_t yD = Kd * (e - cfgGroup->eOld) / Ta;
|
|
int32_t yD1 = Kd * (e1 - cfgGroup->eOld1) / Ta;
|
|
int32_t yD2 = Kd * (e2 - cfgGroup->eOld2) / Ta;
|
|
int32_t yD3 = Kd * (e3 - cfgGroup->eOld3) / Ta;
|
|
cfgGroup->eOld = e;
|
|
cfgGroup->eOld1 = e1;
|
|
cfgGroup->eOld2 = e2;
|
|
cfgGroup->eOld3 = e3;
|
|
|
|
// - PID-Anteil
|
|
int32_t y = yP + yI + yD;
|
|
int32_t y1 = yP1 + yI1 + yD1;
|
|
int32_t y2 = yP2 + yI2 + yD2;
|
|
int32_t y3 = yP3 + yI3 + yD3;
|
|
|
|
// Regelbegrenzung
|
|
// TODO: Hier könnte man den maximalen Sprung begrenzen
|
|
|
|
// Stellgröße
|
|
cfgGroup->y = y;
|
|
cfgGroup->y1 = y1;
|
|
cfgGroup->y2 = y2;
|
|
cfgGroup->y3 = y3;
|
|
|
|
// Log
|
|
if (mCfg->debug) {
|
|
mLog["t"] = "groupController";
|
|
*doLog = true;
|
|
|
|
mLog["w"] = w;
|
|
|
|
mLog["x"] = x;
|
|
mLog["x1"] = x1;
|
|
mLog["x2"] = x2;
|
|
mLog["x3"] = x3;
|
|
|
|
mLog["e"] = e;
|
|
mLog["e1"] = e1;
|
|
mLog["e2"] = e2;
|
|
mLog["e3"] = e3;
|
|
|
|
mLog["Kp"] = Kp;
|
|
mLog["Ki"] = Ki;
|
|
mLog["Kd"] = Kd;
|
|
mLog["Ta"] = Ta;
|
|
|
|
mLog["yP"] = yP;
|
|
mLog["yP1"] = yP1;
|
|
mLog["yP2"] = yP2;
|
|
mLog["yP3"] = yP3;
|
|
|
|
mLog["esum"] = cfgGroup->eSum;
|
|
mLog["esum1"] = cfgGroup->eSum1;
|
|
mLog["esum2"] = cfgGroup->eSum2;
|
|
mLog["esum3"] = cfgGroup->eSum3;
|
|
|
|
mLog["yI"] = yI;
|
|
mLog["yI1"] = yI1;
|
|
mLog["yI2"] = yI2;
|
|
mLog["yI3"] = yI3;
|
|
|
|
mLog["ealt"] = cfgGroup->eOld;
|
|
mLog["ealt1"] = cfgGroup->eOld1;
|
|
mLog["ealt2"] = cfgGroup->eOld2;
|
|
mLog["ealt3"] = cfgGroup->eOld3;
|
|
|
|
mLog["yD"] = yD;
|
|
mLog["yD1"] = yD1;
|
|
mLog["yD2"] = yD2;
|
|
mLog["yD3"] = yD3;
|
|
|
|
mLog["y"] = y;
|
|
mLog["y1"] = y1;
|
|
mLog["y2"] = y2;
|
|
mLog["y3"] = y3;
|
|
}
|
|
|
|
// powerTolerance
|
|
if (
|
|
(e < cfgGroup->powerTolerance) &&
|
|
(e > -cfgGroup->powerTolerance) &&
|
|
(e1 < cfgGroup->powerTolerance) &&
|
|
(e1 > -cfgGroup->powerTolerance) &&
|
|
(e2 < cfgGroup->powerTolerance) &&
|
|
(e2 > -cfgGroup->powerTolerance) &&
|
|
(e3 < cfgGroup->powerTolerance) &&
|
|
(e3 > -cfgGroup->powerTolerance)) {
|
|
mLog["tol"] = cfgGroup->powerTolerance;
|
|
return false;
|
|
}
|
|
|
|
// Advanced
|
|
String gr = "zero/state/groups/" + String(group) + "/advanced";
|
|
mqttObj["setPoint"] = cfgGroup->setPoint;
|
|
mqttObj["refresh"] = cfgGroup->refresh;
|
|
mqttObj["powerTolerance"] = cfgGroup->powerTolerance;
|
|
mqttObj["powerMax"] = cfgGroup->powerMax;
|
|
mqttObj["Kp"] = cfgGroup->Kp;
|
|
mqttObj["Ki"] = cfgGroup->Ki;
|
|
mqttObj["Kd"] = cfgGroup->Kd;
|
|
mqttPublish(gr.c_str(), mqttDoc.as<std::string>().c_str());
|
|
mqttDoc.clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
/** groupPrognose
|
|
*
|
|
* @param group
|
|
* @param *tsp
|
|
* @param *doLog
|
|
* @returns true/false
|
|
* @todo Vorhersage ob ein gedrosselter Inverter mehr Leistung liefern kann, wenn das Limit erhöht wird.
|
|
* @todo Klären in wieweit diese Funktion benötigt wird.
|
|
*/
|
|
bool groupPrognose(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
|
|
mCfg->groups[group].lastRun = *tsp;
|
|
|
|
if (mCfg->debug) {
|
|
mLog["t"] = "groupPrognose";
|
|
*doLog = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** groupAufteilen
|
|
*
|
|
* @param group
|
|
* @returns true/false
|
|
*/
|
|
bool groupAufteilen(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
|
|
cfgGroup->lastRun = *tsp;
|
|
|
|
if (mCfg->debug) {
|
|
mLog["t"] = "groupAufteilen";
|
|
*doLog = true;
|
|
}
|
|
|
|
int32_t y = cfgGroup->y;
|
|
int32_t y1 = cfgGroup->y1;
|
|
int32_t y2 = cfgGroup->y2;
|
|
int32_t y3 = cfgGroup->y3;
|
|
|
|
if (cfgGroup->power > cfgGroup->powerMax) {
|
|
int32_t diff = cfgGroup->power - cfgGroup->powerMax;
|
|
y = y - diff;
|
|
y1 = y1 - (diff * y1 / y);
|
|
y1 = y1 - (diff * y2 / y);
|
|
y1 = y1 - (diff * y3 / y);
|
|
}
|
|
|
|
// TDOD: nochmal durchdenken ... es muss für Sum und L1-3 sein
|
|
// uint16_t groupPmax = 0;
|
|
// for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
// groupPmax = mCfg->groups[group].inverters[inv].limit;
|
|
// }
|
|
// int16_t groupPavail = mCfg->groups[group].powerMax - groupPmax;
|
|
//
|
|
// if ( y > groupPavail ) {
|
|
// y = groupPavail;
|
|
// }
|
|
|
|
mLog["y"] = y;
|
|
mLog["y1"] = y1;
|
|
mLog["y2"] = y2;
|
|
mLog["y3"] = y3;
|
|
|
|
bool grpTarget[7] = {false, false, false, false, false, false, false};
|
|
int8_t ivId_Pmin[7] = {-1, -1, -1, -1, -1, -1, -1};
|
|
int8_t ivId_Pmax[7] = {-1, -1, -1, -1, -1, -1, -1};
|
|
uint16_t ivPmin[7] = {65535, 65535, 65535, 65535, 65535, 65535, 65535};
|
|
uint16_t ivPmax[7] = {0, 0, 0, 0, 0, 0, 0};
|
|
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[inv];
|
|
|
|
// Inverter not enabled or not selected -> ignore
|
|
if (NotEnabledOrNotSelected(group, inv)) continue;
|
|
|
|
/// record_t<> *rec;
|
|
/// rec = mIv[group][inv]->getRecordStruct(RealTimeRunData_Debug);
|
|
/// cfgGroupInv->power = mIv[group][inv]->getChannelFieldValue(CH0, FLD_PAC, rec);
|
|
|
|
if ((cfgGroupInv->power < ivPmin[cfgGroupInv->target]) && (cfgGroupInv->limit < cfgGroupInv->powerMax) && (cfgGroupInv->limit < mIv[group][inv]->getMaxPower())) {
|
|
grpTarget[cfgGroupInv->target] = true;
|
|
ivPmin[cfgGroupInv->target] = cfgGroupInv->power;
|
|
ivId_Pmin[cfgGroupInv->target] = inv;
|
|
// Hier kein return oder continue sonst dauerreboot
|
|
}
|
|
|
|
if ((cfgGroupInv->power > ivPmax[cfgGroupInv->target]) && (cfgGroupInv->limit > cfgGroupInv->powerMin) && (cfgGroupInv->limit > (mIv[group][inv]->getMaxPower() * 2 / 100))) {
|
|
grpTarget[cfgGroupInv->target] = true;
|
|
ivPmax[cfgGroupInv->target] = cfgGroupInv->power;
|
|
ivId_Pmax[cfgGroupInv->target] = inv;
|
|
// Hier kein return oder continue sonst dauerreboot
|
|
}
|
|
}
|
|
for (uint8_t i = 0; i < 7; i++) {
|
|
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 (int8_t i = (7 - 1); i >= 0; i--) {
|
|
if (!grpTarget[i]) {
|
|
continue;
|
|
}
|
|
mLog[String(String("10") + String(i))] = String(i);
|
|
|
|
int32_t *deltaP;
|
|
switch (i) {
|
|
case 6:
|
|
case 3:
|
|
deltaP = &mCfg->groups[group].y3;
|
|
break;
|
|
case 5:
|
|
case 2:
|
|
deltaP = &mCfg->groups[group].y2;
|
|
break;
|
|
case 4:
|
|
case 1:
|
|
deltaP = &mCfg->groups[group].y1;
|
|
break;
|
|
case 0:
|
|
deltaP = &mCfg->groups[group].y;
|
|
break;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** groupSetReboot
|
|
* Sets the calculated Reboot to the Inverter and waits for ACK.
|
|
* @param group
|
|
* @param tsp
|
|
* @param doLog
|
|
* @returns true/false
|
|
*/
|
|
bool groupSetReboot(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
bool result = true;
|
|
|
|
cfgGroup->lastRun = *tsp;
|
|
|
|
if (mCfg->debug) mLog["t"] = "groupSetReboot";
|
|
|
|
JsonArray logArr = mLog.createNestedArray("ix");
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
JsonObject logObj = logArr.createNestedObject();
|
|
logObj["i"] = inv;
|
|
|
|
zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[inv];
|
|
|
|
// Inverter not enabled or not selected -> ignore
|
|
if (NotEnabledOrNotSelected(group, inv)) continue;
|
|
|
|
// Inverter not available -> ignore
|
|
if (!mIv[group][inv]->isAvailable()) {
|
|
logObj["a"] = false;
|
|
continue;
|
|
}
|
|
|
|
if (mCfg->debug) {
|
|
logObj["dR"] = cfgGroupInv->doReboot;
|
|
logObj["wR"] = cfgGroupInv->waitAckSetReboot;
|
|
*doLog = true;
|
|
}
|
|
|
|
// Wait
|
|
if (cfgGroupInv->waitAckSetReboot > 0) {
|
|
result = false;
|
|
if (mCfg->debug) *doLog = true;
|
|
continue;
|
|
}
|
|
|
|
// Reset
|
|
if ((cfgGroupInv->doReboot == 2) && (cfgGroupInv->waitAckSetReboot == 0)) {
|
|
cfgGroupInv->doReboot = -1;
|
|
if (mCfg->debug) {
|
|
logObj["act"] = "done";
|
|
*doLog = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Calculate
|
|
if (cfgGroupInv->doReboot == 1) {
|
|
cfgGroupInv->doReboot = 2;
|
|
cfgGroupInv->waitAckSetReboot = 120;
|
|
}
|
|
|
|
// Inverter nothing to do -> ignore
|
|
if (cfgGroupInv->doReboot == -1) {
|
|
if (mCfg->debug) {
|
|
logObj["act"] = "nothing to do";
|
|
*doLog = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
result = false;
|
|
*doLog = true;
|
|
|
|
// send Command over RestAPI
|
|
DynamicJsonDocument doc(512);
|
|
JsonObject obj = doc.to<JsonObject>();
|
|
obj["id"] = cfgGroupInv->id;
|
|
obj["path"] = "ctrl";
|
|
obj["cmd"] = "restart";
|
|
mApi->ctrlRequest(obj);
|
|
|
|
if (mCfg->debug) logObj["d"] = obj;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/** groupSetPower
|
|
* Sets the calculated Power to the Inverter and waits for ACK.
|
|
* @param group
|
|
* @param tsp
|
|
* @param doLog
|
|
* @returns true/false
|
|
*/
|
|
bool groupSetPower(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
bool result = true;
|
|
|
|
cfgGroup->lastRun = *tsp;
|
|
|
|
if (mCfg->debug) mLog["t"] = "groupSetPower";
|
|
|
|
JsonArray logArr = mLog.createNestedArray("ix");
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
JsonObject logObj = logArr.createNestedObject();
|
|
logObj["i"] = inv;
|
|
|
|
zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[inv];
|
|
|
|
// Inverter not enabled or not selected -> ignore
|
|
if (NotEnabledOrNotSelected(group, inv)) continue;
|
|
|
|
// Inverter not available -> ignore
|
|
if (!mIv[group][inv]->isAvailable()) {
|
|
logObj["a"] = false;
|
|
continue;
|
|
}
|
|
|
|
if (mCfg->debug) {
|
|
logObj["dP"] = cfgGroupInv->doPower;
|
|
logObj["wP"] = cfgGroupInv->waitAckSetPower;
|
|
}
|
|
|
|
// Wait
|
|
if (cfgGroupInv->waitAckSetPower > 0) {
|
|
result = false;
|
|
if (mCfg->debug) *doLog = true;
|
|
continue;
|
|
}
|
|
|
|
// Reset
|
|
if ((cfgGroupInv->doPower != -1) && (cfgGroupInv->waitAckSetPower == 0)) {
|
|
cfgGroupInv->doPower = -1;
|
|
if (mCfg->debug) {
|
|
logObj["act"] = "done";
|
|
*doLog = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Calculate
|
|
logObj["battSw"] = mCfg->groups[group].battSwitch;
|
|
logObj["ivL"] = cfgGroupInv->limitNew;
|
|
logObj["ivSw"] = mIv[group][inv]->isProducing();
|
|
if (
|
|
(mCfg->groups[group].battSwitch == true) &&
|
|
(cfgGroupInv->limitNew > cfgGroupInv->powerMin) &&
|
|
(mIv[group][inv]->isProducing() == false)) {
|
|
// On
|
|
cfgGroupInv->doPower = 1;
|
|
cfgGroupInv->waitAckSetPower = 120;
|
|
logObj["act"] = "on";
|
|
}
|
|
if (
|
|
(
|
|
(mCfg->groups[group].battSwitch == false) ||
|
|
(cfgGroupInv->limitNew < (cfgGroupInv->powerMin - 50))) &&
|
|
(mIv[group][inv]->isProducing() == true)) {
|
|
// Off
|
|
cfgGroupInv->doPower = 0;
|
|
cfgGroupInv->waitAckSetPower = 120;
|
|
logObj["act"] = "off";
|
|
}
|
|
|
|
// Inverter nothing to do -> ignore
|
|
if (cfgGroupInv->doPower == -1) {
|
|
if (mCfg->debug) {
|
|
logObj["act"] = "nothing to do";
|
|
*doLog = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
result = false;
|
|
*doLog = true;
|
|
|
|
// send Command over RestAPI
|
|
DynamicJsonDocument doc(512);
|
|
JsonObject obj = doc.to<JsonObject>();
|
|
obj["val"] = cfgGroupInv->doPower;
|
|
obj["id"] = cfgGroupInv->id;
|
|
obj["path"] = "ctrl";
|
|
obj["cmd"] = "power";
|
|
mApi->ctrlRequest(obj);
|
|
|
|
if (mCfg->debug) logObj["d"] = obj;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/** groupSetLimit
|
|
* Sets the calculated Limit to the Inverter and waits for ACK.
|
|
* @param group
|
|
* @param tsp
|
|
* @param doLog
|
|
* @returns true/false
|
|
*/
|
|
bool groupSetLimit(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
bool result = true;
|
|
|
|
cfgGroup->lastRun = *tsp;
|
|
|
|
if (mCfg->debug) mLog["t"] = "groupSetLimit";
|
|
|
|
JsonArray logArr = mLog.createNestedArray("ix");
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
JsonObject logObj = logArr.createNestedObject();
|
|
logObj["i"] = inv;
|
|
|
|
zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[inv];
|
|
|
|
// Inverter not enabled or not selected -> ignore
|
|
if (NotEnabledOrNotSelected(group, inv)) continue;
|
|
|
|
// Inverter not available -> ignore
|
|
if (!mIv[group][inv]->isAvailable()) {
|
|
logObj["a"] = false;
|
|
continue;
|
|
}
|
|
|
|
logObj["wL"] = cfgGroupInv->waitAckSetLimit;
|
|
if (mCfg->debug) {
|
|
logObj["dL"] = cfgGroupInv->doLimit;
|
|
logObj["L"] = cfgGroupInv->limit;
|
|
}
|
|
|
|
// Wait
|
|
if (cfgGroupInv->waitAckSetLimit > 0) {
|
|
result = false;
|
|
if (mCfg->debug) *doLog = true;
|
|
continue;
|
|
}
|
|
|
|
// Reset
|
|
if ((cfgGroupInv->doLimit != -1) && (cfgGroupInv->waitAckSetLimit == 0)) {
|
|
cfgGroupInv->doLimit = -1;
|
|
if (mCfg->debug) {
|
|
logObj["act"] = "done";
|
|
*doLog = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Calculate
|
|
uint16_t power100proz = mIv[group][inv]->getMaxPower();
|
|
uint16_t power2proz = (power100proz * 2) / 100;
|
|
|
|
// if isOff -> Limit Pmin
|
|
if (!mIv[group][inv]->isProducing()) {
|
|
cfgGroupInv->limitNew = cfgGroupInv->powerMin;
|
|
}
|
|
|
|
// Restriction LimitNew < Pmin
|
|
if (cfgGroupInv->limitNew < cfgGroupInv->powerMin) {
|
|
cfgGroupInv->limitNew = cfgGroupInv->powerMin;
|
|
}
|
|
|
|
// Restriction LimitNew < 2%
|
|
if (cfgGroupInv->limitNew < power2proz) {
|
|
cfgGroupInv->limitNew = power2proz;
|
|
}
|
|
|
|
// Restriction LimitNew > Pmax
|
|
if (cfgGroupInv->limitNew > cfgGroupInv->powerMax) {
|
|
cfgGroupInv->limitNew = cfgGroupInv->powerMax;
|
|
}
|
|
|
|
// Restriction LimitNew > 100%
|
|
if (cfgGroupInv->limitNew > power100proz) {
|
|
cfgGroupInv->limitNew = power100proz;
|
|
}
|
|
|
|
// Restriction modulo 4 bzw 2
|
|
cfgGroupInv->limitNew = cfgGroupInv->limitNew - (cfgGroupInv->limitNew % 4);
|
|
// TODO: HM-800 kann vermutlich nur in 4W Schritten ... 20W -> 16W ... 100W -> 96W
|
|
|
|
// Restriction deltaLimitNew < 5 W
|
|
/*
|
|
if (
|
|
(cfgGroupInv->limitNew > (cfgGroupInv->powerMin + ZEROEXPORT_GROUP_WR_LIMIT_MIN_DIFF)) &&
|
|
(cfgGroupInv->limitNew > (cfgGroupInv->limit + ZEROEXPORT_GROUP_WR_LIMIT_MIN_DIFF)) &&
|
|
(cfgGroupInv->limitNew < (cfgGroupInv->limit - ZEROEXPORT_GROUP_WR_LIMIT_MIN_DIFF))) {
|
|
logObj["err"] = String("Diff < ") + String(ZEROEXPORT_GROUP_WR_LIMIT_MIN_DIFF) + String("W");
|
|
|
|
*doLog = true;
|
|
return false;
|
|
}
|
|
*/
|
|
|
|
if (cfgGroupInv->limit != cfgGroupInv->limitNew) {
|
|
cfgGroupInv->doLimit = 1;
|
|
cfgGroupInv->waitAckSetLimit = 60;
|
|
}
|
|
|
|
cfgGroupInv->limit = cfgGroupInv->limitNew;
|
|
logObj["zeL"] = cfgGroupInv->limit;
|
|
|
|
// Inverter nothing to do -> ignore
|
|
if (cfgGroupInv->doLimit == -1) {
|
|
if (mCfg->debug) {
|
|
logObj["act"] = "nothing to do";
|
|
*doLog = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
result = false;
|
|
*doLog = true;
|
|
|
|
// send Command over RestAPI
|
|
DynamicJsonDocument doc(512);
|
|
JsonObject obj = doc.to<JsonObject>();
|
|
if (cfgGroupInv->limit > 0) {
|
|
obj["val"] = cfgGroupInv->limit;
|
|
} else {
|
|
obj["val"] = 0;
|
|
}
|
|
obj["id"] = cfgGroupInv->id;
|
|
obj["path"] = "ctrl";
|
|
obj["cmd"] = "limit_nonpersistent_absolute";
|
|
mApi->ctrlRequest(obj);
|
|
|
|
// publish to mqtt when mqtt
|
|
if(mMqtt->isConnected())
|
|
{
|
|
String gr = "zero/state/groups/" + String(group) + "/inverters/" + String(inv);
|
|
mqttObj["enabled"] = cfgGroupInv->enabled;
|
|
mqttObj["id"] = cfgGroupInv->id;
|
|
mqttObj["target"] = cfgGroupInv->target;
|
|
mqttObj["powerMin"] = cfgGroupInv->powerMin;
|
|
mqttObj["powerMax"] = cfgGroupInv->powerMax;
|
|
mMqtt->publish(gr.c_str(), mqttDoc.as<std::string>().c_str(), false);
|
|
mqttDoc.clear();
|
|
}
|
|
|
|
if (mCfg->debug) logObj["d"] = obj;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
/* mqttSubscribe
|
|
* when a MQTT Msg is needed to subscribe, then a publish is leading
|
|
*/
|
|
void mqttSubscribe(String gr, String payload)
|
|
{
|
|
mqttPublish(gr, payload);
|
|
mMqtt->subscribe(gr.c_str(), QOS_2);
|
|
}
|
|
/* PubInit
|
|
* when a MQTT Msg is needed to Publish, but not to subscribe.
|
|
*/
|
|
void mqttPublish(String gr, String payload, bool retain = false)
|
|
{
|
|
mMqtt->publish(gr.c_str(), payload.c_str(), retain);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void mqttInitTopic() {
|
|
if (mIsSubscribed) return;
|
|
if (!mMqtt->isConnected()) return;
|
|
|
|
mIsSubscribed = true;
|
|
|
|
// Global (zeroExport)
|
|
// TODO: Global wird fälschlicherweise hier je nach anzahl der aktivierten Gruppen bis zu 6x ausgeführt.
|
|
mqttSubscribe("zero/set/enabled", ((mCfg->enabled) ? dict[STR_TRUE] : dict[STR_FALSE]));
|
|
|
|
// TODO: Global wird fälschlicherweise hier je nach anzahl der aktivierten Gruppen bis zu 6x ausgeführt.
|
|
mqttSubscribe("zero/set/sleep", ((mCfg->sleep) ? dict[STR_TRUE] : dict[STR_FALSE]));
|
|
|
|
String gr;
|
|
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
if(!cfgGroup->enabled) continue; // exit here when group is disabled
|
|
|
|
gr = "zero/set/groups/" + String(group);
|
|
|
|
// General
|
|
mqttSubscribe(gr + "/enabled", ((cfgGroup->enabled) ? dict[STR_TRUE] : dict[STR_FALSE]));
|
|
mqttSubscribe(gr + "/sleep", ((cfgGroup->enabled) ? dict[STR_TRUE] : dict[STR_FALSE]));
|
|
|
|
// Powermeter
|
|
|
|
// Inverters - Only Publish
|
|
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
|
|
zeroExportGroupInverter_t *cfgGroupInv = &cfgGroup->inverters[inv];
|
|
|
|
mqttSubscribe(gr + "/inverters/" + String(inv) + "/enabled", ((cfgGroupInv->enabled) ? dict[STR_TRUE] : dict[STR_FALSE]));
|
|
mqttSubscribe(gr + "/inverters/" + String(inv) + "/powerMin", String(cfgGroupInv->powerMin));
|
|
mqttSubscribe(gr + "/inverters/" + String(inv) + "/powerMax", String(cfgGroupInv->powerMax));
|
|
}
|
|
|
|
// Battery
|
|
mqttSubscribe(gr + "/battery/switch", ((cfgGroup->battSwitch) ? dict[STR_TRUE] : dict[STR_FALSE]));
|
|
|
|
// Advanced
|
|
mqttSubscribe(gr + "/advanced/setPoint", String(cfgGroup->setPoint));
|
|
mqttSubscribe(gr + "/advanced/powerTolerance", String(cfgGroup->powerTolerance));
|
|
mqttSubscribe(gr + "/advanced/powerMax", String(cfgGroup->powerMax));
|
|
mqttSubscribe(gr + "/advanced/refresh", String(cfgGroup->refresh));
|
|
mqttSubscribe(gr + "/advanced/Kp", String(cfgGroup->Kp));
|
|
mqttSubscribe(gr + "/advanced/Ki", String(cfgGroup->Ki));
|
|
mqttSubscribe(gr + "/advanced/Kd", String(cfgGroup->Kd));
|
|
}
|
|
}
|
|
|
|
/** groupPublish
|
|
*
|
|
*/
|
|
bool groupPublish(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
|
|
if (mCfg->debug) mLog["t"] = "groupPublish";
|
|
|
|
cfgGroup->lastRun = *tsp;
|
|
|
|
if (mMqtt->isConnected()) {
|
|
// *doLog = true;
|
|
String gr;
|
|
|
|
// Global (zeroExport)
|
|
mqttSubscribe("zero/set/enabled", ((mCfg->enabled) ? dict[STR_TRUE] : dict[STR_FALSE]));
|
|
mqttSubscribe("zero/set/sleep", ((mCfg->sleep) ? dict[STR_TRUE] : dict[STR_FALSE]));
|
|
|
|
// General
|
|
gr = "zero/state/groups/" + String(group) + "/enabled";
|
|
|
|
mqttPublish(gr.c_str(), ((cfgGroup->enabled) ? dict[STR_TRUE] : dict[STR_FALSE]));
|
|
mqttPublish(gr.c_str(), ((cfgGroup->sleep) ? dict[STR_TRUE] : dict[STR_FALSE]));
|
|
|
|
gr = "zero/state/groups/" + String(group) + "/sleep";
|
|
mMqtt->publish(gr.c_str(), ((cfgGroup->sleep) ? dict[STR_TRUE] : dict[STR_FALSE]), false);
|
|
|
|
gr = "zero/state/groups/" + String(group) + "/name";
|
|
mMqtt->publish(gr.c_str(), cfgGroup->name, false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/** groupEmergency
|
|
* Dieser State versucht zeroExport in einen sicheren Zustand zu bringen, wenn technische Ausfälle vorliegen.
|
|
* @param group
|
|
* @param *tsp
|
|
* @param *doLog
|
|
* @returns true/false
|
|
* @todo Hier ist noch keine Funktion
|
|
*/
|
|
bool groupEmergency(uint8_t group, unsigned long *tsp, bool *doLog) {
|
|
zeroExportGroup_t *cfgGroup = &mCfg->groups[group];
|
|
|
|
cfgGroup->lastRun = *tsp;
|
|
|
|
if (mCfg->debug) {
|
|
mLog["t"] = "groupEmergency";
|
|
*doLog = true;
|
|
}
|
|
|
|
// TODO: hier fehlt der Code
|
|
|
|
return true;
|
|
}
|
|
|
|
/** sendLog
|
|
* Sendet den LogSpeicher über Webserial und/oder MQTT
|
|
*/
|
|
void sendLog(void) {
|
|
// Log over Webserial
|
|
if (mCfg->log_over_webserial) {
|
|
DPRINTLN(DBG_INFO, String("ze: ") + mDocLog.as<String>());
|
|
}
|
|
|
|
// Log over MQTT
|
|
if (mCfg->log_over_mqtt) {
|
|
if (mMqtt->isConnected()) {
|
|
mMqtt->publish("zero/log", mDocLog.as<std::string>().c_str(), false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** clearLog
|
|
* Löscht den LogSpeicher
|
|
*/
|
|
void clearLog(void) {
|
|
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<JsonObject>();
|
|
|
|
Inverter<> *mIv[ZEROEXPORT_MAX_GROUPS][ZEROEXPORT_GROUP_MAX_INVERTERS];
|
|
|
|
powermeter mPowermeter;
|
|
|
|
PubMqttType *mMqtt;
|
|
bool mIsSubscribed = false;
|
|
StaticJsonDocument<512> mqttDoc; // DynamicJsonDocument mqttDoc(512);
|
|
JsonObject mqttObj = mqttDoc.to<JsonObject>();
|
|
};
|
|
|
|
#endif /*__ZEROEXPORT__*/
|
|
|
|
#endif /* #if defined(PLUGIN_ZEROEXPORT) */
|
|
|