Browse Source

0.8.89

* merge
pull/1475/head
lumapu 7 months ago
parent
commit
1548a00520
  1. 86
      src/app.cpp
  2. 14
      src/app.h
  3. 385
      src/config/settings.h
  4. 554
      src/plugins/zeroExport/zeroExport.h
  5. 103
      src/web/RestApi.h
  6. 443
      src/web/html/setup.html
  7. 185
      src/web/lang.json
  8. 70
      src/web/web.h

86
src/app.cpp

@ -14,7 +14,11 @@
//-----------------------------------------------------------------------------
app::app() : ah::Scheduler {} {
#if defined(PLUGIN_ZEROEXPORT)
memset(mVersion, 0, sizeof(char) * 17);
#else
memset(mVersion, 0, sizeof(char) * 12);
#endif
memset(mVersionModules, 0, sizeof(char) * 12);
}
@ -118,17 +122,21 @@ void app::setup() {
esp_task_wdt_reset();
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
// TODO: aufräumen
// if (mConfig->plugin.zeroExport.enabled) {
mZeroExport.setup(&mConfig->plugin.zeroExport, &mSys, mConfig);
// }
#endif
// Plugin ZeroExport - Ende
#if defined(ENABLE_HISTORY)
mHistory.setup(this, &mSys, mConfig, &mTimestamp);
#endif /*ENABLE_HISTORY*/
mPubSerial.setup(mConfig, &mSys, &mTimestamp);
// ZeroExport
if (mConfig->plugin.zexport.enabled) {
mzExport.setup(&mConfig->plugin.zexport, &mSys, mConfig);
}
#if !defined(ETHERNET)
//mImprov.setup(this, mConfig->sys.deviceName, mVersion);
#endif
@ -164,6 +172,15 @@ void app::loop(void) {
if (mMqttEnabled && mNetworkConnected)
mMqtt.loop();
#endif
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
if(mConfig->nrf.enabled || mConfig->cmt.enabled) {
mZeroExport.loop();
}
#endif
// Plugin ZeroExport - Ende
yield();
}
@ -197,11 +214,14 @@ void app::regularTickers(void) {
everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp");
#endif
// ZeroExport
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
if (mConfig->plugin.zexport.enabled)
everySec(std::bind(&ZeroExportType::tickerSecond, &mzExport), "zExport");
// TODO: aufräumen
// if (mConfig->plugin.zeroExport.enabled) {
everySec(std::bind(&ZeroExportType::tickerSecond, &mZeroExport), "ZeroExport");
// }
#endif
// Plugin ZeroExport - Ende
every(std::bind(&PubSerialType::tick, &mPubSerial), 5, "uart");
#if !defined(ETHERNET)
@ -458,9 +478,15 @@ void app::tickSend(void) {
mCommunication.add(iv, cmd);
});
#if defined(ESP32)
if(mConfig->nrf.enabled || mConfig->cmt.enabled) zeroexport();
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
// TODO: aufräumen
if(mConfig->nrf.enabled || mConfig->cmt.enabled) {
mZeroExport.loop();
// zeroexport();
}
#endif
// Plugin ZeroExport - Ende
}
}
@ -521,7 +547,11 @@ void app:: zeroIvValues(bool checkAvail, bool skipYieldDay) {
//-----------------------------------------------------------------------------
void app::resetSystem(void) {
#if defined(PLUGIN_ZEROEXPORT)
snprintf(mVersion, sizeof(mVersion), "zero-%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
#else
snprintf(mVersion, sizeof(mVersion), "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
#endif
snprintf(mVersionModules, sizeof(mVersionModules), "%s",
#ifdef ENABLE_PROMETHEUS_EP
"P"
@ -635,37 +665,45 @@ void app::updateLed(void) {
}
}
//-----------------------------------------------------------------------------
#if defined(ESP32)
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
void app::zeroexport() {
if (!mConfig->plugin.zexport.enabled ||
!mSys.getInverterByPos(mConfig->plugin.zexport.Iv)->isProducing()) { // check if plugin is enabled && indicate to send new value
mConfig->plugin.zexport.lastTime = millis(); // set last timestamp
return;
// TODO: aufräumen
// TODO: umziehen nach loop
/*
if (!mConfig->plugin.zeroExport.enabled ||
!mSys.getInverterByPos(mConfig->plugin.zeroExport.Iv)->isProducing()) { // check if plugin is enabled && indicate to send new value
mConfig->plugin.zeroExport.lastTime = millis(); // set last timestamp
return;
}
if (millis() - mConfig->plugin.zexport.lastTime > mConfig->plugin.zexport.count_avg * 1000UL)
if (millis() - mConfig->plugin.zeroExport.lastTime > mConfig->plugin.zeroExport.count_avg * 1000UL)
{
Inverter<> *iv = mSys.getInverterByPos(mConfig->plugin.zexport.Iv);
Inverter<> *iv = mSys.getInverterByPos(mConfig->plugin.zeroExport.Iv);
DynamicJsonDocument doc(512);
JsonObject object = doc.to<JsonObject>();
double nValue = round(mzExport.getPowertoSetnewValue());
double nValue = round(mZeroExport.getPowertoSetnewValue());
double twoPerVal = nValue <= (iv->getMaxPower() / 100 * 2 );
if(mConfig->plugin.zexport.two_percent && (nValue <= twoPerVal))
if(mConfig->plugin.zeroExport.two_percent && (nValue <= twoPerVal))
nValue = twoPerVal;
if(mConfig->plugin.zexport.max_power <= nValue)
nValue = mConfig->plugin.zexport.max_power;
if(mConfig->plugin.zeroExport.max_power <= nValue)
nValue = mConfig->plugin.zeroExport.max_power;
if(iv->actPowerLimit == nValue) {
mConfig->plugin.zexport.lastTime = millis(); // set last timestamp
mConfig->plugin.zeroExport.lastTime = millis(); // set last timestamp
return; // if PowerLimit same as befor, then skip
}
object["val"] = nValue;
object["id"] = mConfig->plugin.zexport.Iv;
object["id"] = mConfig->plugin.zeroExport.Iv;
object["path"] = "ctrl";
object["cmd"] = "limit_nonpersistent_absolute";
@ -674,7 +712,9 @@ void app::zeroexport() {
DPRINTLN(DBG_INFO, data);
mApi.ctrlRequest(object);
mConfig->plugin.zexport.lastTime = millis(); // set last timestamp
mConfig->plugin.zeroExport.lastTime = millis(); // set last timestamp
}
*/
}
#endif
// Plugin ZeroExport - Ende

14
src/app.h

@ -77,10 +77,12 @@ typedef Simulator<HmSystemType> SimulatorType;
typedef Display<HmSystemType, Radio> DisplayType;
#endif
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
#include "plugins/zeroExport/zeroExport.h"
typedef ZeroExport<HmSystemType> ZeroExportType;
#endif
// Plugin ZeroExport - Ende
class app : public IApp, public ah::Scheduler {
public:
@ -353,9 +355,11 @@ class app : public IApp, public ah::Scheduler {
void setupLed();
void updateLed();
#if defined(ESP32)
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
void zeroexport();
#endif
// Plugin ZeroExport - Ende
void tickReboot(void) {
DPRINTLN(DBG_INFO, F("Rebooting..."));
@ -423,7 +427,11 @@ class app : public IApp, public ah::Scheduler {
CmtRadio<> mCmtRadio;
#endif
#if defined(PLUGIN_ZEROEXPORT)
char mVersion[17];
#else
char mVersion[12];
#endif
char mVersionModules[12];
settings mSettings;
settings_t *mConfig = nullptr;
@ -460,9 +468,11 @@ class app : public IApp, public ah::Scheduler {
SimulatorType mSimulator;
#endif /*ENABLE_SIMULATOR*/
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
ZeroExportType mzExport;
ZeroExportType mZeroExport;
#endif
// Plugin ZeroExport - Ende
};
#endif /*__APP_H__*/

385
src/config/settings.h

@ -139,24 +139,6 @@ typedef struct {
uint16_t interval;
} cfgMqtt_t;
/* Zero Export section */
#if defined(ESP32)
typedef struct {
char monitor_url[ZEXPORT_ADDR_LEN];
char json_path[ZEXPORT_ADDR_LEN];
uint8_t query_device; // 0 - Tibber, 1 - Shelly, 2 - other (rs232?)
uint8_t Iv; // saves the inverter that is used for regulation
bool enabled;
float power_avg;
uint8_t count_avg;
double total_power;
unsigned long lastTime; // tic toc
double max_power;
bool two_percent; // ask if not go lower then 2%
char tibber_pw[10]; // needed for tibber QWGH-ED12
} cfgzeroExport_t;
#endif
typedef struct {
bool enabled;
char name[MAX_NAME_LENGTH];
@ -203,14 +185,133 @@ typedef struct {
} display_t;
#endif
// Plugin ZeroExport
#define ZEROEXPORT_MAX_GROUPS 6
#define ZEROEXPORT_GROUP_MAX_LEN_NAME 25
#define ZEROEXPORT_GROUP_MAX_LEN_PM_URL 100
#define ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH 100
#define ZEROEXPORT_GROUP_MAX_LEN_PM_USER 25
#define ZEROEXPORT_GROUP_MAX_LEN_PM_PASS 25
#define ZEROEXPORT_GROUP_MAX_INVERTERS 3
#define ZEROEXPORT_POWERMETER_MAX_ERRORS 5
#define ZEROEXPORT_DEF_INV_WAITINGTIME_MS 10000
#if defined(PLUGIN_ZEROEXPORT)
enum class zeroExportState : uint8_t {
RESET, GETPOWERMETER, GETINVERTERPOWER, FINISH
};
typedef enum {
None = 0,
Shelly = 1,
Tasmota = 2,
Mqtt = 3,
Hichi = 4,
Tibber = 5,
} zeroExportPowermeterType_t;
/*
typedef struct {
uint8_t type;
uint8_t ip;
uint8_t url;
bool login;
uint8_t username;
uint8_t password;
uint8_t group;
uint8_t phase;
uint16_t nextRun;
uint16_t interval;
uint8_t error;
uint16_t power;
} zeroExportPowermeter_t;
*/
typedef enum {
Sum = 0,
L1 = 1,
L2 = 2,
L3 = 3,
L1Sum = 4,
L2Sum = 5,
L3Sum = 6,
} zeroExportInverterTarget_t;
typedef struct {
bool enabled;
int8_t id;
int8_t target;
bool twoPercent;
uint16_t powerMax;
float power;
uint16_t limit;
bool limitAck;
float dcVoltage;
// uint16_t waitingTime;
} zeroExportGroupInverter_t;
typedef struct {
// General
bool enabled;
char name[ZEROEXPORT_GROUP_MAX_LEN_NAME];
// Powermeter
uint8_t pm_type;
char pm_url[ZEROEXPORT_GROUP_MAX_LEN_PM_URL];
char pm_jsonPath[ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH];
char pm_user[ZEROEXPORT_GROUP_MAX_LEN_PM_USER];
char pm_pass[ZEROEXPORT_GROUP_MAX_LEN_PM_PASS];
// Inverters
zeroExportGroupInverter_t inverters[ZEROEXPORT_GROUP_MAX_INVERTERS];
// Battery
bool battEnabled;
float battVoltageOn;
float battVoltageOff;
// Advanced
uint8_t refresh;
uint8_t powerTolerance;
uint16_t powerMax;
zeroExportState state;
unsigned long lastRun;
float pmPower;
float pmPowerL1;
float pmPowerL2;
float pmPowerL3;
// uint16_t power; // Aktueller Verbrauch
// uint16_t powerLimitAkt; // Aktuelles Limit
// uint16_t powerHyst; // Hysterese
} zeroExportGroup_t;
typedef struct {
bool enabled;
zeroExportGroup_t groups[ZEROEXPORT_MAX_GROUPS];
// uint8_t query_device; // 0 - Tibber, 1 - Shelly, 2 - other (rs232?)
// char monitor_url[ZEXPORT_ADDR_LEN];
// char json_path[ZEXPORT_ADDR_LEN];
// char tibber_pw[10]; // needed for tibber QWGH-ED12
// uint8_t Iv; // saves the inverter that is used for regulation
// float power_avg;
// uint8_t count_avg;
// double total_power;
// unsigned long lastTime; // tic toc
// double max_power;
// bool two_percent; // ask if not go lower then 2%
} zeroExport_t;
#endif
// Plugin ZeroExport - Ende
typedef struct {
#if defined(PLUGIN_DISPLAY)
display_t display;
#endif
char customLink[MAX_CUSTOM_LINK_LEN];
char customLinkText[MAX_CUSTOM_LINK_TEXT_LEN];
#if defined(ESP32)
cfgzeroExport_t zexport;
#if defined(PLUGIN_ZEROEXPORT)
zeroExport_t zeroExport;
#endif
} plugins_t;
@ -315,7 +416,6 @@ class settings {
if(root.containsKey(F("nrf"))) jsonNrf(root[F("nrf")]);
#if defined(ESP32)
if(root.containsKey(F("cmt"))) jsonCmt(root[F("cmt")]);
if(root.containsKey(F("zeroExport"))) jsonzeroExport(root[F("zeroExport")]);
#endif
if(root.containsKey(F("ntp"))) jsonNtp(root[F("ntp")]);
if(root.containsKey(F("sun"))) jsonSun(root[F("sun")]);
@ -345,7 +445,6 @@ class settings {
jsonNrf(root[F("nrf")].to<JsonObject>(), true);
#if defined(ESP32)
jsonCmt(root[F("cmt")].to<JsonObject>(), true);
jsonzeroExport(root.createNestedObject(F("zeroExport")), true);
#endif
jsonNtp(root[F("ntp")].to<JsonObject>(), true);
jsonSun(root[F("sun")].to<JsonObject>(), true);
@ -473,22 +572,7 @@ class settings {
snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC);
mCfg.mqtt.interval = 0; // off
// Zero-Export
#if defined(ESP32)
snprintf(mCfg.plugin.zexport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT);
snprintf(mCfg.plugin.zexport.tibber_pw, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT);
snprintf(mCfg.plugin.zexport.json_path, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT);
mCfg.plugin.zexport.enabled = false;
mCfg.plugin.zexport.count_avg = 10;
mCfg.plugin.zexport.lastTime = millis(); // do not change!
mCfg.plugin.zexport.query_device = 1; // Standard shelly
mCfg.plugin.zexport.power_avg = 10;
mCfg.plugin.zexport.Iv = 0;
mCfg.plugin.zexport.max_power = 600; // Max 600W to stay safe
mCfg.plugin.zexport.two_percent = true;
#endif
mCfg.inst.sendInterval = SEND_INTERVAL;
mCfg.inst.rstYieldMidNight = false;
mCfg.inst.rstValsNotAvail = false;
mCfg.inst.rstValsCommStop = false;
@ -523,6 +607,77 @@ class settings {
mCfg.plugin.display.disp_dc = DEF_PIN_OFF;
mCfg.plugin.display.pirPin = DEF_PIN_OFF;
#endif
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
mCfg.plugin.zeroExport.enabled = false;
for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
// General
mCfg.plugin.zeroExport.groups[group].enabled = false;
snprintf(mCfg.plugin.zeroExport.groups[group].name, ZEROEXPORT_GROUP_MAX_LEN_NAME, "%s", DEF_ZEXPORT);
// Powermeter
mCfg.plugin.zeroExport.groups[group].pm_type = zeroExportPowermeterType_t::None;
snprintf(mCfg.plugin.zeroExport.groups[group].pm_url, ZEROEXPORT_GROUP_MAX_LEN_PM_URL, "%s", DEF_ZEXPORT);
snprintf(mCfg.plugin.zeroExport.groups[group].pm_jsonPath, ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH, "%s", DEF_ZEXPORT);
snprintf(mCfg.plugin.zeroExport.groups[group].pm_user, ZEROEXPORT_GROUP_MAX_LEN_PM_USER, "%s", DEF_ZEXPORT);
snprintf(mCfg.plugin.zeroExport.groups[group].pm_pass, ZEROEXPORT_GROUP_MAX_LEN_PM_PASS, "%s", DEF_ZEXPORT);
// Inverters
for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
mCfg.plugin.zeroExport.groups[group].inverters[inv].enabled = false;
mCfg.plugin.zeroExport.groups[group].inverters[inv].id = -1;
mCfg.plugin.zeroExport.groups[group].inverters[inv].target = -1;
mCfg.plugin.zeroExport.groups[group].inverters[inv].twoPercent = false;
mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax = 600;
}
// Battery
mCfg.plugin.zeroExport.groups[group].battEnabled = false;
mCfg.plugin.zeroExport.groups[group].battVoltageOn = 0;
mCfg.plugin.zeroExport.groups[group].battVoltageOff = 0;
// Advanced
mCfg.plugin.zeroExport.groups[group].refresh = 10;
mCfg.plugin.zeroExport.groups[group].powerTolerance = 10;
mCfg.plugin.zeroExport.groups[group].powerMax = 600;
//
mCfg.plugin.zeroExport.groups[group].state = zeroExportState::RESET;
mCfg.plugin.zeroExport.groups[group].lastRun = 0;
mCfg.plugin.zeroExport.groups[group].pmPower = 0;
mCfg.plugin.zeroExport.groups[group].pmPowerL1 = 0;
mCfg.plugin.zeroExport.groups[group].pmPowerL2 = 0;
mCfg.plugin.zeroExport.groups[group].pmPowerL3 = 0;
}
// snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT);
// snprintf(mCfg.plugin.zeroExport.tibber_pw, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT);
// snprintf(mCfg.plugin.zeroExport.json_path, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT);
// mCfg.plugin.zeroExport.enabled = false;
// mCfg.plugin.zeroExport.count_avg = 10;
// mCfg.plugin.zeroExport.lastTime = millis(); // do not change!
// mCfg.plugin.zeroExport.query_device = 1; // Standard shelly
// mCfg.plugin.zeroExport.power_avg = 10;
// mCfg.plugin.zeroExport.Iv = 0;
// mCfg.plugin.zeroExport.max_power = 600; // Max 600W to stay safe
// mCfg.plugin.zeroExport.two_percent = true;
// uint8_t ip;
// uint8_t url;
// bool login;
// uint8_t username;
// uint8_t password;
// snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT);
// snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT);
// snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT);
// uint8_t group;
// uint8_t phase;
// mCfg.plugin.zeroExport.groups[group].powermeter.nextRun = 0;
// mCfg.plugin.zeroExport.groups[group].powermeter.interval = 10000;
// mCfg.plugin.zeroExport.groups[group].powermeter.error = 0;
// mCfg.plugin.zeroExport.groups[group].powermeter.power = 0;
// mCfg.plugin.zeroExport.groups[group].inverters[inv].waitingTime = 0;
// mCfg.plugin.zeroExport.groups[group].inverters[inv].limit = -1;
// mCfg.plugin.zeroExport.groups[group].inverters[inv].limitAck = false;
// Plugin ZeroExport - Ende
#endif
}
void loadAddedDefaults() {
@ -737,40 +892,6 @@ class settings {
}
}
#if defined(ESP32)
void jsonzeroExport(JsonObject obj, bool set = false) {
if(set) {
obj[F("en_zeroexport")] = (bool) mCfg.plugin.zexport.enabled;
obj[F("monitor_url")] = mCfg.plugin.zexport.monitor_url;
obj[F("json_path")] = mCfg.plugin.zexport.json_path;
obj[F("Iv")] = mCfg.plugin.zexport.Iv;
obj[F("power_avg")] = mCfg.plugin.zexport.power_avg;
obj[F("query_device")] = mCfg.plugin.zexport.query_device;
obj[F("count_avg")] = mCfg.plugin.zexport.count_avg;
obj[F("max_power")] = mCfg.plugin.zexport.max_power;
obj[F("total_power")] = mCfg.plugin.zexport.total_power;
obj[F("two_percent")] = (bool)mCfg.plugin.zexport.two_percent;
}
else
{
getVal<bool>(obj, F("en_zeroexport"), &mCfg.plugin.zexport.enabled);
getVal<bool>(obj, F("two_percent"), &mCfg.plugin.zexport.two_percent);
getChar(obj, F("monitor_url"), mCfg.plugin.zexport.monitor_url, ZEXPORT_ADDR_LEN);
getChar(obj, F("json_path"), mCfg.plugin.zexport.json_path, ZEXPORT_ADDR_LEN);
getVal<uint8_t>(obj, F("Iv"), &mCfg.plugin.zexport.Iv);
getVal<uint8_t>(obj, F("count_avg"), &mCfg.plugin.zexport.count_avg);
getVal<double>(obj, F("max_power"), &mCfg.plugin.zexport.max_power);
getVal<float>(obj, F("power_avg"), &mCfg.plugin.zexport.power_avg);
getVal<uint8_t>(obj, F("query_device"), &mCfg.plugin.zexport.query_device);
getVal<double>(obj, F("total_power"), &mCfg.plugin.zexport.total_power);
}
}
#endif
void jsonLed(JsonObject obj, bool set = false) {
if(set) {
obj[F("0")] = mCfg.led.led[0];
@ -787,6 +908,116 @@ class settings {
}
}
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
void jsonZeroExportGroupInverter(JsonObject obj, uint8_t group, uint8_t inv, bool set = false) {
if(set) {
obj[F("enabled")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].enabled;
obj[F("id")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].id;
obj[F("target")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].target;
obj[F("twoPercent")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].twoPercent;
obj[F("powerMax")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax;
} else {
if (obj.containsKey(F("enabled")))
getVal<bool>(obj, F("enabled"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].enabled);
if (obj.containsKey(F("id")))
getVal<int8_t>(obj, F("id"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].id);
if (obj.containsKey(F("target")))
getVal<int8_t>(obj, F("target"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].target);
if (obj.containsKey(F("twoPercent")))
getVal<bool>(obj, F("twoPercent"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].twoPercent);
if (obj.containsKey(F("powerMax")))
getVal<uint16_t>(obj, F("powerMax"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax);
}
}
void jsonZeroExportGroup(JsonObject obj, uint8_t group, bool set = false) {
if(set) {
// General
obj[F("enabled")] = mCfg.plugin.zeroExport.groups[group].enabled;
obj[F("name")] = mCfg.plugin.zeroExport.groups[group].name;
// Powermeter
obj[F("pm_type")] = mCfg.plugin.zeroExport.groups[group].pm_type;
obj[F("pm_url")] = mCfg.plugin.zeroExport.groups[group].pm_url;
obj[F("pm_jsonPath")] = mCfg.plugin.zeroExport.groups[group].pm_jsonPath;
obj[F("pm_user")] = mCfg.plugin.zeroExport.groups[group].pm_user;
obj[F("pm_pass")] = mCfg.plugin.zeroExport.groups[group].pm_pass;
// Inverters
JsonArray invArr = obj.createNestedArray(F("inverters"));
for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
jsonZeroExportGroupInverter(invArr.createNestedObject(), group, inv, set);
}
// Battery
obj[F("battEnabled")] = mCfg.plugin.zeroExport.groups[group].battEnabled;
obj[F("battVoltageOn")] = mCfg.plugin.zeroExport.groups[group].battVoltageOn;
obj[F("battVoltageOff")] = mCfg.plugin.zeroExport.groups[group].battVoltageOff;
// Advanced
obj[F("refresh")] = mCfg.plugin.zeroExport.groups[group].refresh;
obj[F("powerTolerance")] = mCfg.plugin.zeroExport.groups[group].powerTolerance;
obj[F("powerMax")] = mCfg.plugin.zeroExport.groups[group].powerMax;
} else {
// General
if (obj.containsKey(F("enabled")))
getVal<bool>(obj, F("enabled"), &mCfg.plugin.zeroExport.groups[group].enabled);
if (obj.containsKey(F("name")))
getChar(obj, F("name"), mCfg.plugin.zeroExport.groups[group].name, ZEXPORT_ADDR_LEN);
// Powermeter
if (obj.containsKey(F("pm_type")))
getVal<uint8_t>(obj, F("pm_type"), &mCfg.plugin.zeroExport.groups[group].pm_type);
if (obj.containsKey(F("pm_url")))
getChar(obj, F("pm_url"), mCfg.plugin.zeroExport.groups[group].pm_url, ZEROEXPORT_GROUP_MAX_LEN_PM_URL);
if (obj.containsKey(F("pm_jsonPath")))
getChar(obj, F("pm_jsonPath"), mCfg.plugin.zeroExport.groups[group].pm_jsonPath, ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH);
if (obj.containsKey(F("pm_user")))
getChar(obj, F("pm_user"), mCfg.plugin.zeroExport.groups[group].pm_user, ZEROEXPORT_GROUP_MAX_LEN_PM_USER);
if (obj.containsKey(F("pm_pass")))
getChar(obj, F("pm_pass"), mCfg.plugin.zeroExport.groups[group].pm_pass, ZEROEXPORT_GROUP_MAX_LEN_PM_PASS);
// Inverters
if (obj.containsKey(F("inverters"))) {
for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
jsonZeroExportGroupInverter(obj[F("inverters")][inv], group, inv, set);
}
}
// Battery
if (obj.containsKey(F("battEnabled")))
getVal<bool>(obj, F("battEnabled"), &mCfg.plugin.zeroExport.groups[group].battEnabled);
if (obj.containsKey(F("battVoltageOn")))
getVal<float>(obj, F("battVoltageOn"), &mCfg.plugin.zeroExport.groups[group].battVoltageOn);
if (obj.containsKey(F("battVoltageOff")))
getVal<float>(obj, F("battVoltageOff"), &mCfg.plugin.zeroExport.groups[group].battVoltageOff);
// Advanced
if (obj.containsKey(F("refresh")))
getVal<uint8_t>(obj, F("refresh"), &mCfg.plugin.zeroExport.groups[group].refresh);
if (obj.containsKey(F("powerTolerance")))
getVal<uint8_t>(obj, F("powerTolerance"), &mCfg.plugin.zeroExport.groups[group].powerTolerance);
if (obj.containsKey(F("powerMax")))
getVal<uint16_t>(obj, F("powerMax"), &mCfg.plugin.zeroExport.groups[group].powerMax);
}
}
void jsonZeroExport(JsonObject obj, bool set = false) {
if(set) {
obj[F("enabled")] = mCfg.plugin.zeroExport.enabled;
JsonArray grpArr = obj.createNestedArray(F("groups"));
for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
jsonZeroExportGroup(grpArr.createNestedObject(), group, set);
}
}
else
{
if (obj.containsKey(F("enabled")))
getVal<bool>(obj, F("enabled"), &mCfg.plugin.zeroExport.enabled);
if (obj.containsKey(F("groups"))) {
for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
jsonZeroExportGroup(obj[F("groups")][group], group, set);
}
}
}
}
#endif
// Plugin ZeroExport - Ende
void jsonPlugin(JsonObject obj, bool set = false) {
if(set) {
#if defined(PLUGIN_DISPLAY)
@ -810,6 +1041,11 @@ class settings {
#endif
obj[F("cst_lnk")] = mCfg.plugin.customLink;
obj[F("cst_lnk_txt")] = mCfg.plugin.customLinkText;
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
jsonZeroExport(obj.createNestedObject("zeroExport"), set);
#endif
// Plugin ZeroExport - Ende
} else {
#if defined(PLUGIN_DISPLAY)
JsonObject disp = obj["disp"];
@ -832,6 +1068,11 @@ class settings {
#endif
getChar(obj, F("cst_lnk"), mCfg.plugin.customLink, MAX_CUSTOM_LINK_LEN);
getChar(obj, F("cst_lnk_txt"), mCfg.plugin.customLinkText, MAX_CUSTOM_LINK_TEXT_LEN);
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
jsonZeroExport(obj["zeroExport"], set);
#endif
// Plugin ZeroExport - Ende
}
}

554
src/plugins/zeroExport/zeroExport.h

@ -1,4 +1,4 @@
#if defined(ESP32)
#if defined(PLUGIN_ZEROEXPORT)
#ifndef __ZEROEXPORT__
#define __ZEROEXPORT__
@ -8,29 +8,232 @@
#include "AsyncJson.h"
#include "SML.h"
template <class HMSYSTEM>
class ZeroExport {
// bool enabled; // true
// uint8_t query_device; // 0 - Tibber, 1 - Shelly, 2 - other (rs232?)
// char monitor_url[ZEXPORT_ADDR_LEN]; //
// char json_path[ZEXPORT_ADDR_LEN]; //
// char tibber_pw[10]; //
// uint8_t Iv; // Id gemäß anlegen
// float power_avg; //
// uint8_t count_avg; //
// double total_power; //
// unsigned long lastTime; // tic toc
// double max_power; // 600 W
// bool two_percent; // P >= 2% von max_power
// mCfg // -> siehe oben
// mSys // ->
// mConfig // ->
public:
ZeroExport() { }
ZeroExport() {
mIsInitialized = false;
}
void setup(cfgzeroExport_t *cfg, HMSYSTEM *sys, settings_t *config) {
void setup(zeroExport_t *cfg, HMSYSTEM *sys, settings_t *config) {
mCfg = cfg;
mSys = sys;
mConfig = config;
if (!mCfg->enabled) {
return;
}
mIsInitialized = true;
}
void loop(void) {
if ((!mIsInitialized) || (!mCfg->enabled)) {
return;
}
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
if (!mCfg->groups[group].enabled) {
continue;
}
switch (mCfg->groups[group].state) {
case zeroExportState::RESET:
//DBGPRINT(F("State::RESET: "));
//DBGPRINTLN(String(group));
mCfg->groups[group].lastRun = millis();
// Weiter zum nächsten State
mCfg->groups[group].state = zeroExportState::GETPOWERMETER;
break;
case zeroExportState::GETPOWERMETER:
if ((millis() - mCfg->groups[group].lastRun) > (mCfg->groups[group].refresh * 1000UL)) {
//DBGPRINT(F("State::GETPOWERMETER:"));
//DBGPRINTLN(String(group));
if (getPowermeterWatts(group)) {
// Weiter zum nächsten State
mCfg->groups[group].state = zeroExportState::GETINVERTERPOWER;
} else {
// Wartezeit wenn Keine Daten vom Powermeter
mCfg->groups[group].lastRun = (millis() - (mCfg->groups[group].refresh * 100UL));
}
}
break;
case zeroExportState::GETINVERTERPOWER:
if ((millis() - mCfg->groups[group].lastRun) > (mCfg->groups[group].refresh * 1000UL)) {
//DBGPRINT(F("State::GETINVERTERPOWER:"));
//DBGPRINTLN(String(group));
if (getInverterPowerWatts(group)) {
// Weiter zum nächsten State
mCfg->groups[group].state = zeroExportState::FINISH;
} else {
// Wartezeit wenn Keine Daten vom Powermeter
mCfg->groups[group].lastRun = (millis() - (mCfg->groups[group].refresh * 100UL));
}
}
break;
/*
case 4:
//
mCfg->groups[group].state = zeroExportState::RESET;
break;
case 5:
//
mCfg->groups[group].state = zeroExportState::RESET;
break;
*/
default:
//
//DBGPRINT(F("State::?: "));
//DBGPRINTLN(String(group));
mCfg->groups[group].state = zeroExportState::RESET;
break;
}
/*
if (!mCfg->groups[group].powermeter.error) {
DBGPRINTLN(String("ok verarbeiten."));
}
if (mCfg->groups[group].powermeter.error >= ZEROEXPORT_POWERMETER_MAX_ERRORS) {
DBGPRINTLN(String("nok Notmodus."));
}
*/
/*
// getPowermeterGroup
mCfg->zeGroup[group].PowermeterSum = getPowermeterSum();
// getPowermeterPhase
for (uint8_t phase = 1; phase <= 3; phase++) {
mCfg->zeGroup[group].PowermeterPhase[phase] = getPowermeterPhase();
}
// getInverterPower
for (uint8_t inv = 0; inv < MAX; inv++) {
mCfg->zeGroup[group].InverterpowerGroup = getInverterpowerGroup();
for (uint8_t phase = 1; phase <= 3; phase++) {
mCfg->zeGroup[group].InverterpowerPhase[phase] = getInverterpowerPhase();
}
}
// calcPowerSum
mCfg->zeGroup[group].LimitSumNew = mCfg->zeGroup[group].LimitSumOld + mCfg->zeGroup[group].PowermeterSum;
// calcPowerPhase
for (uint8_t phase = 1; phase <= 3; phase++) {
mCfg->zeGroup[group].LimitPhaseNew[phase] = mCfg->zeGroup[group].LimitPhaseOld[phase] - mCfg->zeGroup[group].PowermeterPhase[phase];
}
// calcPowerInverter
for (uint8_t inv = 0; inv < MAX; inv++) {
}
*/
}
// setPower
for (uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
if (!mCfg->groups[group].enabled) {
continue;
}
/*
// TODO: Inverter sortieren nach Leistung
// -> Aufsteigend bei Leistungserhöhung
// -> Absteigend bei Leistungsreduzierung
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
if (!mCfg->groups[group].inverters[inv].enabled) {
continue;
}
if (mCfg->groups[group].inverters[inv].waitingTime) {
mCfg->groups[group].inverters[inv].waitingTime--;
continue;
}
// Leistung erhöhen
if (mCfg->groups[group].power < mCfg->groups[group].powerLimitAkt - mCfg->groups[group].powerHyst) {
// mCfg->groups[group].powerLimitAkt = mCfg->groups[group].power
mCfg->groups[group].inverters[inv].waitingTime = ZEROEXPORT_DEF_INV_WAITINGTIME_MS;
return;
}
// Leistung reduzieren
if (mCfg->groups[group].power > mCfg->groups[group].powerLimitAkt + mCfg->groups[group].powerHyst) {
mCfg->groups[group].inverters[inv].waitingTime = ZEROEXPORT_DEF_INV_WAITINGTIME_MS;
return;
}
if ((Power < PowerLimit - Hyst) || (Power > PowerLimit + Hyst)) {
if (Limit < 2%) {
setPower(Off);
setPowerLimit(100%)
} else {
setPower(On);
setPowerLimit(Limit);
mCfg->Inv[inv].waitingTime = ZEROEXPORT_DEF_INV_WAITINGTIME_MS;
}
}
}
*/
}
}
void tickerSecond() {
//DPRINTLN(DBG_INFO, (F("tickerSecond()")));
if (millis() - mCfg->lastTime < mCfg->count_avg * 1000UL) {
zero(); // just refresh when it is needed. To get cpu load low.
if ((!mIsInitialized) || (!mCfg->enabled)) {
return;
}
//DPRINTLN(DBG_INFO, (F("tickerSecond()")));
// if (millis() - mCfg->lastTime < mCfg->count_avg * 1000UL) {
/// zero(); // just refresh when it is needed. To get cpu load low.
// }
}
// Sums up the power values ​​of all phases and returns them.
// If the value is negative, all power values ​​from the inverter are taken into account
double getPowertoSetnewValue()
{
/*
float ivPower = 0;
Inverter<> *iv;
record_t<> *rec;
@ -41,8 +244,9 @@ class ZeroExport {
continue;
ivPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec);
}
return ((unsigned)(mCfg->total_power - mCfg->power_avg) >= mCfg->power_avg) ? ivPower + mCfg->total_power : ivPower - mCfg->total_power;
*/
// return ((unsigned)(mCfg->total_power - mCfg->power_avg) >= mCfg->power_avg) ? ivPower + mCfg->total_power : ivPower - mCfg->total_power;
return 0;
}
//C2T2-B91B
private:
@ -51,6 +255,7 @@ class ZeroExport {
// 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);
@ -103,6 +308,7 @@ class ZeroExport {
return false;
}
httpClient.end();
*/
return true;
}
@ -111,12 +317,342 @@ class ZeroExport {
}
bool getPowermeterWatts(uint8_t group) {
bool ret = false;
DBGPRINT(String("getPowermeterWatts: "));
DBGPRINTLN(String(group));
switch (mCfg->groups[group].pm_type) {
case 1:
ret = getPowermeterWattsShelly(group);
break;
// case 2:
// ret = getPowermeterWattsTasmota(group);
// break;
// case 3:
// ret = getPowermeterWattsMqtt(group);
// break;
// case 4:
// ret = getPowermeterWattsHichi(group);
// break;
// case 5:
// ret = getPowermeterWattsTibber(group);
// break;
/// default:
/// ret = false;
/// break;
}
return ret;
}
int getPowermeterWattsShelly(uint8_t group) {
bool ret = false;
HTTPClient http;
// httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
httpClient.setUserAgent("Ahoy-Agent");
// TODO: Ahoy-0.8.850024-zero
// httpClient.setConnectTimeout(1000);
// httpClient.setTimeout(1000);
// httpClient.addHeader("Content-Type", "application/json");
// httpClient.addHeader("Accept", "application/json");
http.begin(mCfg->groups[group].pm_url);
if (http.GET() == HTTP_CODE_OK)
{
// Parsing
DynamicJsonDocument doc(2048);
DeserializationError error = deserializeJson(doc, http.getString());
if (error)
{
DBGPRINT(String("deserializeJson() failed: "));
DBGPRINTLN(String(error.c_str()));
return ret;
}
// Shelly 3EM
if (doc.containsKey(F("total_power"))) {
mCfg->groups[group].pmPower = doc["total_power"];
ret = true;
// Shelly pro 3EM
} else if (doc.containsKey(F("em:0"))) {
mCfg->groups[group].pmPower = doc["em:0"]["total_act_power"];
ret = true;
// Keine Daten
} else {
mCfg->groups[group].pmPower = 0;
}
// Shelly 3EM
if (doc.containsKey(F("emeters"))) {
mCfg->groups[group].pmPowerL1 = doc["emeters"][0]["power"];
ret = true;
// Shelly pro 3EM
} else if (doc.containsKey(F("em:0"))) {
mCfg->groups[group].pmPowerL1 = doc["em:0"]["a_act_power"];
ret = true;
// Shelly plus1pm plus2pm
} else if (doc.containsKey(F("switch:0"))) {
mCfg->groups[group].pmPowerL1 = doc["switch:0"]["apower"];
mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL1;
ret = true;
// Shelly Alternative
} else if (doc.containsKey(F("apower"))) {
mCfg->groups[group].pmPowerL1 = doc["apower"];
mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL1;
ret = true;
// Keine Daten
} else {
mCfg->groups[group].pmPowerL1 = 0;
}
DBGPRINT(String("pmPowerL1: "));
DBGPRINTLN(String(mCfg->groups[group].pmPowerL1));
// Shelly 3EM
if (doc.containsKey(F("emeters"))) {
mCfg->groups[group].pmPowerL2 = doc["emeters"][1]["power"];
ret = true;
// Shelly pro 3EM
} else if (doc.containsKey(F("em:0"))) {
mCfg->groups[group].pmPowerL2 = doc["em:0"]["b_act_power"];
ret = true;
// Shelly plus1pm plus2pm
} else if (doc.containsKey(F("switch:1"))) {
mCfg->groups[group].pmPowerL2 = doc["switch.1"]["apower"];
mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL2;
ret = true;
// // Shelly Alternative
// } else if (doc.containsKey(F("apower"))) {
// mCfg->groups[group].pmPowerL2 = doc["apower"];
// mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL2;
// ret = true;
// Keine Daten
} else {
mCfg->groups[group].pmPowerL2 = 0;
}
DBGPRINT(String("pmPowerL2: "));
DBGPRINTLN(String(mCfg->groups[group].pmPowerL2));
// Shelly 3EM
if (doc.containsKey(F("emeters"))) {
mCfg->groups[group].pmPowerL3 = doc["emeters"][2]["power"];
ret = true;
// Shelly pro 3EM
} else if (doc.containsKey(F("em:0"))) {
mCfg->groups[group].pmPowerL3 = doc["em:0"]["c_act_power"];
ret = true;
// Shelly plus1pm plus2pm
} else if (doc.containsKey(F("switch:2"))) {
mCfg->groups[group].pmPowerL3 = doc["switch:2"]["apower"];
mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL3;
ret = true;
// // Shelly Alternative
// } else if (doc.containsKey(F("apower"))) {
// mCfg->groups[group].pmPowerL3 = doc["apower"];
// mCfg->groups[group].pmPower += mCfg->groups[group].pmPowerL3;
// ret = true;
// Keine Daten
} else {
mCfg->groups[group].pmPowerL3 = 0;
}
DBGPRINT(String("pmPowerL3: "));
DBGPRINTLN(String(mCfg->groups[group].pmPowerL3));
DBGPRINT(String("pmPower: "));
DBGPRINTLN(String(mCfg->groups[group].pmPower));
}
http.end();
return ret;
}
int getPowermeterWattsTasmota(void) {
/*
HTTPClient http;
char url[100] = "http://";
strcat(url, mCfg->monitor_url);
strcat(url, "/cm?cmd=status%2010");
http.begin(url);
if (http.GET() == 200) {
// Parsing
DynamicJsonDocument doc(384);
DeserializationError error = deserializeJson(doc, http.getString());
if (error) {
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return 0;
}
JsonObject Tasmota_ENERGY = doc["StatusSNS"]["ENERGY"];
int Tasmota_Power = Tasmota_ENERGY["Power"]; // 0
return Tasmota_Power;
*/
/*
String url = "http://" + String(TASMOTA_IP) + "/cm?cmnd=status%2010";
ParsedData = http.get(url).json();
int Watts = ParsedData[TASMOTA_JSON_STATUS][TASMOTA_JSON_PAYLOAD_MQTT_PREFIX][TASMOTA_JSON_POWER_MQTT_LABEL].toInt();
return Watts;
*/
// }
// http.end();
return 0;
}
int getPowermeterWattsMqtt(void) {
// TODO:
return 0;
}
int getPowermeterWattsHichi(void) {
// TODO:
return 0;
}
int getPowermeterWattsTibber(void) {
// TODO:
return 0;
}
bool getInverterPowerWatts(uint8_t group) {
bool ret = false;
DBGPRINT(String("getInverterPowerWatts: "));
DBGPRINTLN(String(group));
for (uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
DBGPRINT(String("iv: "));
DBGPRINT(String(inv));
DBGPRINT(String(" "));
// Wenn Inverter deaktiviert -> Eintrag ignorieren
if (!mCfg->groups[group].inverters[inv].enabled) {
DBGPRINT(String("(ze disabled)."));
continue;
}
// Daten holen
Inverter<> *iv;
record_t<> *rec;
// TODO: getInverterById
for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
iv = mSys->getInverterByPos(i);
// Wenn kein Inverter -> ignorieren
if (iv == NULL) {
continue;
}
// Wenn falscher Inverter -> ignorieren
if (iv->id != (uint8_t)mCfg->groups[group].inverters[inv].id) {
continue;
}
DBGPRINT(String("("));
DBGPRINT(String(iv->id));
DBGPRINT(String("( gefunden)"));
// wenn Inverter deaktiviert -> Daten ignorieren
// if (!iv->enabled()) {
//DBGPRINT(String(" aber deaktiviert "));
// continue;
// }
// wenn Inverter nicht verfügbar -> Daten ignorieren
if (!iv->isAvailable()) {
DBGPRINT(String(" aber nicht erreichbar "));
continue;
}
// wenn Inverter nicht produziert -> Daten ignorieren
// if (!iv->isProducing()) {
//DBGPRINT(String(" aber produziert nix "));
// continue;
// }
// Daten abrufen
rec = iv->getRecordStruct(RealTimeRunData_Debug);
// TODO: gibts hier nen Timestamp? Wenn die Daten nicht aktueller sind als beim letzten Durchlauf dann brauch ich nix machen
mCfg->groups[group].inverters[inv].power = iv->getChannelFieldValue(CH0, FLD_PAC, rec);
DBGPRINT(String("(P="));
DBGPRINT(String(mCfg->groups[group].inverters[inv].power));
DBGPRINT(String("W "));
mCfg->groups[group].inverters[inv].limit = iv->actPowerLimit;
DBGPRINT(String("Li="));
DBGPRINT(String(mCfg->groups[group].inverters[inv].limit));
DBGPRINT(String("% "));
mCfg->groups[group].inverters[inv].limitAck = iv->powerLimitAck;
DBGPRINT(String("Ack= "));
DBGPRINT(String(mCfg->groups[group].inverters[inv].limitAck));
DBGPRINT(String(" "));
mCfg->groups[group].inverters[inv].dcVoltage = iv->getChannelFieldValue(CH1, FLD_UDC, rec);
DBGPRINT(String("U="));
DBGPRINT(String(mCfg->groups[group].inverters[inv].dcVoltage));
DBGPRINT(String("V) "));
// TODO: Eingang muss konfigurierbar sein
ret = true;
}
}
DBGPRINTLN(String(""));
return ret;
}
// private member variables
cfgzeroExport_t *mCfg;
bool mIsInitialized = false;
zeroExport_t *mCfg;
settings_t *mConfig;
HMSYSTEM *mSys;
};
/*
Shelly 1pm
Der Shelly 1pm verfügt über keine eigene Spannungsmessung sondern geht von 220V * Korrekturfaktor aus. Dadurch wird die Leistungsmessung verfälscht und der Shelly ist ungeeignet.
http://192.168.xxx.xxx/status
Shelly 3em (oder em3) :
ok
{"wifi_sta":{"connected":true,"ssid":"Odyssee2001","ip":"192.168.100.85","rssi":-23},"cloud":{"enabled":false,"connected":false},"mqtt":{"connected":true},"time":"17:13","unixtime":1709223219,"serial":27384,"has_update":false,"mac":"3494547B94EE","cfg_changed_cnt":1,"actions_stats":{"skipped":0},"relays":[{"ison":false,"has_timer":false,"timer_started":0,"timer_duration":0,"timer_remaining":0,"overpower":false,"is_valid":true,"source":"input"}],"emeters":[{"power":51.08,"pf":0.27,"current":0.78,"voltage":234.90,"is_valid":true,"total":1686297.2,"total_returned":428958.4},{"power":155.02,"pf":0.98,"current":0.66,"voltage":235.57,"is_valid":true,"total":878905.6,"total_returned":4.1},{"power":6.75,"pf":0.26,"current":0.11,"voltage":234.70,"is_valid":true,"total":206151.1,"total_returned":0.0}],"total_power":212.85,"emeter_n":{"current":0.00,"ixsum":1.29,"mismatch":false,"is_valid":false},"fs_mounted":true,"v_data":1,"ct_calst":0,"update":{"status":"idle","has_update":false,"new_version":"20230913-114244/v1.14.0-gcb84623","old_version":"20230913-114244/v1.14.0-gcb84623","beta_version":"20231107-165007/v1.14.1-rc1-g0617c15"},"ram_total":49920,"ram_free":30192,"fs_size":233681,"fs_free":154616,"uptime":13728721}
Shelly plus 2pm :
ok
{"ble":{},"cloud":{"connected":false},"input:0":{"id":0,"state":false},"input:1":{"id":1,"state":false},"mqtt":{"connected":true},"switch:0":{"id":0, "source":"MQTT", "output":false, "apower":0.0, "voltage":237.0, "freq":50.0, "current":0.000, "pf":0.00, "aenergy":{"total":62758.285,"by_minute":[0.000,0.000,0.000],"minute_ts":1709223337},"temperature":{"tC":35.5, "tF":96.0}},"switch:1":{"id":1, "source":"MQTT", "output":false, "apower":0.0, "voltage":237.1, "freq":50.0, "current":0.000, "pf":0.00, "aenergy":{"total":61917.211,"by_minute":[0.000,0.000,0.000],"minute_ts":1709223337},"temperature":{"tC":35.5, "tF":96.0}},"sys":{"mac":"B0B21C10A478","restart_required":false,"time":"17:15","unixtime":1709223338,"uptime":8746115,"ram_size":245016,"ram_free":141004,"fs_size":458752,"fs_free":135168,"cfg_rev":7,"kvs_rev":0,"schedule_rev":0,"webhook_rev":0,"available_updates":{"stable":{"version":"1.2.2"}}},"wifi":{"sta_ip":"192.168.100.87","status":"got ip","ssid":"Odyssee2001","rssi":-62},"ws":{"connected":false}}
http://192.168.xxx.xxx/rpc/Shelly.GetStatus
Shelly plus 1pm :
nok keine negativen Leistungswerte
{"ble":{},"cloud":{"connected":false},"input:0":{"id":0,"state":false},"mqtt":{"connected":true},"switch:0":{"id":0, "source":"MQTT", "output":false, "apower":0.0, "voltage":235.9, "current":0.000, "aenergy":{"total":20393.619,"by_minute":[0.000,0.000,0.000],"minute_ts":1709223441},"temperature":{"tC":34.6, "tF":94.3}},"sys":{"mac":"FCB467A66E3C","restart_required":false,"time":"17:17","unixtime":1709223443,"uptime":8644483,"ram_size":246256,"ram_free":143544,"fs_size":458752,"fs_free":147456,"cfg_rev":9,"kvs_rev":0,"schedule_rev":0,"webhook_rev":0,"available_updates":{"stable":{"version":"1.2.2"}}},"wifi":{"sta_ip":"192.168.100.88","status":"got ip","ssid":"Odyssee2001","rssi":-42},"ws":{"connected":false}}
*/
#endif /*__ZEROEXPORT__*/
#endif /* #if defined(ESP32) */

103
src/web/RestApi.h

@ -618,22 +618,6 @@ class RestApi {
obj[F("interval")] = String(mConfig->mqtt.interval);
}
#if defined(ESP32)
void getzeroExport(JsonObject obj) {
obj[F("en_zeroexport")] = (bool) mConfig->plugin.zexport.enabled;
obj[F("two_percent")] = (bool) mConfig->plugin.zexport.two_percent;
obj[F("monitor_url")] = String(mConfig->plugin.zexport.monitor_url);
obj[F("json_path")] = String(mConfig->plugin.zexport.json_path);
obj[F("count_avg")] = (uint8_t)mConfig->plugin.zexport.count_avg;
obj[F("max_power")] = (double)mConfig->plugin.zexport.max_power;
obj[F("Iv")] = (uint8_t)mConfig->plugin.zexport.Iv;
obj[F("power_avg")] = (float)mConfig->plugin.zexport.power_avg;
obj[F("query_device")] = (float)mConfig->plugin.zexport.query_device;
obj[F("total_power")] = (double)mConfig->plugin.zexport.total_power;
//obj[F("device")] = (uint8_t)mCfg.plugin.zexport.device;
}
#endif
void getNtp(JsonObject obj) {
obj[F("addr")] = String(mConfig->ntp.addr);
obj[F("port")] = String(mConfig->ntp.port);
@ -730,6 +714,49 @@ class RestApi {
}
#endif
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
void getZeroExport(JsonObject obj) {
obj[F("enabled")] = (bool) mConfig->plugin.zeroExport.enabled;
// Groups
obj[F("max_groups")] = ZEROEXPORT_MAX_GROUPS;
JsonArray arrGroup = obj.createNestedArray(F("groups"));
for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
JsonObject objGroup = arrGroup.createNestedObject();
// General
objGroup[F("id")] = (uint8_t)group;
objGroup[F("enabled")] = (bool)mConfig->plugin.zeroExport.groups[group].enabled;
objGroup[F("name")] = String(mConfig->plugin.zeroExport.groups[group].name);
// Powermeter
objGroup[F("pm_type")] = (uint8_t)mConfig->plugin.zeroExport.groups[group].pm_type;
objGroup[F("pm_url")] = String(mConfig->plugin.zeroExport.groups[group].pm_url);
objGroup[F("pm_jsonPath")] = String(mConfig->plugin.zeroExport.groups[group].pm_jsonPath);
objGroup[F("pm_user")] = String(mConfig->plugin.zeroExport.groups[group].pm_user);
objGroup[F("pm_pass")] = String(mConfig->plugin.zeroExport.groups[group].pm_pass);
// Inverters
objGroup[F("max_inverters")] = ZEROEXPORT_GROUP_MAX_INVERTERS;
JsonArray arrInv = objGroup.createNestedArray(F("inverters"));
for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
JsonObject objGroupInv = arrInv.createNestedObject();
objGroupInv[F("enabled")] = (bool)mConfig->plugin.zeroExport.groups[group].inverters[inv].enabled;
objGroupInv[F("id")] = (int8_t)mConfig->plugin.zeroExport.groups[group].inverters[inv].id;
objGroupInv[F("target")] = (int8_t)mConfig->plugin.zeroExport.groups[group].inverters[inv].target;
objGroupInv[F("twoPercent")] = (bool)mConfig->plugin.zeroExport.groups[group].inverters[inv].twoPercent;
objGroupInv[F("powerMax")] = (uint16_t)mConfig->plugin.zeroExport.groups[group].inverters[inv].powerMax;
}
// Battery
objGroup[F("battEnabled")] = (bool)mConfig->plugin.zeroExport.groups[group].battEnabled;
objGroup[F("battVoltageOn")] = ah::round3((float)mConfig->plugin.zeroExport.groups[group].battVoltageOn);
objGroup[F("battVoltageOff")] = ah::round3((float)mConfig->plugin.zeroExport.groups[group].battVoltageOff);
// Advanced
objGroup[F("refresh")] = (uint8_t)mConfig->plugin.zeroExport.groups[group].refresh;
objGroup[F("powerTolerance")] = (uint8_t)mConfig->plugin.zeroExport.groups[group].powerTolerance;
objGroup[F("powerMax")] = (uint16_t)mConfig->plugin.zeroExport.groups[group].powerMax;
}
}
#endif
// Plugin ZeroExport - Ende
void getMqttInfo(JsonObject obj) {
obj[F("enabled")] = (mConfig->mqtt.broker[0] != '\0');
obj[F("connected")] = mApp->getMqttIsConnected();
@ -798,10 +825,11 @@ class RestApi {
#if defined(PLUGIN_DISPLAY)
getDisplay(obj.createNestedObject(F("display")));
#endif
#if defined(ESP32)
getzeroExport(obj.createNestedObject(F("zeroExport")));
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
getZeroExport(obj.createNestedObject(F("zeroExport")));
#endif
// Plugin ZeroExport - Ende
}
#if !defined(ETHERNET)
@ -955,7 +983,42 @@ class RestApi {
iv->config->powerLevel = jsonIn[F("pa")];
iv->config->disNightCom = jsonIn[F("disnightcom")];
mApp->saveSettings(false); // without reboot
} else {
}
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
else if(F("ze_save_group") == jsonIn[F("cmd")]) {
// General
uint8_t group = jsonIn[F("id")];
mConfig->plugin.zeroExport.groups[group].enabled = jsonIn[F("enabled")];
snprintf(mConfig->plugin.zeroExport.groups[group].name, ZEROEXPORT_GROUP_MAX_LEN_NAME, "%s", jsonIn[F("name")].as<const char*>());
// Powermeter
mConfig->plugin.zeroExport.groups[group].pm_type = jsonIn[F("pm_type")];
snprintf(mConfig->plugin.zeroExport.groups[group].pm_url, ZEROEXPORT_GROUP_MAX_LEN_PM_URL, "%s", jsonIn[F("pm_url")].as<const char*>());
snprintf(mConfig->plugin.zeroExport.groups[group].pm_jsonPath, ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH, "%s", jsonIn[F("pm_jsonPath")].as<const char*>());
snprintf(mConfig->plugin.zeroExport.groups[group].pm_user, ZEROEXPORT_GROUP_MAX_LEN_PM_USER, "%s", jsonIn[F("pm_user")].as<const char*>());
snprintf(mConfig->plugin.zeroExport.groups[group].pm_pass, ZEROEXPORT_GROUP_MAX_LEN_PM_PASS, "%s", jsonIn[F("pm_pass")].as<const char*>());
// Inverters
for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) {
mConfig->plugin.zeroExport.groups[group].inverters[inv].enabled = jsonIn[F("inverters")][inv][F("enabled")];
mConfig->plugin.zeroExport.groups[group].inverters[inv].id = jsonIn[F("inverters")][inv][F("id")];
mConfig->plugin.zeroExport.groups[group].inverters[inv].target = jsonIn[F("inverters")][inv][F("target")];
mConfig->plugin.zeroExport.groups[group].inverters[inv].twoPercent = jsonIn[F("inverters")][inv][F("twoPercent")];
mConfig->plugin.zeroExport.groups[group].inverters[inv].powerMax = jsonIn[F("inverters")][inv][F("powerMax")];
}
// Battery
mConfig->plugin.zeroExport.groups[group].battEnabled = jsonIn[F("battEnabled")];
mConfig->plugin.zeroExport.groups[group].battVoltageOn = jsonIn[F("battVoltageOn")];
mConfig->plugin.zeroExport.groups[group].battVoltageOff = jsonIn[F("battVoltageOff")];
// Advanced
mConfig->plugin.zeroExport.groups[group].refresh = jsonIn[F("refresh")];
mConfig->plugin.zeroExport.groups[group].powerTolerance = jsonIn[F("powerTolerance")];
mConfig->plugin.zeroExport.groups[group].powerMax = jsonIn[F("powerMax")];
// Global
mApp->saveSettings(false); // without reboot
}
#endif
// Plugin ZeroExport - Ende
else {
jsonOut[F("error")] = F("ERR_UNKNOWN_CMD");
return false;
}

443
src/web/html/setup.html

@ -304,50 +304,20 @@
</fieldset>
</div>
<!-- Zero Export -->
<button type="button" class="s_collapsible" id="zeroExport_button">Zero Export</button>
<!-- Plugin ZeroExport -->
<button type="button" class="s_collapsible" id="zeroExport_button">{#ZE}</button>
<div class="s_content" id="zeroExport">
<fieldset class="mb-4">
<legend class="des">Zero Export</legend>
<legend class="des">{#ZE}</legend>
<div id="zeroType"></div>
<div class="row mb-3">
<div class="col-8 col-sm-3">Enable zero export</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="en_zeroexport"/></div>
<p>Please select your favorite query interface:</p>
</div>
<div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Monitor IP: </div>
<input type="radio" id="html" name="dev_Tibber" value="Tibber">
<label for="html">Tibber</label>
<input type="radio" id="css" name="dev_Shelly" value="Shelly">
<label for="css">Shelly</label>
<input type="radio" id="javascript" name="dev_Other" value="Other">
<label for="javascript">Other</label>
<div class="col-12 col-sm-9">
<input type="text" name="monitor_url" maxlength="100">A JSON-Format is required to work properly.<br>
HICHI: http://IP_Address/cm?cmnd=status%208</div>
<div class="col-12 col-sm-3 my-2">Prio Inverter</div>
<div class="col-12 col-sm-9"><select name="iv" id="Inv_ID"></select>Which Inverter should be regulated.</div>
<div class="col-12 col-sm-3 my-2">JSON Path: </div>
<div class="col-12 col-sm-9"><input type="text" name="json_path" maxlength="100">Only for HICHI needed!</div>
<div class="col-8 col-sm-3">2% protection: </div>
<div class="col-4 col-sm-9"><input type="checkbox" name="two_percent"/></div>
<br>
<div class="col-8 col-sm-3">Max Power: </div>
<div class="col-4 col-sm-9"><input type="number" name="max_power" min="8" ></div>
<br>
<div class="col-12 col-sm-3 my-2">Refresh rate (sec.)<input type="number" name="count_avg" min="0" max="255"></div>
<div class="col-12 col-sm-3 my-2">Power tolerances (Watt)<input type="number" name="power_avg" min="0" max="255"></div>
<div class="col-12 col-sm-3 my-2">{#ZE_ENABLED}</div>
<div class="col-12 col-sm-9"><input type="checkbox" name="ze_enabled"/></div>
</div>
<p name="total_power">Total: n/a</p>
<div id="ze_groups"></div>
</fieldset>
</div>
<!-- Plugin ZeroExport - Ende -->
<div class="row mb-4 mt-4">
<div class="col-8 col-sm-3">{#BTN_REBOOT_SUCCESSFUL_SAVE}</div>
@ -1201,54 +1171,383 @@
document.getElementById("date").innerHTML = toIsoDateStr((new Date((++ts) * 1000)));
}
function parsezeroExport(obj, type, ) {
if ("ESP8266" == type) {
var e = document.getElementById("zeroExport");
e.remove();
/*IF_PLUGIN_ZEROEXPORT*/
// Plugin ZeroExport
function ZeroExportGroup_Modal(obj, ivObj) {
var e = document.getElementById("zeroExport_button");
e.textContent += " (only for ESP32 available)";
e.disabled = true;
element.classList.add("disabled");
// Tab_General
var cbEnabled = ml("input", {name: "enabled", type: "checkbox"}, null);
cbEnabled.checked = (obj.enabled);
// Tab_Powermeter
return;
// Tab_Inverter
maxInv = obj["max_inverters"];
var lines = [];
lines.push(ml("tr", {}, [
ml("th", {style: "width: 10%;"}, ml("input", {name: "invMax", id: "invMax", type: "hidden", value: maxInv}, null)),
ml("th", {style: "width: 10%;"}, "{#ZE_GROUP_TAB_INVERTER_ENABLED}"),
ml("th", {}, "{#ZE_GROUP_TAB_INVERTER_NAME}"),
ml("th", {}, "{#ZE_GROUP_TAB_INVERTER_SUM}"),
ml("th", {style: "width: 10%;"}, "{#ZE_GROUP_TAB_INVERTER_TWOPERCENT}"),
ml("th", {style: "width: 15%;"}, "{#ZE_GROUP_TAB_INVERTER_POWERMAX}"),
]));
for(var inv = 0; inv < maxInv; inv++) {
lines.push(ml("tr", {}, [
ml("td", {}, String(inv)),
ml("td", {},
ml("div", {}, [
ml("input", {name: "invEnabled"+inv, class: "text", id: "invEnabled"+inv, type: "checkbox"}, null)
]),
),
ml("td", {},
ml("div", {}, [
ml("select", {name: "invId"+inv, class: "text", id: "invId"+inv}, null),
]),
),
ml("td", {},
ml("div", {}, [
ml("select", {name: "invTarget"+inv, class: "text", id: "invTarget"+inv}, null),
]),
),
ml("td", {},
ml("div", {}, [
ml("input", {name: "invTwoPercent"+inv, class: "text", id: "invTwoPercent"+inv, type: "checkbox"}, null)
]),
),
ml("td", {},
ml("div", {}, [
ml("input", {name: "invPowerMax"+inv, class: "text", id: "invPowerMax"+inv, type: "number", min: "0", max: "65535"}, null)
]),
),
]));
}
document.getElementsByName("en_zeroexport")[0].checked = obj["en_zeroexport"];
document.getElementsByName("two_percent")[0].checked = obj["two_percent"];
// Tab_Battery
var cb_battEnabled = ml("input", {name: "battEnabled", type: "checkbox"}, null);
cb_battEnabled.checked = (obj.battEnabled);
document.getElementsByName("dev_Tibber")[0].checked = (obj["query_device"] == 1);
document.getElementsByName("dev_Shelly")[0].checked = (obj["query_device"] == 2);
document.getElementsByName("dev_Other")[0].checked = (obj["query_device"] == 3);
// Tab_Advanced
// Tab
var html = ml("div", {}, [
tabs(["{#ZE_GROUP_TAB_GENERAL}", "{#ZE_GROUP_TAB_POWERMETER}", "{#ZE_GROUP_TAB_INVERTER}", "{#ZE_GROUP_TAB_BATTERY}", "{#ZE_GROUP_TAB_ADVANCED}"]),
// General
ml("div", {id: "div{#ZE_GROUP_TAB_GENERAL}", class: "tab-content"}, [
divRow("{#ZE_GROUP_TAB_GENERAL_GRUPPE}", String(obj.id)),
divRow("{#ZE_GROUP_TAB_GENERAL_ENABLE}", cbEnabled),
divRow("{#ZE_GROUP_TAB_GENERAL_NAME}", ml("input", {name: "name", class: "text", type: "text", value: obj.name}, null)),
]),
// Powermeter
ml("div", {id: "div{#ZE_GROUP_TAB_POWERMETER}", class: "tab-content hide"}, [
divRow("{#ZE_GROUP_TAB_POWERMETER_TYPE}",
ml("select", {name: "pm_type", class: "text", id: "pm_type"}, null),
),
divRow("{#ZE_GROUP_TAB_POWERMETER_URL}", [
ml("input", {name: "pm_url", class: "text", type: "text", value: obj.pm_url, maxlength: "100"}, null),
// TODO: Hilfstexte -> übersetzen mit lang.json
ml("p", {}, "(3em) - http://IP/status"),
ml("p", {}, "(pro3em) - http://IP/rpc/Shelly.GetStatus"),
ml("p", {}, "(plus1pm) - http://IP/rpc/Shelly.GetStatus"),
ml("p", {}, "(plus2pm) - http://IP/rpc/Shelly.GetStatus"),
ml("p", {}, "(plus1pmAlternative) - http://IP/rpc/switch.GetStatus?id=0"),
ml("p", {}, "(plus2pmAlternative) - http://IP/rpc/switch.GetStatus?id=0"),
// ml("p", {}, "A JSON-Format is required to work properly.<br>HICHI: http://IP_Address/cm?cmnd=status%208"),
]),
divRow("{#ZE_GROUP_TAB_POWERMETER_JSONPATH}", [
ml("input", {name: "pm_jsonPath", class: "text", type: "text", value: obj.pm_jsonPath}, null),
// TODO: Hilfstexte -> übersetzen mit lang.json
// ml("p", {}, "Only for HICHI needed!"),
]),
divRow("{#ZE_GROUP_TAB_POWERMETER_USER}",
ml("input", {name: "pm_user", class: "text", type: "text", value: obj.pm_user}, null),
),
divRow("{#ZE_GROUP_TAB_POWERMETER_PASS}",
ml("input", {name: "pm_pass", class: "text", type: "text", value: obj.pm_pass}, null),
),
]),
// Inverter
ml("div", {id: "div{#ZE_GROUP_TAB_INVERTER}", class: "tab-content hide"}, [
ml("table", {class: "table"}, ml("tbody", {}, lines)),
]),
// Battery
ml("div", {id: "div{#ZE_GROUP_TAB_BATTERY}", class: "tab-content hide"}, [
divRow("{#ZE_GROUP_TAB_BATTERY_BATTENABLED}", cb_battEnabled),
divRow("{#ZE_GROUP_TAB_BATTERY_BATTVOLTAGEON}", ml("input", {name: "battVoltageOn", class: "text", type: "number", min: "0", max: "100", step: "0.1", value: obj.battVoltageOn}, null)),
divRow("{#ZE_GROUP_TAB_BATTERY_BATTVOLTAGEOFF}", ml("input", {name: "battVoltageOff", class: "text", type: "number", min: "0", max: "100", step: "0.1", value: obj.battVoltageOff}, null)),
]),
// Advanced
ml("div", {id: "div{#ZE_GROUP_TAB_ADVANCED}", class: "tab-content hide"}, [
divRow("{#ZE_GROUP_TAB_ADVANCED_REFRESH}", ml("input", {name: "refresh", class: "text", type: "number", min: "0", max: "255", value: obj.refresh}, null)),
divRow("{#ZE_GROUP_TAB_ADVANCED_POWERTOLERANCE}", ml("input", {name: "powerTolerance", class: "text", type: "number", min: "0", max: "255", value: obj.powerTolerance}, null)),
divRow("{#ZE_GROUP_TAB_ADVANCED_POWERMAX}", ml("input", {name: "powerMax", class: "text", type: "number", min: "0", max: "65535", value: obj.powerMax}, null)),
]),
// Global
ml("div", {class: "row mt-5"}, [
ml("div", {class: "col-8", id: "res"}, ""),
ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "{#ZE_GROUP_EDIT_BTN_SAVE}", class: "btn", onclick: function() { save(); }}, null))
])
]);
modal("{#ZE_GROUP_EDIT_MODAL}: " + obj.id, html);
// ser.dispatchEvent(new Event('change'));
getAjax("/api/inverter/list", parseZeroIv);
// Inhalt für pm_type aus config laden und in eine Funktion ausgliedern
var e = document.getElementById("pm_type");
selDelAllOpt(e);
// TODO: übersetzen?
e.appendChild(opt("0", "---"));
e.appendChild(opt("1", "Shelly"));
e.appendChild(opt("2", "Tasmota"));
e.appendChild(opt("3", "Mqtt"));
e.appendChild(opt("4", "Hichi"));
e.appendChild(opt("5", "Tibber"));
for (var i = 0; i < e.options.length; i++) {
if (e.options[i].value == obj.pm_type) {
e.selectedIndex = i;
}
}
// Tab_Inverters
// - Enabled
for (var inv = 0; inv < maxInv; inv++) {
var e = document.getElementById("invEnabled"+inv);
e.checked = (obj.inverters[inv].enabled);
}
// - InverterId
for (var inv = 0; inv < maxInv; inv++) {
var e = document.getElementById("invId"+inv);
selDelAllOpt(e);
e.appendChild(opt("-1", "---"));
for (var i = 0; i < ivObj.inverter.length; i++) {
e.appendChild(opt((ivObj.inverter[i].id), (ivObj.inverter[i].name)));
}
for (var i = 0; i < (e.length); i++) {
if (e.options[i].value == obj.inverters[inv].id) {
e.selectedIndex = i;
}
}
}
// - Target
for (var inv = 0; inv < maxInv; inv++) {
var e = document.getElementById("invTarget"+inv);
selDelAllOpt(e);
// TODO: übersetzen?
e.appendChild(opt("-1", "---"));
e.appendChild(opt("0", "Sum"));
e.appendChild(opt("1", "L1"));
e.appendChild(opt("2", "L2"));
e.appendChild(opt("3", "L3"));
e.appendChild(opt("4", "L1 + Sum"));
e.appendChild(opt("5", "L2 + Sum"));
e.appendChild(opt("6", "L3 + Sum"));
for (var i = 0; i < e.options.length; i++) {
if (e.options[i].value == obj.inverters[inv].target) {
e.selectedIndex = i;
}
}
}
// - twoPercent
for (var inv = 0; inv < maxInv; inv++) {
var e = document.getElementById("invTwoPercent"+inv);
e.checked = (obj.inverters[inv].twoPercent);
}
// - powerMax
for (var inv = 0; inv < maxInv; inv++) {
var e = document.getElementById("invPowerMax"+inv);
e.value = (obj.inverters[inv].powerMax);
}
for(var i of [["monitor_url", "monitor_url"], ["power_avg", "power_avg"], ["count_avg", "count_avg"], ["json_path", "json_path"], ["max_power", "max_power"], ["query_device", "query_device"]])
if(null != obj[i[1]])
document.getElementsByName(i[0])[0].value = obj[i[1]];
function save() {
var o = new Object();
o.cmd = "ze_save_group"
// o.token = "*"
// General
o.id = obj.id
o.enabled = document.getElementsByName("enabled")[0].checked;
o.name = document.getElementsByName("name")[0].value;
// Powermeter
//o.pm_type = document.getElementsByName("pm_type")[0].selectedIndex;
var e = document.getElementsByName("pm_type")[0];
o.pm_type = e.options[e.selectedIndex].value;
o.pm_url = document.getElementsByName("pm_url")[0].value;
o.pm_jsonPath = document.getElementsByName("pm_jsonPath")[0].value;
o.pm_user = document.getElementsByName("pm_user")[0].value;
o.pm_pass = document.getElementsByName("pm_pass")[0].value;
// Inverters
o.invMax = document.getElementById("invMax").value;
o.inverters = [];
for(var inv = 0; inv < o.invMax; inv++) {
var q = new Object();
q.enabled = document.getElementById("invEnabled"+inv).checked;
var e = document.getElementById("invId"+inv);
q.id = e.options[e.selectedIndex].value;
var e = document.getElementById("invTarget"+inv);
q.target = e.options[e.selectedIndex].value;
q.twoPercent = document.getElementById("invTwoPercent"+inv).checked;
q.powerMax = document.getElementById("invPowerMax"+inv).value;
o.inverters.push(q);
}
// Battery
o.battEnabled = document.getElementsByName("battEnabled")[0].checked;
o.battVoltageOn = document.getElementsByName("battVoltageOn")[0].value;
o.battVoltageOff = document.getElementsByName("battVoltageOff")[0].value;
// Advanced
o.refresh = document.getElementsByName("refresh")[0].value;
o.powerTolerance = document.getElementsByName("powerTolerance")[0].value;
o.powerMax = document.getElementsByName("powerMax")[0].value;
// Global
getAjax("/api/setup", cb, "POST", JSON.stringify(o));
}
function cb(obj2) {
var e = document.getElementById("res");
if(!obj2.success)
e.innerHTML = "{#ERROR}" + obj2.error;
else {
modalClose();
getAjax("/api/setup", parse);
}
}
}
function ZeroExportGroup_Del(obj) {
var html = ml("div", {class: "row"}, [
ml("div", {class: "col-9"}, "{#ZE_GROUP_DELETE_SURE} (" + obj.name + ")"),
ml("div", {class: "col-3 a-r"}, ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "{#ZE_GROUP_DELETE_BTN_YES}", class: "btn", onclick: function() { del(); }}, null)))
]);
modal("{#ZE_GROUP_DELETE_MODAL}: " + obj.name, html);
function del() {
// TODO: Es wäre gut, wenn die Defaultwerte nicht hier sondern wie in der settings.h gesetzt würden.
var o = new Object();
o.cmd = "ze_save_group";
// General
o.id = obj.id;
o.enabled = false;
o.name = "";
// Powermeter
o.pm_type = 0;
o.pm_url = "";
o.pm_jsonPath = "";
o.pm_user = "";
o.pm_pass = "";
// Inverters
o.invMax = obj.inverters.length;
o.inverters = [];
for(var inv = 0; inv < o.invMax; inv++) {
var q = new Object();
q.enabled = false;
var e = document.getElementById("invId"+inv);
q.id = -1;
var e = document.getElementById("invTarget"+inv);
q.target = -1;
q.twoPercent = false;
q.powerMax = 0;
o.inverters.push(q);
}
// Battery
o.battEnabled = false;
o.battVoltageOn = 0;
o.battVoltageOff = 0;
// Advanced
o.refresh = 10;
o.powerTolerance = 10;
o.powerMax = 600;
// Global
getAjax("/api/setup", cb, "POST", JSON.stringify(o));
}
function cb(obj) {
if(obj.success) {
modalClose();
getAjax("/api/setup", parse);
}
}
document.getElementsByName("total_power")[0].innerHTML = "Total: " + obj["total_power"].toFixed(2) + "W";
document.getElementById("Inv_ID").selectedIndex = obj["Iv"];
}
function parseZeroIv(root)
{
for(var i = 0; i < root.inverter.length; i++)
root.inverter[i];
// Plugin ZeroExport - Ende
/*ENDIF_PLUGIN_ZEROEXPORT*/
function parseZeroExport(obj, type) {
/*IF_PLUGIN_ZEROEXPORT*/
// Plugin ZeroExport
// enabled
document.getElementsByName("ze_enabled")[0].checked = obj["enabled"];
// groups
maxGroups = obj["max_groups"];
var lines = [];
lines.push(ml("tr", {}, [
ml("th", {style: "width: 10%; text-align: center;"}, "{#ZE_GROUP_ENABLED}"),
ml("th", {style: "width: 10%; text-align: center;"}, "{#ZE_GROUP_ID}"),
ml("th", {style: "text-align: center;"}, "{#ZE_GROUP_NAME}"),
ml("th", {style: "width: 10%; text-align: center;"}, "{#ZE_GROUP_POWERTOTAL}"),
ml("th", {style: "width: 10%; text-align: center;"}, "{#ZE_GROUP_EDIT}"),
ml("th", {style: "width: 10%; text-align: center;"}, "{#ZE_GROUP_DELETE}")
]));
for(let group = 0; group < obj.groups.length; group++) {
lines.push(ml("tr", {}, [
ml("td", {style: "text-align: left;", }, badge(obj.groups[group].enabled, (obj.groups[group].enabled) ? "{#ENABLED}" : "{#DISABLED}")),
ml("td", {style: "text-align: center;", }, String(obj.groups[group].id)),
ml("td", {style: "text-align: left;", }, String(obj.groups[group].name)),
ml("td", {style: "text-align: right;", id: "groupPowerTotal"+group}, "n/a"),
ml("td", {style: "text-align: center;", onclick: function() {
function zeroGetIvList(ivObj) {
ZeroExportGroup_Modal(obj.groups[group], ivObj)
}
getAjax("/api/inverter/list", zeroGetIvList)
}}, svg(iconGear, 25, 25, "icon icon-fg pointer")),
ml("td", {style: "text-align: center;", onclick: function() {ZeroExportGroup_Del(obj.groups[group]);}}, svg(iconDel, 25, 25, "icon icon-fg pointer"))
]));
}
select = document.getElementById('Inv_ID');
parseInt(select.value)
// TODO: Das Add sollte anders / überhaupt gelöst werden
var add = new Object();
add.enabled = true;
add.id = obj.groups.length;
add.name = "";
// add.ch_max_pwr = [400,400,400,400,400,400];
// add.ch_name = [];
// add.ch_yield_cor = [];
// add.freq = 12;
// add.pa = 30;
if(null == root) return;
root = root.inverter;
for(var i = 0; i < root.length; i++) {
inv = root[i];
var opt = document.createElement('option');
opt.value = inv.id;
opt.innerHTML = inv.name;
select.appendChild(opt);
var e = document.getElementById("ze_groups");
e.innerHTML = ""; // remove all childs
e.append(ml("table", {class: "table"}, ml("tbody", {}, lines)));
if(maxGroups > obj.groups.length) {
e.append(ml("div", {class: "row my-3"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "{#BTN_INV_ADD}", class: "btn", onclick: function() { ZeroExportGroup_Modal(add); }}, null))));
}
// ivGlob(obj);
// Plugin ZeroExport - Ende
/*ELIF_PLUGIN_ZEROEXPORT*/
if ("ESP32-S3" != type) {
// TODO: entfernen wenn ELIF funktioniert
var e = document.getElementById("zeroExport");
e.remove();
var e = document.getElementById("zeroExport_button");
e.textContent += " (only for ESP32-S3 available)";
e.disabled = true;
element.classList.add("disabled");
// TODO: übersetzen? / Überflüssig? Das Modul ist so immer sichtbar und zeigt was es braucht.
}
/*ENDIF_PLUGIN_ZEROEXPORT*/
}
function parse(root) {
@ -1265,10 +1564,10 @@
/*IF_ESP32*/
parseCmtRadio(root["radioCmt"], root["system"]["esp_type"], root["system"]);
/*ENDIF_ESP32*/
parsezeroExport(root["zeroExport"], root["system"]["esp_type"]);
parseSerial(root["serial"]);
parseDisplay(root["display"], root["system"]["esp_type"], root["system"]);
parseZeroExport(root["zeroExport"], root["system"]["esp_type"]);
getAjax("/api/inverter/list", parseIv);
}

185
src/web/lang.json

@ -767,6 +767,191 @@
"token": "NO_NETWORK_FOUND",
"en": "no network found",
"de": "kein Netzwerk gefunden"
},
{
"token": "ZE",
"en": "Demand-optimized power control",
"de": "Bedarfsorientierte Leistungsregelung"
},
{
"token": "ZE_ENABLED",
"en": "Enabled",
"de": "Aktiviert"
},
{
"token": "ZE_GROUP_ENABLED",
"en": "State:",
"de": "Status:"
},
{
"token": "ZE_GROUP_ID",
"en": "Group:",
"de": "Gruppe:"
},
{
"token": "ZE_GROUP_NAME",
"en": "Name:",
"de": "Name:"
},
{
"token": "ZE_GROUP_POWERTOTAL",
"en": "PowerTotal (W):",
"de": "Leistung (W):"
},
{
"token": "ZE_GROUP_EDIT",
"en": "Edit",
"de": "Bearbeiten"
},
{
"token": "ZE_GROUP_DELETE",
"en": "Delete",
"de": "L&ouml;schen"
},
{
"token": "ZE_GROUP_EDIT_MODAL",
"en": "Edit group",
"de": "Gruppe bearbeiten"
},
{
"token": "ZE_GROUP_TAB_GENERAL",
"en": "General:",
"de": "Allgemein:"
},
{
"token": "ZE_GROUP_TAB_GENERAL_GRUPPE",
"en": "Group:",
"de": "Gruppe:"
},
{
"token": "ZE_GROUP_TAB_GENERAL_ENABLE",
"en": "Enabled:",
"de": "Aktiviert:"
},
{
"token": "ZE_GROUP_TAB_GENERAL_NAME",
"en": "Name:",
"de": "Name:"
},
{
"token": "ZE_GROUP_TAB_POWERMETER",
"en": "Powermeter",
"de": "Leistungsmessung"
},
{
"token": "ZE_GROUP_TAB_POWERMETER_TYPE",
"en": "Type:",
"de": "Typ:"
},
{
"token": "ZE_GROUP_TAB_POWERMETER_URL",
"en": "Url:",
"de": "Url:"
},
{
"token": "ZE_GROUP_TAB_POWERMETER_JSONPATH",
"en": "JSON Path:",
"de": "JSON Pfad:"
},
{
"token": "ZE_GROUP_TAB_POWERMETER_USER",
"en": "Username:",
"de": "Benutzername:"
},
{
"token": "ZE_GROUP_TAB_POWERMETER_PASS",
"en": "Password:",
"de": "Passwort:"
},
{
"token": "ZE_GROUP_TAB_INVERTER",
"en": "Inverter",
"de": "Wechselrichter"
},
{
"token": "ZE_GROUP_TAB_INVERTER_ENABLED",
"en": "Enabled:",
"de": "Aktiviert:"
},
{
"token": "ZE_GROUP_TAB_INVERTER_NAME",
"en": "Name",
"de": "Name"
},
{
"token": "ZE_GROUP_TAB_INVERTER_SUM",
"en": "Sum",
"de": "Gesamt"
},
{
"token": "ZE_GROUP_TAB_INVERTER_TWOPERCENT",
"en": "2%",
"de": "2%"
},
{
"token": "ZE_GROUP_TAB_INVERTER_POWERMAX",
"en": "Power (Max)",
"de": "Leistung (Max)"
},
{
"token": "ZE_GROUP_TAB_BATTERY",
"en": "Battery",
"de": "Batterie"
},
{
"token": "ZE_GROUP_TAB_BATTERY_BATTENABLED",
"en": "Enabled:",
"de": "Aktiviert:"
},
{
"token": "ZE_GROUP_TAB_BATTERY_BATTVOLTAGEON",
"en": "Voltage on (Volt):",
"de": "Spannung Ein (Volt):"
},
{
"token": "ZE_GROUP_TAB_BATTERY_BATTVOLTAGEOFF",
"en": "Voltage off (Volt):",
"de": "Spannung Aus (Volt):"
},
{
"token": "ZE_GROUP_TAB_ADVANCED",
"en": "Advanced",
"de": "Erweitert"
},
{
"token": "ZE_GROUP_TAB_ADVANCED_REFRESH",
"en": "Refresh rate (sec):",
"de": "Aktualisierung (sec):"
},
{
"token": "ZE_GROUP_TAB_ADVANCED_POWERTOLERANCE",
"en": "Power tolerances (Watt):",
"de": "Leistung Toleranz (Watt)"
},
{
"token": "ZE_GROUP_TAB_ADVANCED_POWERMAX",
"en": "Group Power max (Watt):",
"de": "Gruppe Leistung Max (Watt):"
},
{
"token": "ZE_GROUP_EDIT_BTN_SAVE",
"en": "save",
"de": "speichern"
},
{
"token": "ZE_GROUP_DELETE_MODAL",
"en": "Delete group",
"de": "Gruppe l&ouml;schen "
},
{
"token": "ZE_GROUP_DELETE_SURE",
"en": "do you really want to delete group?",
"de": "Willst du die Gruppe wirklich l&ouml;schen?"
},
{
"token": "ZE_GROUP_DELETE_BTN_YES",
"en": "yes",
"de": "ja"
}
]
},

70
src/web/web.h

@ -549,39 +549,6 @@ class Web {
mConfig->mqtt.port = request->arg("mqttPort").toInt();
mConfig->mqtt.interval = request->arg("mqttInterval").toInt();
// zero-export
#if defined(ESP32)
mConfig->plugin.zexport.enabled = (request->arg("en_zeroexport") == "on");
mConfig->plugin.zexport.two_percent = (request->arg("two_percent") == "on");
mConfig->plugin.zexport.Iv = request->arg("Iv").toInt();
mConfig->plugin.zexport.count_avg = request->arg("count_avg").toInt();
mConfig->plugin.zexport.max_power = request->arg("max_power").toDouble();
mConfig->plugin.zexport.power_avg = request->arg("power_avg").toFloat();
mConfig->plugin.zexport.query_device = request->arg("query_device").toInt();
mConfig->plugin.zexport.total_power = request->arg("total_power").toDouble();
if (request->arg("monitor_url") != "") {
String addr = request->arg("monitor_url");
addr.trim();
addr.toCharArray(mConfig->plugin.zexport.monitor_url, ZEXPORT_ADDR_LEN);
} else
mConfig->plugin.zexport.monitor_url[0] = '\0';
if (request->arg("json_path") != "") {
String addr = request->arg("json_path");
addr.trim();
addr.toCharArray(mConfig->plugin.zexport.json_path, ZEXPORT_ADDR_LEN);
} else
mConfig->plugin.zexport.json_path[0] = '\0';
if (request->arg("tibber_pw") != "") {
String addr = request->arg("tibber_pw");
addr.trim();
addr.toCharArray(mConfig->plugin.zexport.tibber_pw, 10);
} else
mConfig->plugin.zexport.tibber_pw[0] = '\0';
#endif
// serial console
mConfig->serial.debug = (request->arg("serDbg") == "on");
mConfig->serial.privacyLog = (request->arg("priv") == "on");
@ -619,6 +586,43 @@ class Web {
mConfig->plugin.display.pirPin = (mConfig->plugin.display.screenSaver != DISP_TYPE_T2_SH1106_128X64) ? DEF_PIN_OFF : request->arg("pir_pin").toInt(); // pir pin only for motion screensaver
#endif // otherweise default value
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
mConfig->plugin.zeroExport.enabled = (request->arg("ze_enabled") == "on");
// TODO: sortieren
// mConfig->plugin.zeroExport.enabled = (request->arg("en_zeroexport") == "on");
// mConfig->plugin.zeroExport.two_percent = (request->arg("two_percent") == "on");
// mConfig->plugin.zeroExport.Iv = request->arg("Iv").toInt();
// mConfig->plugin.zeroExport.count_avg = request->arg("count_avg").toInt();
// mConfig->plugin.zeroExport.max_power = request->arg("max_power").toDouble();
// mConfig->plugin.zeroExport.power_avg = request->arg("power_avg").toFloat();
// mConfig->plugin.zeroExport.query_device = request->arg("query_device").toInt();
// mConfig->plugin.zeroExport.total_power = request->arg("total_power").toDouble();
/*
if (request->arg("monitor_url") != "") {
String addr = request->arg("monitor_url");
addr.trim();
addr.toCharArray(mConfig->plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN);
} else
mConfig->plugin.zeroExport.monitor_url[0] = '\0';
if (request->arg("json_path") != "") {
String addr = request->arg("json_path");
addr.trim();
addr.toCharArray(mConfig->plugin.zeroExport.json_path, ZEXPORT_ADDR_LEN);
} else
mConfig->plugin.zeroExport.json_path[0] = '\0';
if (request->arg("tibber_pw") != "") {
String addr = request->arg("tibber_pw");
addr.trim();
addr.toCharArray(mConfig->plugin.zeroExport.tibber_pw, 10);
} else
mConfig->plugin.zeroExport.tibber_pw[0] = '\0';
*/
#endif
// Plugin ZeroExport - Ende
mApp->saveSettings((request->arg("reboot") == "on"));
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), save_html, save_html_len);

Loading…
Cancel
Save