Browse Source

0.8.1030019

0.8.1030019
pull/1656/head
tictrick 8 months ago
committed by GitHub
parent
commit
fe1689979f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      src/defines.h
  2. 85
      src/plugins/zeroExport/powermeter.h
  3. 244
      src/plugins/zeroExport/zeroExport.h
  4. 38
      src/utils/DynamicJsonHandler.cpp
  5. 44
      src/utils/DynamicJsonHandler.h

2
src/defines.h

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 8 #define VERSION_MINOR 8
#define VERSION_PATCH 1030018 #define VERSION_PATCH 1030019
//------------------------------------- //-------------------------------------
typedef struct { typedef struct {
uint8_t ch; uint8_t ch;

85
src/plugins/zeroExport/powermeter.h

@ -19,7 +19,7 @@
#include <list> #include <list>
#include "plugins/zeroExport/lib/sml.h" #include "plugins/zeroExport/lib/sml.h"
#include "utils/DynamicJsonHandler.h"
typedef struct { typedef struct {
const unsigned char OBIS[6]; const unsigned char OBIS[6];
void (*Fn)(double &); void (*Fn)(double &);
@ -46,7 +46,7 @@ class powermeter {
* @param *log * @param *log
* @returns void * @returns void
*/ */
bool setup(IApp *app, zeroExport_t *cfg, PubMqttType *mqtt, JsonObject *log) { bool setup(IApp *app, zeroExport_t *cfg, PubMqttType *mqtt, DynamicJsonHandler *log) {
mApp = app; mApp = app;
mCfg = cfg; mCfg = cfg;
mMqtt = mqtt; mMqtt = mqtt;
@ -84,7 +84,7 @@ class powermeter {
switch (mCfg->groups[group].pm_type) { switch (mCfg->groups[group].pm_type) {
#if defined(ZEROEXPORT_POWERMETER_SHELLY) #if defined(ZEROEXPORT_POWERMETER_SHELLY)
case zeroExportPowermeterType_t::Shelly: case zeroExportPowermeterType_t::Shelly:
result = getPowermeterWattsShelly(*mLog, group, &power); result = getPowermeterWattsShelly(group, &power);
break; break;
#endif #endif
#if defined(ZEROEXPORT_POWERMETER_TASMOTA) #if defined(ZEROEXPORT_POWERMETER_TASMOTA)
@ -105,12 +105,12 @@ class powermeter {
*/ */
case zeroExportPowermeterType_t::Tibber: case zeroExportPowermeterType_t::Tibber:
if (mCfg->groups[group].pm_refresh < 3) mCfg->groups[group].pm_refresh = 3; if (mCfg->groups[group].pm_refresh < 3) mCfg->groups[group].pm_refresh = 3;
result = getPowermeterWattsTibber(*mLog, group, &power); result = getPowermeterWattsTibber(group, &power);
break; break;
#endif #endif
#if defined(ZEROEXPORT_POWERMETER_SHRDZM) #if defined(ZEROEXPORT_POWERMETER_SHRDZM)
case zeroExportPowermeterType_t::Shrdzm: case zeroExportPowermeterType_t::Shrdzm:
result = getPowermeterWattsShrdzm(*mLog, group, &power); result = getPowermeterWattsShrdzm(group, &power);
break; break;
#endif #endif
} }
@ -280,7 +280,7 @@ class powermeter {
zeroExport_t *mCfg; zeroExport_t *mCfg;
PubMqttType *mMqtt = nullptr; PubMqttType *mMqtt = nullptr;
JsonObject *mLog; DynamicJsonHandler* mLog;
IApp *mApp = nullptr; IApp *mApp = nullptr;
unsigned long mPreviousTsp = millis(); unsigned long mPreviousTsp = millis();
@ -294,7 +294,7 @@ class powermeter {
/** setHeader /** setHeader
* *
*/ */
void setHeader(HTTPClient *h) { void setHeader(HTTPClient *h, String auth = "", u8_t realm = NULL) {
h->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); h->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
/// h->setUserAgent("Ahoy-Agent"); /// h->setUserAgent("Ahoy-Agent");
/// // TODO: Ahoy-0.8.850024-zero /// // TODO: Ahoy-0.8.850024-zero
@ -303,6 +303,43 @@ class powermeter {
h->setTimeout(1000); h->setTimeout(1000);
h->addHeader("Content-Type", "application/json"); h->addHeader("Content-Type", "application/json");
h->addHeader("Accept", "application/json"); h->addHeader("Accept", "application/json");
/*
Shelly PM Mini Gen3
Shelly Plus 1PM
Shelly Plus 2PM
Shelly Pro 3EM - 120A
Shelly Pro 4PM
Shelly Pro Dual Cover / Shutter PM
Shelly Pro 1PM
Shelly Pro 2PM
Shelly Pro EM - 50
Shelly Qubino Wave 1PM Mini
Shelly Qubino Wave PM Mini
Shelly Qubino Wave Shutter
Shelly Qubino Wave 1PM
Shelly Qubino Wave 2PM
Shelly Qubino Wave Pro 1PM
Shelly Qubino Wave Pro 2PM
Shelly 3EM
Shelly EM + 120A Clamp
*/
/*if (auth != NULL && realm) http.addHeader("WWW-Authenticate", "Digest qop=\"auth\", realm=\"" + shellypro4pm-f008d1d8b8b8 + "\", nonce=\"60dc59c6\", algorithm=SHA-256");
else if (auth != NULL) http.addHeader("Authorization", "Basic " + auth);*/
/*
All Required:
realm: string, device_id of the Shelly device.
username: string, must be set to admin.
nonce: number, random or pseudo-random number to prevent replay attacks, taken from the error message.
cnonce: number, client nonce, random number generated by the client.
response: string, encoding of the string <ha1> + ":" + <nonce> + ":" + <nc> + ":" + <cnonce> + ":" + "auth" + ":" + <ha2> in SHA256.
ha1: string, <user>:<realm>:<password> encoded in SHA256
ha2: string, "dummy_method:dummy_uri" encoded in SHA256
algorithm: string, SHA-256.
*/
} }
#if defined(ZEROEXPORT_POWERMETER_SHELLY) #if defined(ZEROEXPORT_POWERMETER_SHELLY)
@ -312,13 +349,13 @@ class powermeter {
* @param group * @param group
* @returns true/false * @returns true/false
*/ */
bool getPowermeterWattsShelly(JsonObject logObj, uint8_t group, float *power) { bool getPowermeterWattsShelly(uint8_t group, float *power) {
logObj["mod"] = "getPowermeterWattsShelly"; mLog->addProperty("mod", "getPowermeterWattsShelly");
setHeader(&http); setHeader(&http);
String url = String("http://") + String(mCfg->groups[group].pm_src) + String("/") + String(mCfg->groups[group].pm_jsonPath); String url = String("http://") + String(mCfg->groups[group].pm_src) + String("/") + String(mCfg->groups[group].pm_jsonPath);
logObj["HTTP_URL"] = url; mLog->addProperty("HTTP_URL", url);
http.begin(url); http.begin(url);
@ -327,7 +364,7 @@ class powermeter {
DynamicJsonDocument doc(2048); DynamicJsonDocument doc(2048);
DeserializationError error = deserializeJson(doc, http.getString()); DeserializationError error = deserializeJson(doc, http.getString());
if (error) { if (error) {
logObj["err"] = "deserializeJson: " + String(error.c_str()); mLog->addProperty("err", "deserializeJson: " + String(error.c_str()));
return false; return false;
} else { } else {
switch (mCfg->groups[group].pm_target) { switch (mCfg->groups[group].pm_target) {
@ -395,7 +432,7 @@ class powermeter {
* @param group * @param group
* @returns true/false * @returns true/false
*/ */
bool getPowermeterWattsTasmota(JsonObject logObj, uint8_t group, float *power) { bool getPowermeterWattsTasmota(DynamicJsonHandler logObj, uint8_t group, float *power) {
logObj["mod"] = "getPowermeterWattsTasmota"; logObj["mod"] = "getPowermeterWattsTasmota";
/* /*
// TODO: nicht komplett // TODO: nicht komplett
@ -467,7 +504,7 @@ class powermeter {
* @param group * @param group
* @returns true/false * @returns true/false
*/ */
bool getPowermeterWattsHichi(JsonObject logObj, uint8_t group, float *power) { bool getPowermeterWattsHichi(DynamicJsonHandler logObj, uint8_t group, float *power) {
logObj["mod"] = "getPowermeterWattsHichi"; logObj["mod"] = "getPowermeterWattsHichi";
// Hier neuer Code - Anfang // Hier neuer Code - Anfang
@ -518,17 +555,20 @@ class powermeter {
{{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterImport}, {{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterImport},
{{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterExport}}; {{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterExport}};
bool getPowermeterWattsTibber(JsonObject logObj, uint8_t group, float *power) { /*
Daniel92: https://tibber.com/de/api/lookup/price-overview?postalCode=
Hab ich mal ausgelesen... hintendran die PLZ eingeben
energy/todayHours/<aktuelleStunde>/priceIncludingVat
*/
bool getPowermeterWattsTibber(uint8_t group, float *power) {
bool result = false; bool result = false;
mLog->addProperty("mod", "getPowermeterWattsTibber");
logObj["mod"] = "getPowermeterWattsTibber";
String auth = mCfg->groups[group].pm_pass; String auth = mCfg->groups[group].pm_pass;
String url = String("http://") + mCfg->groups[group].pm_src + String("/") + String(mCfg->groups[group].pm_jsonPath); String url = String("http://") + mCfg->groups[group].pm_src + String("/") + String(mCfg->groups[group].pm_jsonPath);
setHeader(&http); setHeader(&http, auth);
http.begin(url); http.begin(url);
http.addHeader("Authorization", "Basic " + auth);
if (http.GET() == HTTP_CODE_OK && http.getSize() > 0) { if (http.GET() == HTTP_CODE_OK && http.getSize() > 0) {
String myString = http.getString(); String myString = http.getString();
@ -571,8 +611,8 @@ class powermeter {
* @TODO: Username & Passwort wird mittels base64 verschlüsselt. Dies wird für die Authentizierung benötigt. Wichtig diese im WebUI unkenntlich zu machen und base64 im eeprom zu speichern, statt klartext. * @TODO: Username & Passwort wird mittels base64 verschlüsselt. Dies wird für die Authentizierung benötigt. Wichtig diese im WebUI unkenntlich zu machen und base64 im eeprom zu speichern, statt klartext.
* @TODO: Abfrage Interval einbauen. Info: Datei-Size kann auch mal 0-bytes sein? * @TODO: Abfrage Interval einbauen. Info: Datei-Size kann auch mal 0-bytes sein?
*/ */
bool getPowermeterWattsShrdzm(JsonObject logObj, uint8_t group, float *power) { bool getPowermeterWattsShrdzm(uint8_t group, float *power) {
logObj["mod"] = "getPowermeterWattsShrdzm"; mLog->addProperty("mod", "getPowermeterWattsShrdzm");
setHeader(&http); setHeader(&http);
@ -587,7 +627,7 @@ class powermeter {
DynamicJsonDocument doc(512); DynamicJsonDocument doc(512);
DeserializationError error = deserializeJson(doc, http.getString()); DeserializationError error = deserializeJson(doc, http.getString());
if (error) { if (error) {
logObj["err"] = "deserializeJson: " + String(error.c_str()); mLog->addProperty("err", "deserializeJson: " + String(error.c_str()));
return false; return false;
} else { } else {
if (doc.containsKey(F("16.7.0"))) { if (doc.containsKey(F("16.7.0"))) {
@ -606,8 +646,7 @@ class powermeter {
*/ */
void bufferWrite(float raw, short group) { void bufferWrite(float raw, short group) {
mPowermeterBuffer[group][mPowermeterBufferPos[group]] = raw; mPowermeterBuffer[group][mPowermeterBufferPos[group]] = raw;
mPowermeterBufferPos[group]++; mPowermeterBufferPos[group] = (mPowermeterBufferPos[group] + 1) % 5;
if (mPowermeterBufferPos[group] >= 5) mPowermeterBufferPos[group] = 0;
} }
}; };

244
src/plugins/zeroExport/zeroExport.h

@ -15,6 +15,8 @@
#include "AsyncJson.h" #include "AsyncJson.h"
#include "powermeter.h" #include "powermeter.h"
#include "utils/DynamicJsonHandler.h"
template <class HMSYSTEM> template <class HMSYSTEM>
class ZeroExport { class ZeroExport {
@ -47,9 +49,16 @@ class ZeroExport {
mApi = api; mApi = api;
mMqtt = mqtt; mMqtt = mqtt;
mIsInitialized = mPowermeter.setup(mApp, mCfg, mqtt, &mLog); mIsInitialized = mPowermeter.setup(mApp, mCfg, mqtt, &_log);
} }
/*void printJson() {
serializeJson(doc, Serial);
Serial.println();
serializeJsonPretty(doc, Serial);
}*/
/** loop /** loop
* Arbeitsschleife * Arbeitsschleife
* @param void * @param void
@ -83,13 +92,13 @@ class ZeroExport {
zeroExportGroupInverter_t *CfgGroupInv = &CfgGroup->inverters[inv]; zeroExportGroupInverter_t *CfgGroupInv = &CfgGroup->inverters[inv];
Inverter<> *iv = mSys->getInverterByPos(Queue.id); Inverter<> *iv = mSys->getInverterByPos(Queue.id);
mLog["g"] = group; _log.addProperty("g", group);
mLog["i"] = inv; _log.addProperty("i", inv);
// Check Data->iv // Check Data->iv
if (!iv->isAvailable()) { if (!iv->isAvailable()) {
if (mCfg->debug) { if (mCfg->debug) {
mLog["nA"] = "!isAvailable"; _log.addProperty("nA", "!isAvailable");
sendLog(); sendLog();
} }
clearLog(); clearLog();
@ -99,7 +108,7 @@ class ZeroExport {
// Check Data->waitAck // Check Data->waitAck
if (CfgGroupInv->waitAck > 0) { if (CfgGroupInv->waitAck > 0) {
if (mCfg->debug) { if (mCfg->debug) {
mLog["wA"] = CfgGroupInv->waitAck; _log.addProperty("wA", CfgGroupInv->waitAck);
sendLog(); sendLog();
} }
clearLog(); clearLog();
@ -113,16 +122,19 @@ class ZeroExport {
groupPower += mCfg->groups[group].inverters[inv].power; // Calc Data->groupPower groupPower += mCfg->groups[group].inverters[inv].power; // Calc Data->groupPower
groupLimit += mCfg->groups[group].inverters[inv].limit; // Calc Data->groupLimit groupLimit += mCfg->groups[group].inverters[inv].limit; // Calc Data->groupLimit
} }
mLog["gP"] = groupPower;
mLog["gL"] = groupLimit; _log.addProperty("gP", groupPower);
_log.addProperty("gL", groupLimit);
// Batteryprotection // Batteryprotection
mLog["bEn"] = (uint8_t)CfgGroup->battCfg; _log.addProperty("bEn", (uint8_t)CfgGroup->battCfg);
switch (CfgGroup->battCfg) { switch (CfgGroup->battCfg) {
case zeroExportBatteryCfg::none: case zeroExportBatteryCfg::none:
if (CfgGroup->battSwitch != true) { if (CfgGroup->battSwitch != true) {
CfgGroup->battSwitch = true; CfgGroup->battSwitch = true;
mLog["bA"] = "turn on";
_log.addProperty("bA", "turn on");
} }
break; break;
case zeroExportBatteryCfg::invUdc: case zeroExportBatteryCfg::invUdc:
@ -131,34 +143,34 @@ class ZeroExport {
if (CfgGroup->battSwitch != true) { if (CfgGroup->battSwitch != true) {
if (CfgGroup->battValue > CfgGroup->battLimitOn) { if (CfgGroup->battValue > CfgGroup->battLimitOn) {
CfgGroup->battSwitch = true; CfgGroup->battSwitch = true;
mLog["bA"] = "turn on"; _log.addProperty("bA", "turn on");
} }
if ((CfgGroup->battValue > CfgGroup->battLimitOff) && (CfgGroupInv->power > 0)) { if ((CfgGroup->battValue > CfgGroup->battLimitOff) && (CfgGroupInv->power > 0)) {
CfgGroup->battSwitch = true; CfgGroup->battSwitch = true;
mLog["bA"] = "turn on"; _log.addProperty("bA", "turn on");
} }
} else { } else {
if (CfgGroup->battValue < CfgGroup->battLimitOff) { if (CfgGroup->battValue < CfgGroup->battLimitOff) {
CfgGroup->battSwitch = false; CfgGroup->battSwitch = false;
mLog["bA"] = "turn off"; _log.addProperty("bA", "turn off");
} }
} }
mLog["bU"] = ah::round1(CfgGroup->battValue); _log.addProperty("bU", ah::round1(CfgGroup->battValue));
break; break;
default: default:
if (CfgGroup->battSwitch == true) { if (CfgGroup->battSwitch == true) {
CfgGroup->battSwitch = false; CfgGroup->battSwitch = false;
mLog["bA"] = "turn off"; _log.addProperty("bA", "turn off");
} }
break; break;
} }
mLog["bSw"] = CfgGroup->battSwitch; _log.addProperty("bSw", CfgGroup->battSwitch);
// Controller // Controller
// Führungsgröße w in Watt // Führungsgröße w in Watt
int16_t w = CfgGroup->setPoint; int16_t w = CfgGroup->setPoint;
mLog["w"] = w; _log.addProperty("w", w);
// Regelgröße x in Watt // Regelgröße x in Watt
int16_t x = 0.0; int16_t x = 0.0;
@ -167,16 +179,16 @@ class ZeroExport {
} else { } else {
x = mPowermeter.getDataAVG(group); x = mPowermeter.getDataAVG(group);
} }
mLog["x"] = x; _log.addProperty("x", x);
// Regelabweichung e in Watt // Regelabweichung e in Watt
int16_t e = w - x; int16_t e = w - x;
mLog["e"] = e; _log.addProperty("e", e);
// Keine Regelung innerhalb der Toleranzgrenzen // Keine Regelung innerhalb der Toleranzgrenzen
if ((e < CfgGroup->powerTolerance) && (e > -CfgGroup->powerTolerance)) { if ((e < CfgGroup->powerTolerance) && (e > -CfgGroup->powerTolerance)) {
e = 0; e = 0;
mLog["eK"] = e; _log.addProperty("eK", e);
sendLog(); sendLog();
clearLog(); clearLog();
return; return;
@ -192,7 +204,7 @@ class ZeroExport {
CfgGroup->eSum += e; CfgGroup->eSum += e;
int16_t yI = Ki * Ta * CfgGroup->eSum; int16_t yI = Ki * Ta * CfgGroup->eSum;
if (Ta == 0) { if (Ta == 0) {
mLog["Error"] = "Ta = 0"; _log.addProperty("Error", "Ta = 0");
sendLog(); sendLog();
clearLog(); clearLog();
return; return;
@ -200,21 +212,21 @@ class ZeroExport {
int16_t yD = Kd * (e - CfgGroup->eOld) / Ta; int16_t yD = Kd * (e - CfgGroup->eOld) / Ta;
if (mCfg->debug) { if (mCfg->debug) {
mLog["Kp"] = Kp; _log.addProperty("Kp", Kp);
mLog["Ki"] = Ki; _log.addProperty("Ki", Ki);
mLog["Kd"] = Kd; _log.addProperty("Kd", Kd);
mLog["Ta"] = Ta; _log.addProperty("Ta", Ta);
mLog["yP"] = yP; _log.addProperty("yP", yP);
mLog["yI"] = yI; _log.addProperty("yI", yI);
mLog["eSum"] = CfgGroup->eSum; _log.addProperty("eSum", CfgGroup->eSum);
mLog["yD"] = yD; _log.addProperty("yD", yD);
mLog["eOld"] = CfgGroup->eOld; _log.addProperty("eOld", CfgGroup->eOld);
} }
CfgGroup->eOld = e; CfgGroup->eOld = e;
int16_t y = yP + yI + yD; int16_t y = yP + yI + yD;
mLog["y"] = y; _log.addProperty("y", y);
// Regelbegrenzung // Regelbegrenzung
// TODO: Hier könnte man den maximalen Sprung begrenzen // TODO: Hier könnte man den maximalen Sprung begrenzen
@ -230,7 +242,7 @@ class ZeroExport {
if (CfgGroupInv->actionTimer == 0) CfgGroupInv->actionTimer = 1; if (CfgGroupInv->actionTimer == 0) CfgGroupInv->actionTimer = 1;
if (CfgGroupInv->actionTimer > 10) { if (CfgGroupInv->actionTimer > 10) {
CfgGroupInv->action = zeroExportAction_t::doTurnOn; CfgGroupInv->action = zeroExportAction_t::doTurnOn;
mLog["do"] = "doTurnOn"; _log.addProperty("do", "doTurnOn");
} }
} }
if ((CfgGroupInv->turnOff) && (CfgGroupInv->limitNew <= 0) && (CfgGroupInv->power > 0)) { if ((CfgGroupInv->turnOff) && (CfgGroupInv->limitNew <= 0) && (CfgGroupInv->power > 0)) {
@ -238,26 +250,28 @@ class ZeroExport {
if (CfgGroupInv->actionTimer == 0) CfgGroupInv->actionTimer = -1; if (CfgGroupInv->actionTimer == 0) CfgGroupInv->actionTimer = -1;
if (CfgGroupInv->actionTimer < 30) { if (CfgGroupInv->actionTimer < 30) {
CfgGroupInv->action = zeroExportAction_t::doTurnOff; CfgGroupInv->action = zeroExportAction_t::doTurnOff;
mLog["do"] = "doTurnOff"; _log.addProperty("do", "doTurnOff");
} }
} }
if (((CfgGroup->battSwitch == false) || (mCfg->sleep == true) || (CfgGroup->sleep == true)) && (CfgGroupInv->power > 0)) { if (((CfgGroup->battSwitch == false) || (mCfg->sleep == true) || (CfgGroup->sleep == true)) && (CfgGroupInv->power > 0)) {
CfgGroupInv->action = zeroExportAction_t::doTurnOff; CfgGroupInv->action = zeroExportAction_t::doTurnOff;
mLog["do"] = "sleep"; _log.addProperty("do", "sleep");
} }
} }
mLog["doT"] = CfgGroupInv->action; _log.addProperty("doT", CfgGroupInv->action);
if (CfgGroupInv->action == zeroExportAction_t::doNone) { if (CfgGroupInv->action == zeroExportAction_t::doNone) {
mLog["l"] = CfgGroupInv->limit; _log.addProperty("l", CfgGroupInv->limit);
mLog["ln"] = CfgGroupInv->limitNew; _log.addProperty("ln", CfgGroupInv->limitNew);
// groupMax // groupMax
uint16_t otherIvLimit = groupLimit - CfgGroupInv->limit; uint16_t otherIvLimit = groupLimit - CfgGroupInv->limit;
if ((otherIvLimit + CfgGroupInv->limitNew) > CfgGroup->powerMax) { if ((otherIvLimit + CfgGroupInv->limitNew) > CfgGroup->powerMax) {
CfgGroupInv->limitNew = CfgGroup->powerMax - otherIvLimit; CfgGroupInv->limitNew = CfgGroup->powerMax - otherIvLimit;
} }
if (mCfg->debug) mLog["gPM"] = CfgGroup->powerMax; if (mCfg->debug) {
_log.addProperty("gPM", CfgGroup->powerMax);
}
// PowerMax // PowerMax
uint16_t powerMax = 100; uint16_t powerMax = 100;
@ -291,13 +305,13 @@ class ZeroExport {
// CfgGroupInv->actionTimer = 0; // CfgGroupInv->actionTimer = 0;
// TODO: Timer stoppen wenn Limit gesetzt wird. // TODO: Timer stoppen wenn Limit gesetzt wird.
mLog["lN"] = CfgGroupInv->limitNew; _log.addProperty("lN", CfgGroupInv->limitNew);
CfgGroupInv->limit = CfgGroupInv->limitNew; CfgGroupInv->limit = CfgGroupInv->limitNew;
} }
// doAction // doAction
mLog["a"] = CfgGroupInv->action; _log.addProperty("a", CfgGroupInv->action);
switch (CfgGroupInv->action) { switch (CfgGroupInv->action) {
case zeroExportAction_t::doRestart: case zeroExportAction_t::doRestart:
@ -422,14 +436,20 @@ class ZeroExport {
if (!mCfg->groups[group].inverters[inv].enabled) continue; if (!mCfg->groups[group].inverters[inv].enabled) continue;
if (iv->id == (uint8_t)mCfg->groups[group].inverters[inv].id) { if (iv->id == (uint8_t)mCfg->groups[group].inverters[inv].id) {
mLog["g"] = group; _log.addProperty("g", group);
mLog["i"] = inv; _log.addProperty("i", inv);
mCfg->groups[group].inverters[inv].waitAck = 0; mCfg->groups[group].inverters[inv].waitAck = 0;
mLog["wA"] = mCfg->groups[group].inverters[inv].waitAck;
_log.addProperty("wA", mCfg->groups[group].inverters[inv].waitAck);
// Wenn ein neuer LimitWert da ist. Soll es in group schreiben.
if (iv->actPowerLimit != 0xffff) { if (iv->actPowerLimit != 0xffff) {
mLog["l"] = mCfg->groups[group].inverters[inv].limit; _log.addProperty("l", mCfg->groups[group].inverters[inv].limit);
mCfg->groups[group].inverters[inv].limit = iv->actPowerLimit; mCfg->groups[group].inverters[inv].limit = iv->actPowerLimit;
mLog["lF"] = mCfg->groups[group].inverters[inv].limit;
_log.addProperty("lF", mCfg->groups[group].inverters[inv].limit);
} }
sendLog(); sendLog();
clearLog(); clearLog();
@ -453,10 +473,12 @@ class ZeroExport {
if (!mCfg->groups[group].inverters[inv].enabled) continue; if (!mCfg->groups[group].inverters[inv].enabled) continue;
if (iv->id == mCfg->groups[group].inverters[inv].id) { if (iv->id == mCfg->groups[group].inverters[inv].id) {
mLog["g"] = group; _log.addProperty("g", group);
mLog["i"] = inv; _log.addProperty("i", inv);
mCfg->groups[group].inverters[inv].waitAck = 0; mCfg->groups[group].inverters[inv].waitAck = 0;
mLog["wA"] = mCfg->groups[group].inverters[inv].waitAck;
_log.addProperty("wA", mCfg->groups[group].inverters[inv].waitAck);
sendLog(); sendLog();
clearLog(); clearLog();
} }
@ -479,10 +501,12 @@ class ZeroExport {
if (!mCfg->groups[group].inverters[inv].enabled) continue; if (!mCfg->groups[group].inverters[inv].enabled) continue;
if (iv->id == mCfg->groups[group].inverters[inv].id) { if (iv->id == mCfg->groups[group].inverters[inv].id) {
mLog["g"] = group; _log.addProperty("g", group);
mLog["i"] = inv; _log.addProperty("i", inv);
mCfg->groups[group].inverters[inv].waitAck = 0; mCfg->groups[group].inverters[inv].waitAck = 0;
mLog["wA"] = mCfg->groups[group].inverters[inv].waitAck;
_log.addProperty("wA", mCfg->groups[group].inverters[inv].waitAck);
mCfg->groups[group].inverters[inv].limit = mCfg->groups[group].inverters[inv].powerMin; mCfg->groups[group].inverters[inv].limit = mCfg->groups[group].inverters[inv].powerMin;
iv->powerLimit[0] = static_cast<uint16_t>(mCfg->groups[group].inverters[inv].limit * 10.0); iv->powerLimit[0] = static_cast<uint16_t>(mCfg->groups[group].inverters[inv].limit * 10.0);
@ -518,8 +542,8 @@ class ZeroExport {
if (!CfgGroupInv->enabled) continue; if (!CfgGroupInv->enabled) continue;
if (CfgGroupInv->id != iv->id) continue; if (CfgGroupInv->id != iv->id) continue;
mLog["g"] = group; _log.addProperty("g", group);
mLog["i"] = inv; _log.addProperty("i", inv);
// TODO: Ist nach eventAckSetLimit verschoben // TODO: Ist nach eventAckSetLimit verschoben
// if (iv->actPowerLimit != 0xffff) { // if (iv->actPowerLimit != 0xffff) {
@ -531,17 +555,19 @@ class ZeroExport {
// TODO: Es dauert bis getMaxPower übertragen wird. // TODO: Es dauert bis getMaxPower übertragen wird.
if (iv->getMaxPower() > 0) { if (iv->getMaxPower() > 0) {
CfgGroupInv->MaxPower = iv->getMaxPower(); CfgGroupInv->MaxPower = iv->getMaxPower();
mLog["pM"] = CfgGroupInv->MaxPower;
_log.addProperty("pM", CfgGroupInv->MaxPower);
} }
record_t<> *rec; record_t<> *rec;
rec = iv->getRecordStruct(RealTimeRunData_Debug); rec = iv->getRecordStruct(RealTimeRunData_Debug);
if (iv->getLastTs(rec) > (millis() - 15000)) { if (iv->getLastTs(rec) > (millis() - 15000)) {
CfgGroupInv->power = iv->getChannelFieldValue(CH0, FLD_PAC, rec); CfgGroupInv->power = iv->getChannelFieldValue(CH0, FLD_PAC, rec);
mLog["p"] = CfgGroupInv->power;
_log.addProperty("pM", CfgGroupInv->MaxPower);
CfgGroupInv->dcVoltage = iv->getChannelFieldValue(CH1, FLD_UDC, rec); CfgGroupInv->dcVoltage = iv->getChannelFieldValue(CH1, FLD_UDC, rec);
mLog["bU"] = ah::round1(CfgGroupInv->dcVoltage); _log.addProperty("bU", ah::round1(CfgGroupInv->dcVoltage));
// Batterieüberwachung - Überwachung über die DC-Spannung am PV-Eingang 1 des Inverters // Batterieüberwachung - Überwachung über die DC-Spannung am PV-Eingang 1 des Inverters
if (CfgGroup->battCfg == zeroExportBatteryCfg::invUdc) { if (CfgGroup->battCfg == zeroExportBatteryCfg::invUdc) {
@ -561,16 +587,16 @@ class ZeroExport {
uint16_t powerPercent = 100 / CfgGroupInv->MaxPower * CfgGroupInv->power; uint16_t powerPercent = 100 / CfgGroupInv->MaxPower * CfgGroupInv->power;
uint16_t delta = abs(limitPercent - powerPercent); uint16_t delta = abs(limitPercent - powerPercent);
if ((delta > 10) && (CfgGroupInv->power > 0)) { if ((delta > 10) && (CfgGroupInv->power > 0)) {
mLog["delta"] = delta; _log.addProperty("delta", delta);
unsigned long delay = iv->getLastTs(rec) - CfgGroupInv->actionTimestamp; unsigned long delay = iv->getLastTs(rec) - CfgGroupInv->actionTimestamp;
mLog["delay"] = delay; _log.addProperty("delay", delay);
if (delay > 30000) { if (delay > 30000) {
CfgGroupInv->action = zeroExportAction_t::doActivePowerContr; CfgGroupInv->action = zeroExportAction_t::doActivePowerContr;
mLog["do"] = "doActivePowerContr"; _log.addProperty("do", "doActivePowerContr");
} }
if (delay > 60000) { if (delay > 60000) {
CfgGroupInv->action = zeroExportAction_t::doRestart; CfgGroupInv->action = zeroExportAction_t::doRestart;
mLog["do"] = "doRestart"; _log.addProperty("do", "doRestart");
} }
} }
} }
@ -584,7 +610,6 @@ class ZeroExport {
sendLog(); sendLog();
clearLog(); clearLog();
return; return;
} }
} }
@ -634,40 +659,59 @@ class ZeroExport {
if (strcmp(mCfg->groups[group].battTopic, String(topic).c_str())) { if (strcmp(mCfg->groups[group].battTopic, String(topic).c_str())) {
mCfg->groups[group].battValue = (bool)obj["val"]; mCfg->groups[group].battValue = (bool)obj["val"];
mLog["k"] = mCfg->groups[group].battTopic;
mLog["v"] = mCfg->groups[group].battValue;
_log.addProperty("k", mCfg->groups[group].battTopic);
_log.addProperty("v", mCfg->groups[group].battValue);
} }
} }
// "topic":"ctrl/zero" // "topic":"ctrl/zero"
if (topic.indexOf("ctrl/zero") == -1) return; if (topic.indexOf("ctrl/zero") == -1) return;
if (mCfg->debug) mLog["d"] = obj;
_log.addProperty("d", obj);
if (obj["path"] == "ctrl" && obj["cmd"] == "zero") { if (obj["path"] == "ctrl" && obj["cmd"] == "zero") {
int8_t topicGroup = getGroupFromTopic(topic.c_str()); int8_t topicGroup = getGroupFromTopic(topic.c_str());
int8_t topicInverter = getInverterFromTopic(topic.c_str()); int8_t topicInverter = getInverterFromTopic(topic.c_str());
if (topicGroup != -1) mLog["g"] = topicGroup; if (topicGroup != -1) {
if (topicInverter == -1) mLog["i"] = topicInverter; _log.addProperty("g", topicGroup);
}
if (topicInverter == -1) {
_log.addProperty("i", topicInverter);
}
mLog["k"] = topic; _log.addProperty("k", topic);
// "topic":"ctrl/zero/enabled" // "topic":"ctrl/zero/enabled"
if (topic.indexOf("ctrl/zero/enabled") != -1) mCfg->enabled = mLog["v"] = (bool)obj["val"]; if (topic.indexOf("ctrl/zero/enabled") != -1) {
_log.addProperty("v", (bool)obj["val"]);
mCfg->enabled = (bool)obj["val"];
}
// "topic":"ctrl/zero/sleep" // "topic":"ctrl/zero/sleep"
else if (topic.indexOf("ctrl/zero/sleep") != -1) mCfg->sleep = mLog["v"] = (bool)obj["val"]; else if (topic.indexOf("ctrl/zero/sleep") != -1) {
_log.addProperty("v", (bool)obj["val"]);
mCfg->sleep = (bool)obj["val"];
}
else if ((topicGroup >= 0) && (topicGroup < ZEROEXPORT_MAX_GROUPS)) else if ((topicGroup >= 0) && (topicGroup < ZEROEXPORT_MAX_GROUPS))
{ {
String stopicGroup = String(topicGroup); String stopicGroup = String(topicGroup);
// "topic":"ctrl/zero/groups/+/enabled" // "topic":"ctrl/zero/groups/+/enabled"
if (topic.endsWith("/enabled")) mCfg->groups[topicGroup].enabled = mLog["v"] = (bool)obj["val"]; if (topic.endsWith("/enabled")) {
_log.addProperty("v", (bool)obj["val"]);
mCfg->groups[topicGroup].enabled = (bool)obj["val"];
}
// "topic":"ctrl/zero/groups/+/sleep" // "topic":"ctrl/zero/groups/+/sleep"
else if (topic.endsWith("/sleep")) mCfg->groups[topicGroup].sleep = mLog["v"] = (bool)obj["val"]; else if (topic.endsWith("/sleep")) {
_log.addProperty("v", (bool)obj["val"]);
mCfg->groups[topicGroup].sleep = (bool)obj["val"];
}
// Auf Eis gelegt, dafür 2 Gruppen mehr // Auf Eis gelegt, dafür 2 Gruppen mehr
// 0.8.103008.2 // 0.8.103008.2
@ -692,36 +736,59 @@ class ZeroExport {
// } // }
// "topic":"ctrl/zero/groups/+/battery/switch" // "topic":"ctrl/zero/groups/+/battery/switch"
else if (topic.endsWith("/battery/switch")) mCfg->groups[topicGroup].battSwitch = mLog["v"] = (bool)obj["val"]; else if (topic.endsWith("/battery/switch")) {
_log.addProperty("v", (bool)obj["val"]);
mCfg->groups[topicGroup].battSwitch = (bool)obj["val"];
}
else if (topic.indexOf("/advanced/") != -1) else if (topic.indexOf("/advanced/") != -1)
{ {
// "topic":"ctrl/zero/groups/+/advanced/setPoint" // "topic":"ctrl/zero/groups/+/advanced/setPoint"
if (topic.endsWith("/setPoint")) mCfg->groups[topicGroup].setPoint = mLog["v"] = (int16_t)obj["val"]; if (topic.endsWith("/setPoint")) {
_log.addProperty("v", (int16_t)obj["val"]);
mCfg->groups[topicGroup].setPoint = (int16_t)obj["val"];
}
// "topic":"ctrl/zero/groups/+/advanced/powerTolerance" // "topic":"ctrl/zero/groups/+/advanced/powerTolerance"
else if (topic.endsWith("/powerTolerance")) mCfg->groups[topicGroup].powerTolerance = mLog["v"] = (uint8_t)obj["val"]; else if (topic.endsWith("/powerTolerance")) {
_log.addProperty("v", (uint8_t)obj["val"]);
mCfg->groups[topicGroup].powerTolerance = (uint8_t)obj["val"];
}
// "topic":"ctrl/zero/groups/+/advanced/powerMax" // "topic":"ctrl/zero/groups/+/advanced/powerMax"
else if (topic.endsWith("/powerMax")) mCfg->groups[topicGroup].powerMax = mLog["v"] = (uint16_t)obj["val"]; else if (topic.endsWith("/powerMax")) {
_log.addProperty("v", (uint16_t)obj["val"]);
mCfg->groups[topicGroup].powerMax = (uint16_t)obj["val"];
}
} }
else if (topic.indexOf("/inverter/") != -1) else if (topic.indexOf("/inverter/") != -1)
{ {
if ((topicInverter >= 0) && (topicInverter < ZEROEXPORT_GROUP_MAX_INVERTERS)) if ((topicInverter >= 0) && (topicInverter < ZEROEXPORT_GROUP_MAX_INVERTERS))
{ {
// "topic":"ctrl/zero/groups/+/inverter/+/enabled" // "topic":"ctrl/zero/groups/+/inverter/+/enabled"
if (topic.endsWith("/enabled")) mCfg->groups[topicGroup].inverters[topicInverter].enabled = mLog["v"] = (bool)obj["val"]; if (topic.endsWith("/enabled")) {
_log.addProperty("v", (bool)obj["val"]);
mCfg->groups[topicGroup].inverters[topicInverter].enabled = (bool)obj["val"];
}
// "topic":"ctrl/zero/groups/+/inverter/+/powerMin" // "topic":"ctrl/zero/groups/+/inverter/+/powerMin"
else if (topic.endsWith("/powerMin")) mCfg->groups[topicGroup].inverters[topicInverter].powerMin = mLog["v"] = (uint16_t)obj["val"]; else if (topic.endsWith("/powerMin")) {
_log.addProperty("v", (uint16_t)obj["val"]);
mCfg->groups[topicGroup].inverters[topicInverter].powerMin = (uint16_t)obj["val"];
}
// "topic":"ctrl/zero/groups/+/inverter/+/powerMax" // "topic":"ctrl/zero/groups/+/inverter/+/powerMax"
else if (topic.endsWith("/powerMax")) mCfg->groups[topicGroup].inverters[topicInverter].powerMax = mLog["v"] = (uint16_t)obj["val"]; else if (topic.endsWith("/powerMax")) {
else mLog["k"] = "error"; _log.addProperty("v", (uint16_t)obj["val"]);
mCfg->groups[topicGroup].inverters[topicInverter].powerMax = (uint16_t)obj["val"];
}
else
{
_log.addProperty("k", "error");
}
} }
} }
else { else {
mLog["k"] = "error"; _log.addProperty("k", "error");
} }
} }
} }
@ -770,7 +837,8 @@ class ZeroExport {
while (*pGroupSection != '/' && digitsCopied < 2) strGroup[digitsCopied++] = *pGroupSection++; while (*pGroupSection != '/' && digitsCopied < 2) strGroup[digitsCopied++] = *pGroupSection++;
strGroup[digitsCopied] = '\0'; strGroup[digitsCopied] = '\0';
int8_t group = atoi(strGroup); int8_t group = atoi(strGroup);
mLog["getGroupFromTopic"] = group;
_log.addProperty("getGroupFromTopic", "group");
return group; return group;
} }
@ -819,7 +887,7 @@ class ZeroExport {
void sendLog(void) { void sendLog(void) {
// Log over Webserial // Log over Webserial
if (mCfg->log_over_webserial) { if (mCfg->log_over_webserial) {
DPRINTLN(DBG_INFO, String("ze: ") + mDocLog.as<String>()); DPRINTLN(DBG_INFO, String("ze: ") + _log.toString());
} }
// Log over MQTT // Log over MQTT
@ -836,12 +904,15 @@ class ZeroExport {
* Löscht den LogSpeicher * Löscht den LogSpeicher
*/ */
void clearLog(void) { void clearLog(void) {
mDocLog.clear(); _log.clear();
} }
// private member variables // private member variables
bool mIsInitialized = false; bool mIsInitialized = false;
// Maximale Größe des JSON-Dokuments
static const size_t max_size = 5000;
IApp *mApp = nullptr; IApp *mApp = nullptr;
uint32_t *mTimestamp = nullptr; uint32_t *mTimestamp = nullptr;
zeroExport_t *mCfg = nullptr; zeroExport_t *mCfg = nullptr;
@ -855,15 +926,12 @@ class ZeroExport {
unsigned long mLastRun = 0; unsigned long mLastRun = 0;
StaticJsonDocument<5000> mDocLog;
JsonObject mLog = mDocLog.to<JsonObject>();
powermeter mPowermeter; powermeter mPowermeter;
PubMqttType *mMqtt = nullptr; PubMqttType *mMqtt = nullptr;
bool mIsSubscribed = false; bool mIsSubscribed = false;
StaticJsonDocument<512> mqttDoc; // DynamicJsonDocument mqttDoc(512);
JsonObject mqttObj = mqttDoc.to<JsonObject>(); DynamicJsonHandler _log;
}; };
#endif /*__ZEROEXPORT__*/ #endif /*__ZEROEXPORT__*/

38
src/utils/DynamicJsonHandler.cpp

@ -0,0 +1,38 @@
#include "DynamicJsonHandler.h"
DynamicJsonHandler::DynamicJsonHandler() : doc(min_size) {
}
DynamicJsonHandler::~DynamicJsonHandler() {
delete &doc;
}
String DynamicJsonHandler::toString() {
String jsonString;
serializeJson(doc, jsonString);
return jsonString;
}
void DynamicJsonHandler::clear() {
doc.clear();
}
size_t DynamicJsonHandler::size() const {
return doc.memoryUsage();
}
void DynamicJsonHandler::resizeDocument(size_t requiredSize) {
// TODO: multiplikator zwei muss ersetzt werden? Kann noch minimal werden.
size_t newCapacity = min(max(requiredSize * 2, min_size), max_size);
DynamicJsonDocument newDoc(newCapacity);
newDoc.set(doc); // Bestehende Daten kopieren
doc = std::move(newDoc);
}
size_t DynamicJsonHandler::min(size_t a, size_t b) {
return (a < b) ? a : b;
}
size_t DynamicJsonHandler::max(size_t a, size_t b) {
return (a > b) ? a : b;
}

44
src/utils/DynamicJsonHandler.h

@ -0,0 +1,44 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#ifndef __DYNAMICJSONHANDLER_H__
#define __DYNAMICJSONHANDLER_H__
#include <Arduino.h>
#include <ArduinoJson.h>
#include <string>
class DynamicJsonHandler {
public:
DynamicJsonHandler();
~DynamicJsonHandler();
template<typename T>
void addProperty(const std::string& key, const T& value);
String toString();
void clear();
size_t size() const;
private:
DynamicJsonDocument doc;
static const size_t min_size = 256;
static const size_t max_size = 5000; // Max RAM : 2 = da es für resizeDocument eng werden könnte?
void resizeDocument(size_t requiredSize);
size_t min(size_t a, size_t b);
size_t max(size_t a, size_t b);
};
template<typename T>
void DynamicJsonHandler::addProperty(const std::string& key, const T& value) {
size_t additionalSize = JSON_OBJECT_SIZE(1) + key.length() + sizeof(value);
if (doc.memoryUsage() + additionalSize > doc.capacity()) {
resizeDocument(doc.memoryUsage() + additionalSize);
}
doc[key] = value;
}
#endif /*__DYNAMICJSONHANDLER_H__*/
Loading…
Cancel
Save