Browse Source

0.8.1030003

0.8.1030003
pull/1615/head
tictrick 6 months ago
committed by GitHub
parent
commit
cba0979779
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      src/app.cpp
  2. 40
      src/config/settings.h
  3. 2
      src/defines.h
  4. 293
      src/plugins/zeroExport/powermeter.h
  5. 141
      src/plugins/zeroExport/powermeter.txt
  6. 1293
      src/plugins/zeroExport/zeroExport.h
  7. 3
      src/web/RestApi.h
  8. 17
      src/web/html/setup.html

8
src/app.cpp

@ -74,7 +74,7 @@ void app::setup() {
#if defined(PLUGIN_ZEROEXPORT) || defined(ENABLE_MQTT)
mCommunication.addPowerLimitAckListener([this] (Inverter<> *iv) {
#if defined(PLUGIN_ZEROEXPORT)
mZeroExport.resetWaitLimitAck(iv);
mZeroExport.eventAckSetLimit(iv);
#endif /*PLUGIN_ZEROEXPORT*/
#if defined(ENABLE_MQTT)
mMqtt.setPowerLimitAck(iv);
@ -83,17 +83,17 @@ void app::setup() {
#endif /*defined(PLUGIN_ZEROEXPORT) || defined(ENABLE_MQTT)*/
#if defined(PLUGIN_ZEROEXPORT)
mCommunication.addPowerPowerAckListener([this] (Inverter<> *iv) {
mZeroExport.resetWaitPowerAck(iv);
mZeroExport.eventAckSetPower(iv);
});
#endif /*PLUGIN_ZEROEXPORT*/
#if defined(PLUGIN_ZEROEXPORT)
mCommunication.addPowerRebootAckListener([this] (Inverter<> *iv) {
mZeroExport.resetWaitRebootAck(iv);
mZeroExport.eventAckSetReboot(iv);
});
#endif /*PLUGIN_ZEROEXPORT*/
#if defined(PLUGIN_ZEROEXPORT)
mCommunication.addNewDataListener([this] (Inverter<> *iv) {
mZeroExport.newDataAvailable(iv);
mZeroExport.eventNewDataAvailable(iv);
});
#endif /*PLUGIN_ZEROEXPORT*/

40
src/config/settings.h

@ -203,7 +203,7 @@ typedef struct {
// Plugin ZeroExport
#if defined(PLUGIN_ZEROEXPORT)
//#define ZEROEXPORT_DEV_POWERMETER
#define ZEROEXPORT_DEV_POWERMETER
#define ZEROEXPORT_MAX_GROUPS 6
#define ZEROEXPORT_GROUP_MAX_LEN_NAME 25
#define ZEROEXPORT_GROUP_MAX_LEN_PM_URL 100
@ -214,7 +214,12 @@ typedef struct {
#define ZEROEXPORT_POWERMETER_MAX_ERRORS 5
#define ZEROEXPORT_DEF_INV_WAITINGTIME_MS 10000
#define ZEROEXPORT_GROUP_WR_LIMIT_MIN_DIFF 5
#define ZEROEXPORT_POWERMETER_SHELLY
//#define ZEROEXPORT_POWERMETER_TASMOTA
#define ZEROEXPORT_POWERMETER_MQTT
//#define ZEROEXPORT_POWERMETER_HICHI
#define ZEROEXPORT_POWERMETER_TIBBER
#define ZEROEXPORT_POWERMETER_SHRDZM
enum class zeroExportState : uint8_t {
INIT,
@ -228,7 +233,6 @@ enum class zeroExportState : uint8_t {
SETREBOOT,
SETPOWER,
SETLIMIT,
PUBLISH,
EMERGENCY,
FINISH,
ERROR
@ -241,6 +245,7 @@ typedef enum {
Mqtt = 3,
Hichi = 4,
Tibber = 5,
Shrdzm = 6,
} zeroExportPowermeterType_t;
typedef enum {
@ -264,16 +269,16 @@ typedef struct {
int32_t power;
int32_t limit;
int32_t limitNew;
uint8_t waitLimitAck;
uint8_t waitPowerAck;
uint8_t waitRebootAck;
uint8_t waitAckSetLimit;
uint8_t waitAckSetPower;
uint8_t waitAckSetReboot;
unsigned long limitTsp;
float dcVoltage;
bool state;
//
int8_t doReboot;
int8_t doPower;
bool doLimit;
int8_t doLimit;
float dcVoltage;
} zeroExportGroupInverter_t;
typedef struct {
@ -293,11 +298,14 @@ typedef struct {
bool battEnabled;
float battVoltageOn;
float battVoltageOff;
bool isChangedBattery;
// Advanced
int16_t setPoint;
uint8_t refresh;
int32_t power;
uint8_t powerTolerance;
uint16_t powerMax;
bool isChangedAdvanced;
//
zeroExportState state;
@ -704,17 +712,19 @@ class settings {
mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMin = 10;
mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax = 600;
//
mCfg.plugin.zeroExport.groups[group].inverters[inv].waitLimitAck = false;
mCfg.plugin.zeroExport.groups[group].inverters[inv].waitPowerAck = false;
mCfg.plugin.zeroExport.groups[group].inverters[inv].waitRebootAck = false;
mCfg.plugin.zeroExport.groups[group].inverters[inv].doReboot = false;
mCfg.plugin.zeroExport.groups[group].inverters[inv].waitAckSetLimit = false;
mCfg.plugin.zeroExport.groups[group].inverters[inv].waitAckSetPower = false;
mCfg.plugin.zeroExport.groups[group].inverters[inv].waitAckSetReboot = false;
mCfg.plugin.zeroExport.groups[group].inverters[inv].doReboot = -1;
mCfg.plugin.zeroExport.groups[group].inverters[inv].doPower = -1;
mCfg.plugin.zeroExport.groups[group].inverters[inv].doLimit = false;
mCfg.plugin.zeroExport.groups[group].inverters[inv].doLimit = -1;
mCfg.plugin.zeroExport.groups[group].inverters[inv].dcVoltage = 0;
}
// Battery
mCfg.plugin.zeroExport.groups[group].battEnabled = false;
mCfg.plugin.zeroExport.groups[group].battVoltageOn = 0;
mCfg.plugin.zeroExport.groups[group].battVoltageOff = 0;
mCfg.plugin.zeroExport.groups[group].isChangedBattery = true;
// Advanced
mCfg.plugin.zeroExport.groups[group].setPoint = 0;
mCfg.plugin.zeroExport.groups[group].refresh = 10;
@ -723,17 +733,19 @@ class settings {
mCfg.plugin.zeroExport.groups[group].Kp = -1;
mCfg.plugin.zeroExport.groups[group].Ki = 0;
mCfg.plugin.zeroExport.groups[group].Kd = 0;
mCfg.plugin.zeroExport.groups[group].isChangedAdvanced = true;
//
mCfg.plugin.zeroExport.groups[group].state = zeroExportState::INIT;
mCfg.plugin.zeroExport.groups[group].lastRun = 0;
mCfg.plugin.zeroExport.groups[group].lastRefresh = 0;
mCfg.plugin.zeroExport.groups[group].wait = 0;
mCfg.plugin.zeroExport.groups[group].wait = 60000;
mCfg.plugin.zeroExport.groups[group].pm_P = 0;
mCfg.plugin.zeroExport.groups[group].pm_P1 = 0;
mCfg.plugin.zeroExport.groups[group].pm_P2 = 0;
mCfg.plugin.zeroExport.groups[group].pm_P3 = 0;
mCfg.plugin.zeroExport.groups[group].battSwitch = false;
mCfg.plugin.zeroExport.groups[group].power = 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);

2
src/defines.h

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

293
src/plugins/zeroExport/powermeter.h

@ -7,12 +7,13 @@
#define __POWERMETER_H__
#include <AsyncJson.h>
#include <base64.h>
#include <HTTPClient.h>
#include "config/settings.h"
#if defined(ZEROEXPORT_POWERMETER_TIBBER)
#include <base64.h>
#include <string.h>
#include <list>
#include "config/settings.h"
#include "plugins/zeroExport/lib/sml.h"
typedef struct {
@ -20,6 +21,7 @@ typedef struct {
void (*Fn)(double &);
float *Arg;
} OBISHandler;
#endif
typedef struct {
float P;
@ -36,7 +38,7 @@ class powermeter {
~powermeter() {
}
bool setup(zeroExport_t *cfg, JsonObject *log /*Hier muss noch geklärt werden was gebraucht wird*/) {
bool setup(zeroExport_t *cfg, JsonObject *log /*Hier muss noch geklärt werden was gebraucht wird*/) {
mCfg = cfg;
mLog = log;
return true;
@ -45,35 +47,49 @@ class powermeter {
/** loop
* abfrage der gruppen um die aktuellen Werte (Zähler) zu ermitteln.
*/
void loop(void)
{
unsigned long Tsp = millis();
if(Tsp - mPreviousTsp <= 1000) return; // skip when it is to fast
mPreviousTsp = Tsp;
void loop(unsigned long *tsp, bool *doLog) {
if (*tsp - mPreviousTsp <= 1000) return; // skip when it is to fast
mPreviousTsp = *tsp;
PowermeterBuffer_t power;
for (u_short group = 0; group < ZEROEXPORT_MAX_GROUPS; group++)
{
for (u_short group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) {
switch (mCfg->groups[group].pm_type) {
case zeroExportPowermeterType_t::Shelly:
power = getPowermeterWattsShelly(*mLog, group);
break;
case zeroExportPowermeterType_t::Tasmota:
power = getPowermeterWattsTasmota(*mLog, group);
break;
case zeroExportPowermeterType_t::Mqtt:
power = getPowermeterWattsMqtt(*mLog, group);
break;
case zeroExportPowermeterType_t::Hichi:
power = getPowermeterWattsHichi(*mLog, group);
break;
case zeroExportPowermeterType_t::Tibber:
power = getPowermeterWattsTibber(*mLog, group);
break;
#if defined(ZEROEXPORT_POWERMETER_SHELLY)
case zeroExportPowermeterType_t::Shelly:
power = getPowermeterWattsShelly(*mLog, group);
break;
#endif
#if defined(ZEROEXPORT_POWERMETER_TASMOTA)
case zeroExportPowermeterType_t::Tasmota:
power = getPowermeterWattsTasmota(*mLog, group);
break;
#endif
#if defined(ZEROEXPORT_POWERMETER_MQTT)
case zeroExportPowermeterType_t::Mqtt:
power = getPowermeterWattsMqtt(*mLog, group);
break;
#endif
#if defined(ZEROEXPORT_POWERMETER_HICHI)
case zeroExportPowermeterType_t::Hichi:
power = getPowermeterWattsHichi(*mLog, group);
break;
#endif
#if defined(ZEROEXPORT_POWERMETER_TIBBER)
case zeroExportPowermeterType_t::Tibber:
power = getPowermeterWattsTibber(*mLog, group);
mPreviousTsp += 2000; // Zusätzliche Pause
break;
#endif
#if defined(ZEROEXPORT_POWERMETER_SHRDZM)
case zeroExportPowermeterType_t::Shrdzm:
power = getPowermeterWattsShrdzm(*mLog, group);
break;
#endif
}
bufferWrite(power, group);
if (mCfg->debug) *doLog = true;
}
}
@ -86,8 +102,7 @@ class powermeter {
PowermeterBuffer_t avg;
avg.P = avg.P1 = avg.P2 = avg.P2 = avg.P3 = 0;
for (int i = 0; i < 5; i++)
{
for (int i = 0; i < 5; i++) {
avg.P += mPowermeterBuffer[group][i].P;
avg.P1 += mPowermeterBuffer[group][i].P1;
avg.P2 += mPowermeterBuffer[group][i].P2;
@ -102,6 +117,29 @@ class powermeter {
}
private:
HTTPClient http;
zeroExport_t *mCfg;
JsonObject *mLog;
unsigned long mPreviousTsp = 0;
PowermeterBuffer_t mPowermeterBuffer[ZEROEXPORT_MAX_GROUPS][5] = {0};
short mPowermeterBufferPos[ZEROEXPORT_MAX_GROUPS] = {0};
// set HTTPClient header
void setHeader(HTTPClient* h) {
h->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
h->setUserAgent("Ahoy-Agent");
// TODO: Ahoy-0.8.850024-zero
h->setConnectTimeout(500);
h->setTimeout(1000);
h->addHeader("Content-Type", "application/json");
h->addHeader("Accept", "application/json");
}
#if defined(ZEROEXPORT_POWERMETER_SHELLY)
/** getPowermeterWattsShelly
* ...
* @param logObj
@ -114,14 +152,7 @@ class powermeter {
logObj["mod"] = "getPowermeterWattsShelly";
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.setUserAgent("Ahoy-Agent");
// TODO: Ahoy-0.8.850024-zero
http.setConnectTimeout(500);
http.setTimeout(1000);
http.addHeader("Content-Type", "application/json");
http.addHeader("Accept", "application/json");
setHeader(&http);
String url = String("http://") + String(mCfg->groups[group].pm_url) + String("/") + String(mCfg->groups[group].pm_jsonPath);
logObj["HTTP_URL"] = url;
@ -142,6 +173,9 @@ class powermeter {
} else if (doc.containsKey(F("em:0"))) {
// Shelly pro 3EM
result.P = doc["em:0"]["total_act_power"];
} else if (doc.containsKey(F("total_act_power"))) {
// Shelly pro 3EM
result.P = doc["total_act_power"];
} else {
// Keine Daten
result.P = 0;
@ -153,6 +187,9 @@ class powermeter {
} else if (doc.containsKey(F("em:0"))) {
// Shelly pro 3EM
result.P1 = doc["em:0"]["a_act_power"];
} else if (doc.containsKey(F("a_act_power"))) {
// Shelly pro 3EM
result.P1 = doc["a_act_power"];
} else if (doc.containsKey(F("switch:0"))) {
// Shelly plus1pm plus2pm
result.P1 = doc["switch:0"]["apower"];
@ -172,6 +209,9 @@ class powermeter {
} else if (doc.containsKey(F("em:0"))) {
// Shelly pro 3EM
result.P2 = doc["em:0"]["b_act_power"];
} else if (doc.containsKey(F("b_act_power"))) {
// Shelly pro 3EM
result.P2 = doc["b_act_power"];
} else if (doc.containsKey(F("switch:1"))) {
// Shelly plus1pm plus2pm
result.P2 = doc["switch.1"]["apower"];
@ -192,6 +232,9 @@ class powermeter {
} else if (doc.containsKey(F("em:0"))) {
// Shelly pro 3EM
result.P3 = doc["em:0"]["c_act_power"];
} else if (doc.containsKey(F("c_act_power"))) {
// Shelly pro 3EM
result.P3 = doc["c_act_power"];
} else if (doc.containsKey(F("switch:2"))) {
// Shelly plus1pm plus2pm
result.P3 = doc["switch:2"]["apower"];
@ -210,54 +253,14 @@ class powermeter {
http.end();
return result;
}
#endif
#if defined(ZEROEXPORT_POWERMETER_TASMOTA)
/** getPowermeterWattsTasmota
* ...
* @param logObj
* @param group
* @returns true/false
*
* Vorlage:
* http://IP/cm?cmnd=Status0
* {
* "Status":{
* "Module":1,
* "DeviceName":"Tasmota",
* "FriendlyName":["Tasmota"],
* "Topic":"Tasmota",
* "ButtonTopic":"0",
* "Power":0,
* "PowerOnState":3,
* "LedState":1,
* "LedMask":"FFFF",
* "SaveData":1,
* "SaveState":1,
* "SwitchTopic":"0",
* "SwitchMode":[0,0,0,0,0,0,0,0],
* "ButtonRetain":0,
* "SwitchRetain":0,
* "SensorRetain":0,
* "PowerRetain":0,
* "InfoRetain":0,
* "StateRetain":0},
* "StatusPRM":{"Baudrate":9600,"SerialConfig":"8N1","GroupTopic":"tasmotas","OtaUrl":"http://ota.tasmota.com/tasmota/release/tasmota.bin.gz","RestartReason":"Software/System restart","Uptime":"202T01:24:51","StartupUTC":"2023-08-13T15:21:13","Sleep":50,"CfgHolder":4617,"BootCount":27,"BCResetTime":"2023-02-04T16:45:38","SaveCount":150,"SaveAddress":"F5000"},
* "StatusFWR":{"Version":"11.1.0(tasmota)","BuildDateTime":"2022-05-05T03:23:22","Boot":31,"Core":"2_7_4_9","SDK":"2.2.2-dev(38a443e)","CpuFrequency":80,"Hardware":"ESP8266EX","CR":"378/699"},
* "StatusLOG":{"SerialLog":0,"WebLog":2,"MqttLog":0,"SysLog":0,"LogHost":"","LogPort":514,"SSId":["Odyssee2001",""],"TelePeriod":300,"Resolution":"558180C0","SetOption":["00008009","2805C80001000600003C5A0A190000000000","00000080","00006000","00004000"]},
* "StatusMEM":{"ProgramSize":658,"Free":344,"Heap":17,"ProgramFlashSize":1024,"FlashSize":1024,"FlashChipId":"14325E","FlashFrequency":40,"FlashMode":3,"Features":["00000809","87DAC787","043E8001","000000CF","010013C0","C000F989","00004004","00001000","04000020"],"Drivers":"1,2,3,4,5,6,7,8,9,10,12,16,18,19,20,21,22,24,26,27,29,30,35,37,45,56,62","Sensors":"1,2,3,4,5,6,53"},
* "StatusNET":{"Hostname":"Tasmota","IPAddress":"192.168.100.81","Gateway":"192.168.100.1","Subnetmask":"255.255.255.0","DNSServer1":"192.168.100.1","DNSServer2":"0.0.0.0","Mac":"4C:11:AE:11:F8:50","Webserver":2,"HTTP_API":1,"WifiConfig":4,"WifiPower":17.0},
* "StatusMQT":{"MqttHost":"192.168.100.80","MqttPort":1883,"MqttClientMask":"Tasmota","MqttClient":"Tasmota","MqttUser":"mqttuser","MqttCount":156,"MAX_PACKET_SIZE":1200,"KEEPALIVE":30,"SOCKET_TIMEOUT":4},
* "StatusTIM":{"UTC":"2024-03-02T16:46:04","Local":"2024-03-02T17:46:04","StartDST":"2024-03-31T02:00:00","EndDST":"2024-10-27T03:00:00","Timezone":"+01:00","Sunrise":"07:29","Sunset":"18:35"},
* "StatusSNS":{
* "Time":"2024-03-02T17:46:04",
* "PV":{
* "Bezug":0.364,
* "Einspeisung":3559.439,
* "Leistung":-14
* }
* },
* "StatusSTS":{"Time":"2024-03-02T17:46:04","Uptime":"202T01:24:51","UptimeSec":17457891,"Heap":16,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":19,"MqttCount":156,"POWER":"OFF","Wifi":{"AP":1,"SSId":"Odyssee2001","BSSId":"34:31:C4:22:92:74","Channel":6,"Mode":"11n","RSSI":100,"Signal":-50,"LinkCount":15,"Downtime":"0T00:08:22"}
* }
* }
*/
PowermeterBuffer_t getPowermeterWattsTasmota(JsonObject logObj, uint8_t group) {
PowermeterBuffer_t result;
@ -325,7 +328,9 @@ class powermeter {
*/
return result;
}
#endif
#if defined(ZEROEXPORT_POWERMETER_MQTT)
/** getPowermeterWattsMqtt
* ...
* @param logObj
@ -338,15 +343,15 @@ class powermeter {
logObj["mod"] = "getPowermeterWattsMqtt";
// Hier neuer Code - Anfang
// TODO: Noch nicht komplett
// Hier neuer Code - Ende
// topic for powermeter?
result.P = mCfg->groups[group].pm_P;
result.P1 = result.P2 = result.P3 = mCfg->groups[group].pm_P / 3;
return result;
}
#endif
#if defined(ZEROEXPORT_POWERMETER_HICHI)
/** getPowermeterWattsHichi
* ...
* @param logObj
@ -367,7 +372,9 @@ class powermeter {
return result;
}
#endif
#if defined(ZEROEXPORT_POWERMETER_TIBBER)
/** getPowermeterWattsTibber
* ...
* @param logObj
@ -379,7 +386,6 @@ class powermeter {
sml_states_t currentState;
float _powerMeterTotal = 0.0;
float _powerMeter1Power = 0.0;
@ -389,16 +395,15 @@ class powermeter {
float _powerMeterImport = 0.0;
float _powerMeterExport = 0.0;
/*
07 81 81 c7 82 03 ff #objName: OBIS Kennzahl für den Hersteller
07 01 00 01 08 00 ff #objName: OBIS Kennzahl für Wirkenergie Bezug gesamt tariflos
07 01 00 01 08 01 ff #objName: OBIS-Kennzahl für Wirkenergie Bezug Tarif1
07 01 00 01 08 02 ff #objName: OBIS-Kennzahl für Wirkenergie Bezug Tarif2
07 01 00 02 08 00 ff #objName: OBIS-Kennzahl für Wirkenergie Einspeisung gesamt tariflos
07 01 00 02 08 01 ff #objName: OBIS-Kennzahl für Wirkenergie Einspeisung Tarif1
07 01 00 02 08 02 ff #objName: OBIS-Kennzahl für Wirkenergie Einspeisung Tarif2
*/
/*
07 81 81 c7 82 03 ff #objName: OBIS Kennzahl für den Hersteller
07 01 00 01 08 00 ff #objName: OBIS Kennzahl für Wirkenergie Bezug gesamt tariflos
07 01 00 01 08 01 ff #objName: OBIS-Kennzahl für Wirkenergie Bezug Tarif1
07 01 00 01 08 02 ff #objName: OBIS-Kennzahl für Wirkenergie Bezug Tarif2
07 01 00 02 08 00 ff #objName: OBIS-Kennzahl für Wirkenergie Einspeisung gesamt tariflos
07 01 00 02 08 01 ff #objName: OBIS-Kennzahl für Wirkenergie Einspeisung Tarif1
07 01 00 02 08 02 ff #objName: OBIS-Kennzahl für Wirkenergie Einspeisung Tarif2
*/
const std::list<OBISHandler> smlHandlerList{
{{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_powerMeterTotal}, // total - OBIS-Kennzahl für momentane Gesamtwirkleistung
@ -410,25 +415,28 @@ class powermeter {
{{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterExport}};
PowermeterBuffer_t getPowermeterWattsTibber(JsonObject logObj, uint8_t group) {
mPreviousTsp = mPreviousTsp + 2000; // Zusätzliche Pause
mPreviousTsp = mPreviousTsp + 2000; // Zusätzliche Pause
PowermeterBuffer_t result;
result.P = result.P1 = result.P2 = result.P3 = 0;
logObj["mod"] = "getPowermeterWattsTibber";
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.setUserAgent("Ahoy-Agent");
// TODO: Ahoy-0.8.850024-zero
http.setConnectTimeout(500);
http.setTimeout(1000);
http.addHeader("Content-Type", "application/json");
http.addHeader("Accept", "application/json");
String auth;
if(strlen(mCfg->groups[group].pm_user) > 0 && strlen(mCfg->groups[group].pm_pass) > 0) {
auth = base64::encode(String(mCfg->groups[group].pm_user) + String(":") + String(mCfg->groups[group].pm_pass));
snprintf(mCfg->groups[group].pm_user, ZEROEXPORT_GROUP_MAX_LEN_PM_USER, "%s", DEF_ZEXPORT);
snprintf(mCfg->groups[group].pm_pass, ZEROEXPORT_GROUP_MAX_LEN_PM_PASS, "%s", auth.c_str());
//@TODO:mApp->saveSettings(false);
}
else
{
auth = mCfg->groups[group].pm_pass;
}
String url = String("http://") + mCfg->groups[group].pm_url + String("/") + String(mCfg->groups[group].pm_jsonPath);
String auth = base64::encode(String(mCfg->groups[group].pm_user) + String(":") + String(mCfg->groups[group].pm_pass));
setHeader(&http);
http.begin(url);
http.addHeader("Authorization", "Basic " + auth);
@ -448,7 +456,7 @@ class powermeter {
result.P2 = _powerMeter2Power;
result.P3 = _powerMeter3Power;
if(! (_powerMeter1Power && _powerMeter2Power && _powerMeter3Power)) {
if (!(_powerMeter1Power && _powerMeter2Power && _powerMeter3Power)) {
result.P1 = result.P2 = result.P3 = _powerMeterTotal / 3;
}
break;
@ -468,43 +476,62 @@ class powermeter {
http.end();
return result;
}
#endif
void bufferWrite(PowermeterBuffer_t raw, short group)
{
mPowermeterBuffer[group][mPowermeterBufferPos[group]] = raw;
mPowermeterBufferPos[group]++;
if(mPowermeterBufferPos[group] >= 5) mPowermeterBufferPos[group] = 0;
}
#if defined(ZEROEXPORT_POWERMETER_SHRDZM)
/** getPowermeterWattsShrdzm
* ...
* @param logObj
* @param group
* @returns true/false
* @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?
*/
PowermeterBuffer_t getPowermeterWattsShrdzm(JsonObject logObj, uint8_t group) {
PowermeterBuffer_t result;
result.P = result.P1 = result.P2 = result.P3 = 0;
zeroExport_t *mCfg;
JsonObject *mLog;
logObj["mod"] = "getPowermeterWattsShrdzm";
unsigned long mPreviousTsp = 0;
setHeader(&http);
PowermeterBuffer_t mPowermeterBuffer[ZEROEXPORT_MAX_GROUPS][5] = { 0 };
short mPowermeterBufferPos[ZEROEXPORT_MAX_GROUPS] = { 0 };
};
String url =
String("http://") + String(mCfg->groups[group].pm_url) +
String("/") + String(mCfg->groups[group].pm_jsonPath +
String("?user=") + String(mCfg->groups[group].pm_user) +
String("&password=") + String(mCfg->groups[group].pm_pass));
// TODO: Vorlagen für Powermeter-Analyse
/*
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.begin(url);
if (http.GET() == HTTP_CODE_OK && http.getSize() > 0) {
// Parsing
DynamicJsonDocument doc(512);
DeserializationError error = deserializeJson(doc, http.getString());
if (error) {
logObj["err"] = "deserializeJson: " + String(error.c_str());
return result;
} else {
if (doc.containsKey(F("16.7.0"))) {
result.P = doc["16.7.0"];
}
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}
if (!(result.P1 && result.P2 && result.P3)) {
result.P1 = result.P2 = result.P3 = result.P / 3;
}
}
}
http.end();
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}}
return result;
}
#endif
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}}
*/
void bufferWrite(PowermeterBuffer_t raw, short group) {
mPowermeterBuffer[group][mPowermeterBufferPos[group]] = raw;
mPowermeterBufferPos[group]++;
if (mPowermeterBufferPos[group] >= 5) mPowermeterBufferPos[group] = 0;
}
};
#endif /*__POWERMETER_H__*/

141
src/plugins/zeroExport/powermeter.txt

@ -0,0 +1,141 @@
//-----------------------------------------------------------------------------
// 2024 Ahoy, https://ahoydtu.de
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
Shelly
======
Shelly 3EM
----------
Shelly pro 3EM
--------------
Stand: 02.04.2024
Abfrage:
/rpc/EM.GetStatus?id=0
Ergebnis:
{"id":0,"a_current":1.275,"a_voltage":228.8,"a_act_power":243.2,"a_aprt_power":291.6,"a_pf":0.86,"a_freq":50.0,"b_current":0.335,"b_voltage":228.2,"b_act_power":24.5,"b_aprt_power":76.6,"b_pf":0.59,"b_freq":50.0,"c_current":0.338,"c_voltage":227.6,"c_act_power":34.9,"c_aprt_power":76.9,"c_pf":0.65,"c_freq":50.0,"n_current":null,"total_current":1.949,"total_act_power":302.554,"total_aprt_power":445.042, "user_calibrated_phase":[]}
Abfrage:
/rpc/Shelly.GetStatus
Ergebnis:
{"ble":{},"cloud":{"connected":true},"em:0":{"id":0,"a_current":1.269,"a_voltage":228.5,"a_act_power":239.8,"a_aprt_power":289.7,"a_pf":0.86,"a_freq":50.0,"b_current":0.348,"b_voltage":227.5,"b_act_power":24.7,"b_aprt_power":79.0,"b_pf":0.58,"b_freq":50.0,"c_current":0.378,"c_voltage":228.5,"c_act_power":42.3,"c_aprt_power":86.3,"c_pf":0.66,"c_freq":50.0,"n_current":null,"total_current":1.995,"total_act_power":306.811,"total_aprt_power":455.018, "user_calibrated_phase":[]},"emdata:0":{"id":0,"a_total_act_energy":648103.86,"a_total_act_ret_energy":0.00,"b_total_act_energy":142793.47,"b_total_act_ret_energy":171396.02,"c_total_act_energy":493778.01,"c_total_act_ret_energy":0.00,"total_act":1284675.34, "total_act_ret":171396.02},"eth":{"ip":null},"modbus":{},"mqtt":{"connected":false},"script:1":{"id":1,"running":true,"mem_used":1302,"mem_peak":3094,"mem_free":23898},"sys":{"mac":"3CE90E6EBE5C","restart_required":false,"time":"18:09","unixtime":1712074162,"uptime":3180551,"ram_size":240004,"ram_free":96584,"fs_size":524288,"fs_free":176128,"cfg_rev":23,"kvs_rev":9104,"schedule_rev":0,"webhook_rev":0,"available_updates":{},"reset_reason":3},"temperature:0":{"id": 0,"tC":46.2, "tF":115.1},"wifi":{"sta_ip":"192.168.0.69","status":"got ip","ssid":"Riker","rssi":-70},"ws":{"connected":false}}
Shelly PM Mini Gen3
-------------------
Stand: 04.04.2024
Abfrage:
rpc/PM1.GetStatus?id=0
Ergebnis:
{"id":0, "voltage":226.0, "current":0.384, "apower":-55.0 ,"freq":50.0,"aenergy":{"total":11755.618,"by_minute":[1063.082,1700.931,1275.698],"minute_ts":1712248200},"ret_aenergy":{"total":10973.555,"by_minute":[1063.082,1700.931,1275.698],"minute_ts":1712248200}}
Abfrage:
rpc/Shelly.GetStatus
Ergebnis:
{"ble":{},"cloud":{"connected":true},"mqtt":{"connected":false},"pm1:0":{"id":0, "voltage":228.5, "current":0.451, "apower":-67.1 ,"freq":50.1,"aenergy":{"total":11754.555,"by_minute":[1700.931,1275.698,1913.547],"minute_ts":1712248140},"ret_aenergy":{"total":10972.492,"by_minute":[1700.931,1275.698,1913.547],"minute_ts":1712248140}},"sys":{"mac":"ECDA3BC63CC8","restart_required":false,"time":"18:29","unixtime":1712248162,"uptime":13723,"ram_size":260764,"ram_free":146460,"fs_size":1048576,"fs_free":729088,"cfg_rev":11,"kvs_rev":2,"schedule_rev":0,"webhook_rev":0,"available_updates":{},"reset_reason":1},"wifi":{"sta_ip":"192.168.0.57","status":"got ip","ssid":"Riker","rssi":-30},"ws":{"connected":false}}
---------------------------------------------------------------------------
Tasmota
=======
* Vorlage:
* http://IP/cm?cmnd=Status0
* {
* "Status":{
* "Module":1,
* "DeviceName":"Tasmota",
* "FriendlyName":["Tasmota"],
* "Topic":"Tasmota",
* "ButtonTopic":"0",
* "Power":0,
* "PowerOnState":3,
* "LedState":1,
* "LedMask":"FFFF",
* "SaveData":1,
* "SaveState":1,
* "SwitchTopic":"0",
* "SwitchMode":[0,0,0,0,0,0,0,0],
* "ButtonRetain":0,
* "SwitchRetain":0,
* "SensorRetain":0,
* "PowerRetain":0,
* "InfoRetain":0,
* "StateRetain":0},
* "StatusPRM":{"Baudrate":9600,"SerialConfig":"8N1","GroupTopic":"tasmotas","OtaUrl":"http://ota.tasmota.com/tasmota/release/tasmota.bin.gz","RestartReason":"Software/System restart","Uptime":"202T01:24:51","StartupUTC":"2023-08-13T15:21:13","Sleep":50,"CfgHolder":4617,"BootCount":27,"BCResetTime":"2023-02-04T16:45:38","SaveCount":150,"SaveAddress":"F5000"},
* "StatusFWR":{"Version":"11.1.0(tasmota)","BuildDateTime":"2022-05-05T03:23:22","Boot":31,"Core":"2_7_4_9","SDK":"2.2.2-dev(38a443e)","CpuFrequency":80,"Hardware":"ESP8266EX","CR":"378/699"},
* "StatusLOG":{"SerialLog":0,"WebLog":2,"MqttLog":0,"SysLog":0,"LogHost":"","LogPort":514,"SSId":["Odyssee2001",""],"TelePeriod":300,"Resolution":"558180C0","SetOption":["00008009","2805C80001000600003C5A0A190000000000","00000080","00006000","00004000"]},
* "StatusMEM":{"ProgramSize":658,"Free":344,"Heap":17,"ProgramFlashSize":1024,"FlashSize":1024,"FlashChipId":"14325E","FlashFrequency":40,"FlashMode":3,"Features":["00000809","87DAC787","043E8001","000000CF","010013C0","C000F989","00004004","00001000","04000020"],"Drivers":"1,2,3,4,5,6,7,8,9,10,12,16,18,19,20,21,22,24,26,27,29,30,35,37,45,56,62","Sensors":"1,2,3,4,5,6,53"},
* "StatusNET":{"Hostname":"Tasmota","IPAddress":"192.168.100.81","Gateway":"192.168.100.1","Subnetmask":"255.255.255.0","DNSServer1":"192.168.100.1","DNSServer2":"0.0.0.0","Mac":"4C:11:AE:11:F8:50","Webserver":2,"HTTP_API":1,"WifiConfig":4,"WifiPower":17.0},
* "StatusMQT":{"MqttHost":"192.168.100.80","MqttPort":1883,"MqttClientMask":"Tasmota","MqttClient":"Tasmota","MqttUser":"mqttuser","MqttCount":156,"MAX_PACKET_SIZE":1200,"KEEPALIVE":30,"SOCKET_TIMEOUT":4},
* "StatusTIM":{"UTC":"2024-03-02T16:46:04","Local":"2024-03-02T17:46:04","StartDST":"2024-03-31T02:00:00","EndDST":"2024-10-27T03:00:00","Timezone":"+01:00","Sunrise":"07:29","Sunset":"18:35"},
* "StatusSNS":{
* "Time":"2024-03-02T17:46:04",
* "PV":{
* "Bezug":0.364,
* "Einspeisung":3559.439,
* "Leistung":-14
* }
* },
* "StatusSTS":{"Time":"2024-03-02T17:46:04","Uptime":"202T01:24:51","UptimeSec":17457891,"Heap":16,"SleepMode":"Dynamic","Sleep":50,"LoadAvg":19,"MqttCount":156,"POWER":"OFF","Wifi":{"AP":1,"SSId":"Odyssee2001","BSSId":"34:31:C4:22:92:74","Channel":6,"Mode":"11n","RSSI":100,"Signal":-50,"LinkCount":15,"Downtime":"0T00:08:22"}
* }
* }
---------------------------------------------------------------------------
Tibber
======
---------------------------------------------------------------------------
Shrdzm
======
Stand: 03.04.2024
Quelle:
https://cms.shrdzm.com
Abfrage:
http://IP/getLastData?user=XXXXXX&password=YYYYYY
Ergebnis:
{"1.8.0":"21561001","2.8.0":"44477","3.8.1":"64037","4.8.1":"6021512","1.7.0":"277","2.7.0":"0","3.7.0":"0","4.7.0":"184","1.128.0":"0","16.7.0":"277","uptime":"0000:07:35:38"}
---------------------------------------------------------------------------
/*
Sortieren:
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}}
*/

1293
src/plugins/zeroExport/zeroExport.h

File diff suppressed because it is too large

3
src/web/RestApi.h

@ -80,7 +80,7 @@ class RestApi {
mHeapFrag = ESP.getHeapFragmentation();
#endif
AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000);
AsyncJsonResponse* response = new AsyncJsonResponse(false, 10000);
JsonObject root = response->getRoot();
String path = request->url().substring(5);
@ -860,6 +860,7 @@ class RestApi {
// Advanced
objGroup[F("setPoint")] = (int16_t)mConfig->plugin.zeroExport.groups[group].setPoint;
objGroup[F("refresh")] = (uint8_t)mConfig->plugin.zeroExport.groups[group].refresh;
objGroup[F("power")] = (int32_t)mConfig->plugin.zeroExport.groups[group].power;
objGroup[F("powerTolerance")] = (uint8_t)mConfig->plugin.zeroExport.groups[group].powerTolerance;
objGroup[F("powerMax")] = (uint16_t)mConfig->plugin.zeroExport.groups[group].powerMax;
objGroup[F("Kp")] = ah::round3((float)mConfig->plugin.zeroExport.groups[group].Kp);

17
src/web/html/setup.html

@ -1393,10 +1393,10 @@
ml("input", {name: "pm_jsonPath", class: "text", type: "text", value: obj.pm_jsonPath}, null),
]),
divRow("{#ZE_GROUP_TAB_POWERMETER_USER}",
ml("input", {name: "pm_user", class: "text", type: "text", value: obj.pm_user}, null),
ml("input", {name: "pm_user", class: "text", type: "text", value: "" }, null),
),
divRow("{#ZE_GROUP_TAB_POWERMETER_PASS}",
ml("input", {name: "pm_pass", class: "text", type: "text", value: obj.pm_pass}, null),
ml("input", {name: "pm_pass", class: "text", type: "password", value: "****"}, null),
),
// TODO: Uebersetzen mit lang.json und auf die entsprechende Dokuseite verlinken
divRow("Hinweis: ",
@ -1462,16 +1462,17 @@
modal("{#ZE_GROUP_EDIT_MODAL}: " + obj.id, html);
// ser.dispatchEvent(new Event('change'));
// Inhalt f?r pm_type aus config laden und in eine Funktion ausgliedern
// Inhalt fuer pm_type aus config laden und in eine Funktion ausgliedern
var e = document.getElementById("pm_type");
selDelAllOpt(e);
// TODO: ?bersetzen?
// TODO: uebersetzen?
e.appendChild(opt("0", "---"));
e.appendChild(opt("1", "Shelly"));
e.appendChild(opt("2", "Tasmota"));
//e.appendChild(opt("2", "Tasmota"));
e.appendChild(opt("3", "Mqtt"));
e.appendChild(opt("4", "Hichi"));
//e.appendChild(opt("4", "Hichi"));
e.appendChild(opt("5", "Tibber"));
e.appendChild(opt("6", "Shrdzm"));
for (var i = 0; i < e.options.length; i++) {
if (e.options[i].value == obj.pm_type) {
e.selectedIndex = i;
@ -1628,6 +1629,7 @@
// Advanced
o.setPoint = 0;
o.refresh = 10;
o.power = 0;
o.powerTolerance = 10;
o.powerMax = 600;
o.Kp = -1;
@ -1682,7 +1684,8 @@
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: right;", id: "groupPowerTotal"+group}, "n/a"),
ml("td", {style: "text-align: right;", id: "groupPower"+group}, String(obj.groups[group].power)),
ml("td", {style: "text-align: center;", onclick: function() {
function zeroGetIvList(ivObj) {
ZeroExportGroup_Modal(obj.groups[group], ivObj)

Loading…
Cancel
Save