mirror of https://github.com/lumapu/ahoy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
640 lines
26 KiB
640 lines
26 KiB
//-----------------------------------------------------------------------------
|
|
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
|
|
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#if defined(ESP32) && defined(F)
|
|
#undef F
|
|
#define F(sl) (sl)
|
|
#endif
|
|
|
|
#include "web.h"
|
|
|
|
#include "html/h/index_html.h"
|
|
#include "html/h/style_css.h"
|
|
#include "favicon.h"
|
|
#include "html/h/setup_html.h"
|
|
#include "html/h/visualization_html.h"
|
|
|
|
|
|
const uint16_t pwrLimitOptionValues[] {
|
|
NoPowerLimit,
|
|
AbsolutNonPersistent,
|
|
AbsolutPersistent,
|
|
RelativNonPersistent,
|
|
RelativPersistent
|
|
};
|
|
|
|
const char* const pwrLimitOptions[] {
|
|
"no power limit",
|
|
"absolute in Watt non persistent",
|
|
"absolute in Watt persistent",
|
|
"relativ in percent non persistent",
|
|
"relativ in percent persistent"
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) {
|
|
mMain = main;
|
|
mSysCfg = sysCfg;
|
|
mConfig = config;
|
|
mVersion = version;
|
|
#ifdef ESP8266
|
|
mWeb = new ESP8266WebServer(80);
|
|
mUpdater = new ESP8266HTTPUpdateServer();
|
|
#elif defined(ESP32)
|
|
mWeb = new WebServer(80);
|
|
mUpdater = new HTTPUpdateServer();
|
|
#endif
|
|
mUpdater->setup(mWeb);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::setup(void) {
|
|
DPRINTLN(DBG_VERBOSE, F("app::setup-begin"));
|
|
mWeb->begin();
|
|
DPRINTLN(DBG_VERBOSE, F("app::setup-on"));
|
|
mWeb->on("/", std::bind(&web::showIndex, this));
|
|
mWeb->on("/style.css", std::bind(&web::showCss, this));
|
|
mWeb->on("/favicon.ico", std::bind(&web::showFavicon, this));
|
|
mWeb->onNotFound ( std::bind(&web::showNotFound, this));
|
|
mWeb->on("/uptime", std::bind(&web::showUptime, this));
|
|
mWeb->on("/reboot", std::bind(&web::showReboot, this));
|
|
mWeb->on("/erase", std::bind(&web::showErase, this));
|
|
mWeb->on("/factory", std::bind(&web::showFactoryRst, this));
|
|
|
|
mWeb->on("/setup", std::bind(&web::showSetup, this));
|
|
mWeb->on("/save", std::bind(&web::showSave, this));
|
|
|
|
mWeb->on("/cmdstat", std::bind(&web::showStatistics, this));
|
|
mWeb->on("/visualization", std::bind(&web::showVisualization, this));
|
|
mWeb->on("/livedata", std::bind(&web::showLiveData, this));
|
|
mWeb->on("/json", std::bind(&web::showJson, this));
|
|
mWeb->on("/api", HTTP_POST, std::bind(&web::showWebApi, this));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::loop(void) {
|
|
mWeb->handleClient();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showIndex(void) {
|
|
DPRINTLN(DBG_VERBOSE, F("showIndex"));
|
|
String html = FPSTR(index_html);
|
|
html.replace(F("{DEVICE}"), mSysCfg->deviceName);
|
|
html.replace(F("{VERSION}"), mVersion);
|
|
html.replace(F("{TS}"), String(mConfig->sendInterval) + " ");
|
|
html.replace(F("{JS_TS}"), String(mConfig->sendInterval * 1000));
|
|
html.replace(F("{BUILD}"), String(AUTO_GIT_HASH));
|
|
mWeb->send(200, "text/html", html);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showCss(void) {
|
|
mWeb->send(200, "text/css", FPSTR(style_css));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showFavicon(void) {
|
|
static const char favicon_type[] PROGMEM = "image/x-icon";
|
|
static const char favicon_content[] PROGMEM = FAVICON_PANEL_16;
|
|
mWeb->send_P(200, favicon_type, favicon_content, sizeof(favicon_content));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showNotFound(void) {
|
|
DPRINTLN(DBG_VERBOSE, F("showNotFound - ") + mWeb->uri());
|
|
String msg = F("File Not Found\n\nURI: ");
|
|
msg += mWeb->uri();
|
|
mWeb->send(404, F("text/plain"), msg);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showUptime(void) {
|
|
char time[21] = {0};
|
|
uint32_t uptime = mMain->getUptime();
|
|
|
|
uint32_t upTimeSc = uint32_t((uptime) % 60);
|
|
uint32_t upTimeMn = uint32_t((uptime / (60)) % 60);
|
|
uint32_t upTimeHr = uint32_t((uptime / (60 * 60)) % 24);
|
|
uint32_t upTimeDy = uint32_t((uptime / (60 * 60 * 24)) % 365);
|
|
|
|
snprintf(time, 20, "%d Days, %02d:%02d:%02d", upTimeDy, upTimeHr, upTimeMn, upTimeSc);
|
|
|
|
mWeb->send(200, "text/plain", String(time) + "; now: " + mMain->getDateTimeStr(mMain->getTimestamp()));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showReboot(void) {
|
|
mWeb->send(200, F("text/html"), F("<!doctype html><html><head><title>Rebooting ...</title><meta http-equiv=\"refresh\" content=\"10; URL=/\"></head><body>rebooting ... auto reload after 10s</body></html>"));
|
|
delay(1000);
|
|
ESP.restart();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showErase() {
|
|
DPRINTLN(DBG_VERBOSE, F("showErase"));
|
|
mMain->eraseSettings();
|
|
showReboot();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showFactoryRst(void) {
|
|
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
|
|
String content = "";
|
|
int refresh = 3;
|
|
if(mWeb->args() > 0) {
|
|
if(mWeb->arg("reset").toInt() == 1) {
|
|
mMain->eraseSettings(true);
|
|
content = F("factory reset: success\n\nrebooting ... ");
|
|
refresh = 10;
|
|
}
|
|
else {
|
|
content = F("factory reset: aborted");
|
|
refresh = 3;
|
|
}
|
|
}
|
|
else {
|
|
content = F("<h1>Factory Reset</h1>"
|
|
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
|
refresh = 120;
|
|
}
|
|
mWeb->send(200, F("text/html"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>"));
|
|
if(refresh == 10) {
|
|
delay(1000);
|
|
ESP.restart();
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showSetup(void) {
|
|
DPRINTLN(DBG_VERBOSE, F("showSetup"));
|
|
String html = FPSTR(setup_html);
|
|
html.replace(F("{SSID}"), mSysCfg->stationSsid);
|
|
// PWD will be left at the default value (for protection)
|
|
// -> the PWD will only be changed if it does not match the default "{PWD}"
|
|
html.replace(F("{DEVICE}"), String(mSysCfg->deviceName));
|
|
html.replace(F("{VERSION}"), String(mVersion));
|
|
if(mMain->getWifiApActive())
|
|
html.replace("{IP}", String(F("http://192.168.1.1")));
|
|
else
|
|
html.replace("{IP}", (F("http://") + String(WiFi.localIP().toString())));
|
|
|
|
String inv = "";
|
|
Inverter<> *iv;
|
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
|
iv = mMain->mSys->getInverterByPos(i);
|
|
|
|
inv += F("<p class=\"subdes\">Inverter ") + String(i) + "</p>";
|
|
inv += F("<label for=\"inv") + String(i) + F("Addr\">Address*</label>");
|
|
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Addr\" value=\"");
|
|
if(NULL != iv)
|
|
inv += String(iv->serial.u64, HEX);
|
|
inv += F("\"/ maxlength=\"12\">");
|
|
|
|
inv += F("<label for=\"inv") + String(i) + F("Name\">Name*</label>");
|
|
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Name\" value=\"");
|
|
if(NULL != iv)
|
|
inv += String(iv->name);
|
|
inv += F("\"/ maxlength=\"") + String(MAX_NAME_LENGTH) + "\">";
|
|
|
|
inv += F("<label for=\"inv") + String(i) + F("ActivePowerLimit\">Active Power Limit</label>");
|
|
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("ActivePowerLimit\" value=\"");
|
|
if(NULL != iv)
|
|
inv += String(iv->powerLimit[0]);
|
|
inv += F("\"/ maxlength=\"") + String(6) + "\">";
|
|
|
|
inv += F("<label for=\"inv") + String(i) + F("ActivePowerLimitConType\">Active Power Limit Control Type</label>");
|
|
inv += F("<select name=\"inv") + String(i) + F("PowerLimitControl\">");
|
|
for(uint8_t j = 0; j < 5; j++) {
|
|
inv += F("<option value=\"") + String(pwrLimitOptionValues[j]) + F("\"");
|
|
if(NULL != iv) {
|
|
if(iv->powerLimit[1] == pwrLimitOptionValues[j])
|
|
inv += F(" selected");
|
|
}
|
|
inv += F(">") + String(pwrLimitOptions[j]) + F("</option>");
|
|
}
|
|
inv += F("</select>");
|
|
|
|
inv += F("<label for=\"inv") + String(i) + F("ModPwr0\" name=\"lbl") + String(i);
|
|
inv += F("ModPwr\">Max Module Power (Wp)</label><div class=\"modpwr\">");
|
|
for(uint8_t j = 0; j < 4; j++) {
|
|
inv += F("<input type=\"text\" class=\"text sh\" name=\"inv") + String(i) + F("ModPwr") + String(j) + F("\" value=\"");
|
|
if(NULL != iv)
|
|
inv += String(iv->chMaxPwr[j]);
|
|
inv += F("\"/ maxlength=\"4\">");
|
|
}
|
|
inv += F("</div><br/><label for=\"inv") + String(i) + F("ModName0\" name=\"lbl") + String(i);
|
|
inv += F("ModName\">Module Name</label><div class=\"modname\">");
|
|
for(uint8_t j = 0; j < 4; j++) {
|
|
inv += F("<input type=\"text\" class=\"text sh\" name=\"inv") + String(i) + F("ModName") + String(j) + F("\" value=\"");
|
|
if(NULL != iv)
|
|
inv += String(iv->chName[j]);
|
|
inv += F("\"/ maxlength=\"") + String(MAX_NAME_LENGTH) + "\">";
|
|
}
|
|
inv += F("</div>");
|
|
}
|
|
html.replace(F("{INVERTERS}"), String(inv));
|
|
|
|
|
|
// pinout
|
|
String pinout;
|
|
for(uint8_t i = 0; i < 3; i++) {
|
|
pinout += F("<label for=\"") + String(pinArgNames[i]) + "\">" + String(pinNames[i]) + F("</label>");
|
|
pinout += F("<select name=\"") + String(pinArgNames[i]) + "\">";
|
|
for(uint8_t j = 0; j <= 16; j++) {
|
|
pinout += F("<option value=\"") + String(j) + "\"";
|
|
switch(i) {
|
|
default: if(j == mConfig->pinCs) pinout += F(" selected"); break;
|
|
case 1: if(j == mConfig->pinCe) pinout += F(" selected"); break;
|
|
case 2: if(j == mConfig->pinIrq) pinout += F(" selected"); break;
|
|
}
|
|
pinout += ">" + String(wemosPins[j]) + F("</option>");
|
|
}
|
|
pinout += F("</select>");
|
|
}
|
|
html.replace(F("{PINOUT}"), String(pinout));
|
|
|
|
|
|
// nrf24l01+
|
|
String rf24;
|
|
for(uint8_t i = 0; i <= 3; i++) {
|
|
rf24 += F("<option value=\"") + String(i) + "\"";
|
|
if(i == mConfig->amplifierPower)
|
|
rf24 += F(" selected");
|
|
rf24 += ">" + String(rf24AmpPowerNames[i]) + F("</option>");
|
|
}
|
|
html.replace(F("{RF24}"), String(rf24));
|
|
|
|
|
|
html.replace(F("{INV_INTVL}"), String(mConfig->sendInterval));
|
|
html.replace(F("{INV_RETRIES}"), String(mConfig->maxRetransPerPyld));
|
|
|
|
html.replace(F("{SER_INTVL}"), String(mConfig->serialInterval));
|
|
html.replace(F("{SER_VAL_CB}"), (mConfig->serialShowIv) ? "checked" : "");
|
|
html.replace(F("{SER_DBG_CB}"), (mConfig->serialDebug) ? "checked" : "");
|
|
|
|
html.replace(F("{NTP_ADDR}"), String(mConfig->ntpAddr));
|
|
html.replace(F("{NTP_PORT}"), String(mConfig->ntpPort));
|
|
|
|
html.replace(F("{MQTT_ADDR}"), String(mConfig->mqtt.broker));
|
|
html.replace(F("{MQTT_PORT}"), String(mConfig->mqtt.port));
|
|
html.replace(F("{MQTT_USER}"), String(mConfig->mqtt.user));
|
|
html.replace(F("{MQTT_PWD}"), String(mConfig->mqtt.pwd));
|
|
html.replace(F("{MQTT_TOPIC}"), String(mConfig->mqtt.topic));
|
|
|
|
mWeb->send(200, F("text/html"), html);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showSave(void) {
|
|
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
|
|
|
if(mWeb->args() > 0) {
|
|
char buf[20] = {0};
|
|
|
|
// general
|
|
if(mWeb->arg("ssid") != "")
|
|
mWeb->arg("ssid").toCharArray(mSysCfg->stationSsid, SSID_LEN);
|
|
if(mWeb->arg("pwd") != "{PWD}")
|
|
mWeb->arg("pwd").toCharArray(mSysCfg->stationPwd, PWD_LEN);
|
|
if(mWeb->arg("device") != "")
|
|
mWeb->arg("device").toCharArray(mSysCfg->deviceName, DEVNAME_LEN);
|
|
|
|
// inverter
|
|
Inverter<> *iv;
|
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
|
iv = mMain->mSys->getInverterByPos(i, false);
|
|
// address
|
|
mWeb->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
|
|
if(strlen(buf) == 0)
|
|
memset(buf, 0, 20);
|
|
iv->serial.u64 = mMain->Serial2u64(buf);
|
|
|
|
// active power limit
|
|
uint16_t actPwrLimit = mWeb->arg("inv" + String(i) + "ActivePowerLimit").toInt();
|
|
uint16_t actPwrLimitControl = mWeb->arg("inv" + String(i) + "PowerLimitControl").toInt();
|
|
if (actPwrLimit != 0xffff && actPwrLimit > 0){
|
|
iv->powerLimit[0] = actPwrLimit;
|
|
iv->powerLimit[1] = actPwrLimitControl;
|
|
iv->devControlCmd = ActivePowerContr;
|
|
iv->devControlRequest = true;
|
|
if ((iv->powerLimit[1] & 0x0001) == 0x0001)
|
|
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") );
|
|
else {
|
|
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W") );
|
|
DPRINTLN(DBG_INFO, F("Power Limit Control Setting ") + String(iv->powerLimit[1]));
|
|
}
|
|
}
|
|
if (actPwrLimit == 0xffff) { // set to 100%
|
|
iv->powerLimit[0] = 100;
|
|
iv->powerLimit[1] = RelativPersistent;
|
|
iv->devControlCmd = ActivePowerContr;
|
|
iv->devControlRequest = true;
|
|
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to unlimted"));
|
|
}
|
|
|
|
// name
|
|
mWeb->arg("inv" + String(i) + "Name").toCharArray(iv->name, MAX_NAME_LENGTH);
|
|
|
|
// max channel power / name
|
|
for(uint8_t j = 0; j < 4; j++) {
|
|
iv->chMaxPwr[j] = mWeb->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
|
|
mWeb->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->chName[j], MAX_NAME_LENGTH);
|
|
}
|
|
iv->initialized = true;
|
|
}
|
|
if(mWeb->arg("invInterval") != "")
|
|
mConfig->sendInterval = mWeb->arg("invInterval").toInt();
|
|
if(mWeb->arg("invRetry") != "")
|
|
mConfig->maxRetransPerPyld = mWeb->arg("invRetry").toInt();
|
|
|
|
// pinout
|
|
uint8_t pin;
|
|
for(uint8_t i = 0; i < 3; i ++) {
|
|
pin = mWeb->arg(String(pinArgNames[i])).toInt();
|
|
switch(i) {
|
|
default: mConfig->pinCs = pin; break;
|
|
case 1: mConfig->pinCe = pin; break;
|
|
case 2: mConfig->pinIrq = pin; break;
|
|
}
|
|
}
|
|
|
|
// nrf24 amplifier power
|
|
mConfig->amplifierPower = mWeb->arg("rf24Power").toInt() & 0x03;
|
|
|
|
// ntp
|
|
if(mWeb->arg("ntpAddr") != "") {
|
|
mWeb->arg("ntpAddr").toCharArray(mConfig->ntpAddr, NTP_ADDR_LEN);
|
|
mConfig->ntpPort = mWeb->arg("ntpPort").toInt() & 0xffff;
|
|
}
|
|
|
|
// mqtt
|
|
if(mWeb->arg("mqttAddr") != "") {
|
|
String addr = mWeb->arg("mqttAddr");
|
|
addr.trim();
|
|
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
|
mWeb->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
|
mWeb->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
|
mWeb->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
|
mConfig->mqtt.port = mWeb->arg("mqttPort").toInt();
|
|
}
|
|
|
|
// serial console
|
|
if(mWeb->arg("serIntvl") != "") {
|
|
mConfig->serialInterval = mWeb->arg("serIntvl").toInt() & 0xffff;
|
|
|
|
mConfig->serialDebug = (mWeb->arg("serDbg") == "on");
|
|
mConfig->serialShowIv = (mWeb->arg("serEn") == "on");
|
|
// Needed to log TX buffers to serial console
|
|
mMain->mSys->Radio.mSerialDebug = mConfig->serialDebug;
|
|
}
|
|
|
|
mMain->saveValues();
|
|
|
|
if(mWeb->arg("reboot") == "on")
|
|
showReboot();
|
|
else
|
|
mWeb->send(200, F("text/html"), F("<!doctype html><html><head><title>Setup saved</title><meta http-equiv=\"refresh\" content=\"0; URL=/setup\"></head><body>"
|
|
"<p>saved</p></body></html>"));
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showStatistics(void) {
|
|
DPRINTLN(DBG_VERBOSE, F("web::showStatistics"));
|
|
mWeb->send(200, F("text/plain"), mMain->getStatistics());
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showVisualization(void) {
|
|
DPRINTLN(DBG_VERBOSE, F("web::showVisualization"));
|
|
String html = FPSTR(visualization_html);
|
|
html.replace(F("{DEVICE}"), mSysCfg->deviceName);
|
|
html.replace(F("{VERSION}"), mVersion);
|
|
html.replace(F("{TS}"), String(mConfig->sendInterval) + " ");
|
|
html.replace(F("{JS_TS}"), String(mConfig->sendInterval * 1000));
|
|
mWeb->send(200, F("text/html"), html);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showLiveData(void) {
|
|
DPRINTLN(DBG_VERBOSE, F("web::showLiveData"));
|
|
|
|
String modHtml, totalModHtml;
|
|
float totalYield = 0, totalYieldToday = 0, totalActual = 0;
|
|
uint8_t count = 0;
|
|
|
|
for (uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) {
|
|
count++;
|
|
|
|
Inverter<> *iv = mMain->mSys->getInverterByPos(id);
|
|
if (NULL != iv) {
|
|
#ifdef LIVEDATA_VISUALIZED
|
|
uint8_t modNum, pos;
|
|
switch (iv->type) {
|
|
default:
|
|
case INV_TYPE_1CH: modNum = 1; break;
|
|
case INV_TYPE_2CH: modNum = 2; break;
|
|
case INV_TYPE_4CH: modNum = 4; break;
|
|
}
|
|
|
|
modHtml += F("<div class=\"iv\">"
|
|
"<div class=\"ch-iv\"><span class=\"head\">")
|
|
+ String(iv->name) + F(" Limit ")
|
|
+ String(iv->actPowerLimit) + F("%");
|
|
if(NoPowerLimit == iv->powerLimit[1])
|
|
modHtml += F(" (not controlled)");
|
|
modHtml += F(" | last Alarm: ") + iv->lastAlarmMsg + F("</span>");
|
|
|
|
uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PCT, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_PRA, FLD_ALARM_MES_ID};
|
|
|
|
for (uint8_t fld = 0; fld < 11; fld++) {
|
|
pos = (iv->getPosByChFld(CH0, list[fld]));
|
|
|
|
if(fld == 6){
|
|
totalYield += iv->getValue(pos);
|
|
}
|
|
|
|
if(fld == 7){
|
|
totalYieldToday += iv->getValue(pos);
|
|
}
|
|
|
|
if(fld == 2){
|
|
totalActual += iv->getValue(pos);
|
|
}
|
|
|
|
if (0xff != pos) {
|
|
modHtml += F("<div class=\"subgrp\">");
|
|
modHtml += F("<span class=\"value\">") + String(iv->getValue(pos));
|
|
modHtml += F("<span class=\"unit\">") + String(iv->getUnit(pos)) + F("</span></span>");
|
|
modHtml += F("<span class=\"info\">") + String(iv->getFieldName(pos)) + F("</span>");
|
|
modHtml += F("</div>");
|
|
}
|
|
}
|
|
modHtml += "</div>";
|
|
|
|
for (uint8_t ch = 1; ch <= modNum; ch++) {
|
|
modHtml += F("<div class=\"ch\"><span class=\"head\">");
|
|
if (iv->chName[ch - 1][0] == 0)
|
|
modHtml += F("CHANNEL ") + String(ch);
|
|
else
|
|
modHtml += String(iv->chName[ch - 1]);
|
|
modHtml += F("</span>");
|
|
for (uint8_t j = 0; j < 6; j++) {
|
|
switch (j) {
|
|
default: pos = (iv->getPosByChFld(ch, FLD_UDC)); break;
|
|
case 1: pos = (iv->getPosByChFld(ch, FLD_IDC)); break;
|
|
case 2: pos = (iv->getPosByChFld(ch, FLD_PDC)); break;
|
|
case 3: pos = (iv->getPosByChFld(ch, FLD_YD)); break;
|
|
case 4: pos = (iv->getPosByChFld(ch, FLD_YT)); break;
|
|
case 5: pos = (iv->getPosByChFld(ch, FLD_IRR)); break;
|
|
}
|
|
if (0xff != pos) {
|
|
modHtml += F("<span class=\"value\">") + String(iv->getValue(pos));
|
|
modHtml += F("<span class=\"unit\">") + String(iv->getUnit(pos)) + F("</span></span>");
|
|
modHtml += F("<span class=\"info\">") + String(iv->getFieldName(pos)) + F("</span>");
|
|
}
|
|
}
|
|
modHtml += "</div>";
|
|
yield();
|
|
}
|
|
modHtml += F("<div class=\"ts\">Last received data requested at: ") + mMain->getDateTimeStr(iv->ts) + F("</div>");
|
|
modHtml += F("</div>");
|
|
#else
|
|
// dump all data to web frontend
|
|
modHtml = F("<pre>");
|
|
char topic[30], val[10];
|
|
for (uint8_t i = 0; i < iv->listLen; i++) {
|
|
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
|
snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i));
|
|
modHtml += String(topic) + ": " + String(val) + "\n";
|
|
}
|
|
modHtml += F("</pre>");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if(count > 1){
|
|
totalModHtml += F("<div class=\"iv\">"
|
|
"<div class=\"ch-all\"><span class=\"head\">Gesamt</span>");
|
|
|
|
totalModHtml += F("<div class=\"subgrp\">");
|
|
totalModHtml += F("<span class=\"value\">") + String(totalActual);
|
|
totalModHtml += F("<span class=\"unit\">W</span></span>");
|
|
totalModHtml += F("<span class=\"info\">P_AC All</span>");
|
|
totalModHtml += F("</div>");
|
|
|
|
totalModHtml += F("<div class=\"subgrp\">");
|
|
totalModHtml += F("<span class=\"value\">") + String(totalYieldToday);
|
|
totalModHtml += F("<span class=\"unit\">Wh</span></span>");
|
|
totalModHtml += F("<span class=\"info\">YieldDayAll</span>");
|
|
totalModHtml += F("</div>");
|
|
|
|
totalModHtml += F("<div class=\"subgrp\">");
|
|
totalModHtml += F("<span class=\"value\">") + String(totalYield);
|
|
totalModHtml += F("<span class=\"unit\">kWh</span></span>");
|
|
totalModHtml += F("<span class=\"info\">YieldTotalAll</span>");
|
|
totalModHtml += F("</div>");
|
|
|
|
totalModHtml += F("</div>");
|
|
totalModHtml += F("</div>");
|
|
mWeb->send(200, F("text/html"), totalModHtml + modHtml);
|
|
} else {
|
|
mWeb->send(200, F("text/html"), modHtml);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showJson(void) {
|
|
DPRINTLN(DBG_VERBOSE, F("web::showJson"));
|
|
mWeb->send(200, F("application/json"), mMain->getJson());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void web::showWebApi(void)
|
|
{
|
|
DPRINTLN(DBG_VERBOSE, F("web::showWebApi"));
|
|
DPRINTLN(DBG_DEBUG, mWeb->arg("plain"));
|
|
const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity.
|
|
DynamicJsonDocument response(capacity);
|
|
|
|
// Parse JSON object
|
|
deserializeJson(response, mWeb->arg("plain"));
|
|
// ToDo: error handling for payload
|
|
uint8_t iv_id = response["inverter"];
|
|
uint8_t cmd = response["cmd"];
|
|
Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id);
|
|
if (NULL != iv)
|
|
{
|
|
if (response["tx_request"] == (uint8_t)TX_REQ_INFO)
|
|
{
|
|
// if the AlarmData is requested set the Alarm Index to the requested one
|
|
if (cmd == AlarmData || cmd == AlarmUpdate){
|
|
// set the AlarmMesIndex for the request from user input
|
|
iv->alarmMesIndex = response["payload"];
|
|
}
|
|
DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String((uint16_t) response["payload"]));
|
|
// process payload from web request corresponding to the cmd
|
|
iv->enqueCommand<InfoCommand>(cmd);
|
|
}
|
|
|
|
|
|
if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL)
|
|
{
|
|
if (response["cmd"] == (uint8_t)ActivePowerContr)
|
|
{
|
|
uint16_t webapiPayload = response["payload"];
|
|
uint16_t webapiPayload2 = response["payload2"];
|
|
if (webapiPayload > 0 && webapiPayload < 10000)
|
|
{
|
|
iv->devControlCmd = ActivePowerContr;
|
|
iv->powerLimit[0] = webapiPayload;
|
|
if (webapiPayload2 > 0)
|
|
{
|
|
iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check
|
|
}
|
|
else
|
|
{ // if not set, set it to 0x0000 default
|
|
iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporay in Watt absolut
|
|
}
|
|
if (iv->powerLimit[1] & 0x0001)
|
|
{
|
|
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API"));
|
|
}
|
|
else
|
|
{
|
|
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API"));
|
|
}
|
|
iv->devControlRequest = true; // queue it in the request loop
|
|
}
|
|
}
|
|
if (response["cmd"] == (uint8_t)TurnOff){
|
|
iv->devControlCmd = TurnOff;
|
|
iv->devControlRequest = true; // queue it in the request loop
|
|
}
|
|
if (response["cmd"] == (uint8_t)TurnOn){
|
|
iv->devControlCmd = TurnOn;
|
|
iv->devControlRequest = true; // queue it in the request loop
|
|
}
|
|
}
|
|
}
|
|
mWeb->send(200, "text/json", "{success:true}");
|
|
}
|
|
|