Browse Source

* built visualization as xhr

* converted /update to xhr
* started web serial console /serial
* /save does not work yet - not debugged
pull/283/head
lumapu 2 years ago
parent
commit
8ae78842a8
  1. 2
      tools/esp8266/ahoywifi.cpp
  2. 3
      tools/esp8266/config.h
  3. 3
      tools/esp8266/dbg.cpp
  4. 15
      tools/esp8266/html/api.js
  5. 5
      tools/esp8266/html/convert.py
  6. 29
      tools/esp8266/html/index.html
  7. 50
      tools/esp8266/html/serial.html
  8. 16
      tools/esp8266/html/setup.html
  9. 18
      tools/esp8266/html/update.html
  10. 127
      tools/esp8266/html/visualization.html
  11. 19
      tools/esp8266/include/dbg.h
  12. 108
      tools/esp8266/tmplProc.h
  13. 235
      tools/esp8266/web.cpp
  14. 11
      tools/esp8266/web.h
  15. 54
      tools/esp8266/webApi.cpp
  16. 2
      tools/esp8266/webApi.h

2
tools/esp8266/ahoywifi.cpp

@ -54,7 +54,7 @@ void ahoywifi::setup(uint32_t timeout, bool settingValid) {
if(mApActive)
DBGPRINTLN(F("192.168.1.1"));
else
DBGPRINTLN(WiFi.localIP());
DBGPRINTLN(WiFi.localIP().toString());
DPRINTLN(DBG_INFO, F("to configure your device"));
DPRINTLN(DBG_INFO, F("----------------------------------------\n"));
}

3
tools/esp8266/config.h

@ -108,9 +108,6 @@
// default MQTT topic
#define DEF_MQTT_TOPIC "inverter"
// changes the style of "/setup" page, visualized = nicer
#define LIVEDATA_VISUALIZED
#if __has_include("config_override.h")
#include "config_override.h"
#endif

3
tools/esp8266/dbg.cpp

@ -0,0 +1,3 @@
#include "dbg.h"
DBG_CB mCb = NULL;

15
tools/esp8266/html/api.js

@ -16,8 +16,10 @@ function getAjax(url, ptr) {
http.send(null);
}
function p() {
if(http.readyState == 4)
ptr(JSON.parse(http.responseText));
if(http.readyState == 4) {
if(null != http.responseText)
ptr(JSON.parse(http.responseText));
}
}
}
@ -60,6 +62,13 @@ function sel(name, opt, selId) {
function div(cl) {
e = document.createElement('div');
e.classList.add(cl);
e.classList.add(...cl);
return e;
}
function span(val, cl) {
e = document.createElement('span');
e.innerHTML = val;
e.classList.add(...cl);
return e;
}

5
tools/esp8266/html/convert.py

@ -64,7 +64,8 @@ def convert2Header(inFile, compress):
convert2Header("index.html", True)
convert2Header("setup.html", True)
convert2Header("visualization.html", False)
convert2Header("update.html", False)
convert2Header("visualization.html", True)
convert2Header("update.html", True)
convert2Header("serial.html", True)
convert2Header("style.css", True)
convert2Header("api.js", True)

29
tools/esp8266/html/index.html

@ -10,9 +10,10 @@
<h1>AHOY</h1>
<div id="content" class="content">
<p>
<a href="/visualization">Visualization</a><br/>
<a href="/live">Visualization</a><br/>
<br/>
<a href="/setup">Setup</a><br/>
<a href="/serial">Serial Console</a><br/>
</p>
<p><span class="des">Uptime: </span><span id="uptime"></span></p>
<p><span class="des">ESP-Time: </span><span id="date"></span></p>
@ -80,8 +81,10 @@
html += "producing\n";
if(false == i["is_avail"]) {
var date = new Date(i["ts_last_success"] * 1000);
html += "-> last successful transmission: " + date.toLocaleString('de-DE', {timeZone: 'UTC'});
if(i["ts_last_success"] > 0) {
var date = new Date(i["ts_last_success"] * 1000);
html += "-> last successful transmission: " + date.toLocaleString('de-DE', {timeZone: 'UTC'});
}
}
}
@ -100,13 +103,19 @@
}
function parse(obj) {
parseSys(obj["system"]);
parseStat(obj["statistics"]);
parseIv(obj["inverter"]);
parseWarnInfo(obj["warnings"], obj["infos"]);
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
if(false == intervalSet)
window.setInterval("getAjax('/api/index', parse)", obj["refresh_interval"] * 1000);
if(null != obj) {
parseSys(obj["system"]);
parseStat(obj["statistics"]);
parseIv(obj["inverter"]);
parseWarnInfo(obj["warnings"], obj["infos"]);
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
if(false == intervalSet) {
window.setInterval("getAjax('/api/index', parse)", obj["refresh_interval"] * 1000);
intervalSet = true;
}
}
else
document.getElementById("refresh").innerHTML = "n/a";
}
getAjax("/api/index", parse);

50
tools/esp8266/html/serial.html

@ -0,0 +1,50 @@
<!doctype html>
<html>
<head>
<title>Serial Console</title>
<link rel="stylesheet" type="text/css" href="style.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="api.js"></script>
</head>
<body>
<h1>Serial Console</h1>
<div id="content" class="content">
<textarea rows="20" cols="90" id="serial" readonly></textarea>
</div>
<div id="footer">
<p class="left">&copy 2022</p>
<p class="left"><a href="/">Home</a></p>
<p class="right" id="version"></p>
</div>
<script type="text/javascript">
function parseSys(obj) {
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
}
var con = document.getElementById("serial");
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
//console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
//console.log("Events Disconnected");
}
}, false);
source.addEventListener('serial', function(e) {
//var ascii = "";
//for(i = 0; i < e.data.length; i++)
// ascii += e.data.charCodeAt(i).toString(16) + " ";
//console.log(ascii);
con.value += e.data.replace(/\<rn\>/g, '\r\n');
con.scrollTop = con.scrollHeight;
}, false);
}
getAjax("/api/system", parseSys);
</script>
</body>
</html>

16
tools/esp8266/html/setup.html

@ -262,13 +262,15 @@
}
function parse(root) {
parseSys(root["system"]);
parseIv(root["inverter"]);
parseMqtt(root["mqtt"]);
parseNtp(root["ntp"]);
parsePinout(root["pinout"]);
parseRadio(root["radio"]);
parseSerial(root["serial"]);
if(null != root) {
parseSys(root["system"]);
parseIv(root["inverter"]);
parseMqtt(root["mqtt"]);
parseNtp(root["ntp"]);
parsePinout(root["pinout"]);
parseRadio(root["radio"]);
parseSerial(root["serial"]);
}
}
getAjax("/api/setup", parse);

18
tools/esp8266/html/update.html

@ -4,7 +4,7 @@
<title>Update</title>
<link rel="stylesheet" type="text/css" href="style.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
{#HEAD}
<script type="text/javascript" src="api.js"></script>
</head>
<body>
<h1>Update</h1>
@ -13,13 +13,21 @@
Make sure that you have noted all or settings before starting an update. New versions maybe changed their memory layout which remains in default settings.
</div>
<br/><br/>
{#CONTENT}
<form method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
<input type="file" name="update"><input type="submit" value="Update">
</form>
</div>
<div id="footer">
<p class="left">&copy 2022</p>
<p class="left"><a href="{#IP}/">Home</a></p>
<p class="right">AHOY :: {#VERSION}</p>
<p class="right"><a href="/reboot">Reboot</a></p>
<p class="left"><a href="/">Home</a></p>
<p class="right" id="version"></p>
</div>
<script type="text/javascript">
function parseSys(obj) {
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
}
getAjax("/api/system", parseSys);
</script>
</body>
</html>

127
tools/esp8266/html/visualization.html

@ -1,43 +1,116 @@
<!doctype html>
<html>
<head>
<title>Index - {DEVICE}</title>
<title>Live</title>
<link rel="stylesheet" type="text/css" href="style.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<script type="text/javascript">
getAjax('/livedata', 'livedata');
window.setInterval("getAjax('/livedata', 'livedata')", {JS_TS});
function getAjax(url, resid) {
var http = null;
http = new XMLHttpRequest();
if(http != null) {
http.open("GET", url, true);
http.onreadystatechange = print;
http.send(null);
}
function print() {
if(http.readyState == 4) {
document.getElementById(resid).innerHTML = http.responseText;
}
}
}
</script>
<style type="text/css">
</style>
<script type="text/javascript" src="api.js"></script>
</head>
<body>
<h1>AHOY - {DEVICE}</h1>
<h1>AHOY</h1>
<div id="content" class="content">
<div id="livedata"></div>
<p>Every {TS}seconds the values are updated</p>
<div id="live"></div>
<p>Every <span id="refresh"></span> seconds the values are updated</p>
</div>
<div id="footer">
<p class="left">&copy 2022</p>
<p class="left"><a href="/">Home</a></p>
<p class="right">AHOY :: {VERSION}</p>
<p class="right" id="version"></p>
</div>
<script type="text/javascript">
var intervalSet = false;
function parseSys(obj) {
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
}
function parseIv(obj, root) {
var ivHtml = [];
var tDiv = div(["ch-all", "iv"]);
tDiv.appendChild(span("Total", ["head"]));
var total = new Array(root.ch0_fld_names.length).fill(0);
if(obj.length > 1)
ivHtml.push(tDiv);
for(var iv of obj) {
main = div(["iv"]);
var ch0 = div(["ch-iv"]);
var ctrl = (iv["power_limit_active"]) ? "" : " (not controlled)";
ch0.appendChild(span(iv["name"] + " Limit " + iv["power_limit_read"] + "%" + ctrl + " | last Alarm: " + iv["last_alarm"], ["head"]));
for(var j = 0; j < root.ch0_fld_names.length; j++) {
var val = Math.round(iv["ch"][0][j] * 100) / 100;
if(val > 0) {
var sub = div(["subgrp"]);
sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"]));
sub.appendChild(span(root["ch0_fld_names"][j], ["info"]));
ch0.appendChild(sub);
switch(j) {
case 2: total[j] += val; break;
case 6: total[j] += val; break;
case 7: total[j] += val; break;
case 8: total[j] += val; break;
case 10: total[j] += val; break;
}
}
}
main.appendChild(ch0);
for(var i = 1; i < 5; i++) {
var ch = div(["ch"]);
ch.appendChild(span(("" == iv["ch_names"][i]) ? ("CHANNEL " + i) : iv["ch_names"][i], ["head"]));
for(var j = 0; j < root.fld_names.length; j++) {
var val = Math.round(iv["ch"][i][j] * 100) / 100;
if(val > 0) {
ch.appendChild(span(val + " " + span(root["fld_units"][j], ["unit"]).innerHTML, ["value"]));
ch.appendChild(span(root["fld_names"][j], ["info"]));
}
}
main.appendChild(ch);
}
var ts = div(["ts"]);
var date = new Date(iv["ts_last_success"] * 1000);
ts.innerHTML = "Last received data requested at: " + date.toLocaleString('de-DE', {timeZone: 'UTC'});
main.appendChild(ts);
ivHtml.push(main);
}
// total
if(obj.length > 1) {
for(var j = 0; j < root.ch0_fld_names.length; j++) {
var val = total[j];
if(val > 0) {
var sub = div(["subgrp"]);
sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"]));
sub.appendChild(span(root["ch0_fld_names"][j], ["info"]));
tDiv.appendChild(sub);
}
}
}
document.getElementById("live").replaceChildren(...ivHtml);
}
function parse(obj) {
if(null != obj) {
parseSys(obj["system"]);
parseIv(obj["inverter"], obj);
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
if(false == intervalSet) {
window.setInterval("getAjax('/api/live', parse)", obj["refresh_interval"] * 1000);
intervalSet = true;
}
}
else
document.getElementById("refresh").innerHTML = "n/a";
}
getAjax("/api/live", parse);
</script>
</body>
</html>

19
tools/esp8266/include/dbg.h

@ -10,6 +10,7 @@
#define F(sl) (sl)
#endif
#include <functional>
//-----------------------------------------------------------------------------
// available levels
#define DBG_ERROR 1
@ -32,14 +33,22 @@
#define DBGPRINTLN(str)
#else
#ifdef ARDUINO
#define DBG_CB std::function<void(String)>
extern DBG_CB mCb;
//static DBG_CB mCb;
inline void registerDebugCb(DBG_CB cb) {
mCb = cb;
}
#ifndef DSERIAL
#define DSERIAL Serial
#endif
template <class T>
inline void DBGPRINT(T str) { DSERIAL.print(str); }
template <class T>
inline void DBGPRINTLN(T str) { DBGPRINT(str); DBGPRINT(F("\r\n")); }
//template <class T>
inline void DBGPRINT(String str) { DSERIAL.print(str); if(NULL != mCb) mCb(str); }
//template <class T>
inline void DBGPRINTLN(String str) { DBGPRINT(str); DBGPRINT(F("\r\n")); }
inline void DHEX(uint8_t b) {
if( b<0x10 ) DSERIAL.print('0');
DSERIAL.print(b,HEX);
@ -60,6 +69,8 @@
else if( b<0x10000000 ) DSERIAL.print(F("0"));
DSERIAL.print(b,HEX);
}
#endif
#endif

108
tools/esp8266/tmplProc.h

@ -1,108 +0,0 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#ifndef __TMPL_PROC__
#define __TMPL_PROC__
// HTML template processor, searches keywords and calls callback
// inspired by: https://github.com/plapointe6/EspHtmlTemplateProcessor
#include "dbg.h"
#include <string.h>
#include <functional>
#include "ESPAsyncWebServer.h"
#define MAX_BUFFER_SIZE 256
#define MAX_KEY_LEN 20
enum { ST_NONE = 0, ST_BUF, ST_PROC, ST_START, ST_KEY };
typedef std::function<String(char* key)> TmplCb;
class tmplProc {
public:
tmplProc(AsyncWebServerRequest *request, uint32_t bufStartSize = 1000) {
// Note: don't choose the bufStartSize to small. A too small start
// size will result in fractioned memory and maybe in a zero
// increase -> fail (Arduino - cbuf.cpp)
mRequest = request;
mResponse = request->beginResponseStream("text/html", bufStartSize);
}
~tmplProc() {}
void process(const char* tmpl, const uint32_t tmplLen, TmplCb cb) {
char* buf = new char[MAX_BUFFER_SIZE];
char* p = buf;
uint32_t len = 0, pos = 0, i = 0;
uint8_t state = ST_BUF;
bool complete = false;
while (i < tmplLen) {
switch (state) {
default:
DPRINTLN(DBG_DEBUG, F("unknown state"));
break;
case ST_BUF:
if(0 != i) {
buf[pos] = '\0';
mResponse->print(p);
}
pos = 0;
len = ((tmplLen - i) > MAX_BUFFER_SIZE) ? MAX_BUFFER_SIZE : (tmplLen - i);
if((len + i) == tmplLen)
complete = true;
memcpy_P(buf, &tmpl[i], len);
if(len < MAX_BUFFER_SIZE)
buf[len] = '\0';
p = buf;
state = ST_PROC;
break;
case ST_PROC:
if(((pos + MAX_KEY_LEN) >= len) && !complete)
state = ST_BUF;
else if(buf[pos] == '{')
state = ST_START;
break;
case ST_START:
if(buf[pos] == '#') {
if(pos != 0)
buf[pos-1] = '\0';
mResponse->print(p);
p = &buf[pos+1];
state = ST_KEY;
}
else
state = ST_PROC;
break;
case ST_KEY:
if(buf[pos] == '}') {
buf[pos] = '\0';
mResponse->print((cb)(p));
p = &buf[pos+1];
state = ST_PROC;
}
break;
}
if(ST_BUF != state) {
pos++;
i++;
}
}
mResponse->print(p);
delete[] buf;
mRequest->send(mResponse);
}
private:
AsyncWebServerRequest *mRequest;
AsyncResponseStream *mResponse;
};
#endif /*__TMPL_PROC__*/

235
tools/esp8266/web.cpp

@ -17,6 +17,7 @@
#include "html/h/setup_html.h"
#include "html/h/visualization_html.h"
#include "html/h/update_html.h"
#include "html/h/serial_html.h"
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"};
@ -28,7 +29,7 @@ web::web(app *main, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, c
mStat = stat;
mVersion = version;
mWeb = new AsyncWebServer(80);
//mEvts = new AsyncEventSource("/events");
mEvts = new AsyncEventSource("/events");
mApi = new webApi(mWeb, main, sysCfg, config, stat, version);
}
@ -39,31 +40,34 @@ void web::setup(void) {
DPRINTLN(DBG_VERBOSE, F("app::setup-begin"));
mWeb->begin();
DPRINTLN(DBG_VERBOSE, F("app::setup-on"));
mWeb->on("/", HTTP_GET, std::bind(&web::onIndex, this, std::placeholders::_1));
mWeb->on("/style.css", HTTP_GET, std::bind(&web::onCss, this, std::placeholders::_1));
mWeb->on("/api.js", HTTP_GET, std::bind(&web::onApiJs, this, std::placeholders::_1));
mWeb->on("/favicon.ico", HTTP_GET, std::bind(&web::onFavicon, this, std::placeholders::_1));
mWeb->onNotFound ( std::bind(&web::showNotFound, this, std::placeholders::_1));
mWeb->on("/reboot", HTTP_ANY, std::bind(&web::showReboot, this, std::placeholders::_1));
mWeb->on("/erase", HTTP_ANY, std::bind(&web::showErase, this, std::placeholders::_1));
mWeb->on("/factory", HTTP_ANY, std::bind(&web::showFactoryRst, this, std::placeholders::_1));
mWeb->on("/", HTTP_GET, std::bind(&web::onIndex, this, std::placeholders::_1));
mWeb->on("/style.css", HTTP_GET, std::bind(&web::onCss, this, std::placeholders::_1));
mWeb->on("/api.js", HTTP_GET, std::bind(&web::onApiJs, this, std::placeholders::_1));
mWeb->on("/favicon.ico", HTTP_GET, std::bind(&web::onFavicon, this, std::placeholders::_1));
mWeb->onNotFound ( std::bind(&web::showNotFound, this, std::placeholders::_1));
mWeb->on("/reboot", HTTP_ANY, std::bind(&web::showReboot, this, std::placeholders::_1));
mWeb->on("/erase", HTTP_ANY, std::bind(&web::showErase, this, std::placeholders::_1));
mWeb->on("/factory", HTTP_ANY, std::bind(&web::showFactoryRst, this, std::placeholders::_1));
mWeb->on("/setup", HTTP_GET, std::bind(&web::onSetup, this, std::placeholders::_1));
mWeb->on("/save", HTTP_ANY, std::bind(&web::showSave, this, std::placeholders::_1));
mWeb->on("/setup", HTTP_GET, std::bind(&web::onSetup, this, std::placeholders::_1));
mWeb->on("/save", HTTP_ANY, std::bind(&web::showSave, this, std::placeholders::_1));
mWeb->on("/visualization", HTTP_ANY, std::bind(&web::showVisualization, this, std::placeholders::_1));
mWeb->on("/livedata", HTTP_ANY, std::bind(&web::showLiveData, this, std::placeholders::_1));
mWeb->on("/api1", HTTP_POST, std::bind(&web::showWebApi, this, std::placeholders::_1));
mWeb->on("/live", HTTP_ANY, std::bind(&web::onLive, this, std::placeholders::_1));
mWeb->on("/api1", HTTP_POST, std::bind(&web::showWebApi, this, std::placeholders::_1));
mWeb->on("/update", HTTP_GET, std::bind(&web::showUpdateForm, this, std::placeholders::_1));
mWeb->on("/update", HTTP_POST, std::bind(&web::showUpdate, this, std::placeholders::_1),
std::bind(&web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6));
mWeb->on("/update", HTTP_GET, std::bind(&web::onUpdate, this, std::placeholders::_1));
mWeb->on("/update", HTTP_POST, std::bind(&web::showUpdate, this, std::placeholders::_1),
std::bind(&web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6));
mWeb->on("/serial", HTTP_GET, std::bind(&web::onSerial, this, std::placeholders::_1));
//mEvts->onConnect(std::bind(&web::onConnect, this, std::placeholders::_1));
//mWeb->addHandler(mEvts);
mEvts->onConnect(std::bind(&web::onConnect, this, std::placeholders::_1));
mWeb->addHandler(mEvts);
mApi->setup();
registerDebugCb(std::bind(&web::serialCb, this, std::placeholders::_1));
}
@ -74,14 +78,14 @@ void web::loop(void) {
//-----------------------------------------------------------------------------
/*void web::onConnect(AsyncEventSourceClient *client) {
DPRINTLN(DBG_INFO, "onConnect");
void web::onConnect(AsyncEventSourceClient *client) {
DPRINTLN(DBG_VERBOSE, "onConnect");
if(client->lastId())
DPRINTLN(DBG_INFO, "Client reconnected! Last message ID that it got is: " + String(client->lastId()));
DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId()));
client->send("hello!", NULL, millis(), 1000);
}*/
}
//-----------------------------------------------------------------------------
@ -91,11 +95,6 @@ void web::onIndex(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), index_html, index_html_len);
response->addHeader(F("Content-Encoding"), "gzip");
request->send(response);
/*
html.replace(F("{TS}"), String(mConfig->sendInterval) + " ");
html.replace(F("{JS_TS}"), String(mConfig->sendInterval * 1000));
request->send(200, "text/html", html);*/
}
@ -318,149 +317,17 @@ void web::showSave(AsyncWebServerRequest *request) {
//-----------------------------------------------------------------------------
void web::showVisualization(AsyncWebServerRequest *request) {
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));
request->send(200, F("text/html"), html);
}
//-----------------------------------------------------------------------------
void web::showLiveData(AsyncWebServerRequest *request) {
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};
void web::onLive(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLive"));
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>";
}
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>");
request->send(200, F("text/html"), modHtml);
} else {
request->send(200, F("text/html"), modHtml);
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), visualization_html, visualization_html_len);
response->addHeader(F("Content-Encoding"), "gzip");
request->send(response);
}
//-----------------------------------------------------------------------------
void web::showWebApi(AsyncWebServerRequest *request)
{
void web::showWebApi(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("web::showWebApi"));
DPRINTLN(DBG_DEBUG, request->arg("plain"));
const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity.
@ -531,9 +398,13 @@ void web::showWebApi(AsyncWebServerRequest *request)
//-----------------------------------------------------------------------------
void web::showUpdateForm(AsyncWebServerRequest *request) {
tmplProc *proc = new tmplProc(request, 850);
proc->process(update_html, update_html_len, std::bind(&web::showUpdateFormCb, this, std::placeholders::_1));
void web::onUpdate(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onUpdate"));
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), update_html, update_html_len);
response->addHeader(F("Content-Encoding"), "gzip");
request->send(response);
}
@ -580,27 +451,17 @@ void web::showUpdate2(AsyncWebServerRequest *request, String filename, size_t in
//-----------------------------------------------------------------------------
String web::replaceHtmlGenericKeys(char *key) {
if(0 == strncmp(key, "VERSION", 7)) return mVersion;
else if(0 == strncmp(key, "DEVICE", 6)) return mSysCfg->deviceName;
else if(0 == strncmp(key, "IP", 2)) {
if(mMain->getWifiApActive()) return F("http://192.168.1.1");
else return (F("http://") + String(WiFi.localIP().toString()));
}
void web::onSerial(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSerial"));
return "";
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), serial_html, serial_html_len);
response->addHeader(F("Content-Encoding"), "gzip");
request->send(response);
}
//-----------------------------------------------------------------------------
String web::showUpdateFormCb(char *key) {
String generic = replaceHtmlGenericKeys(key);
if(generic.length() == 0) {
if(0 == strncmp(key, "CONTENT", 7))
return F("<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>");
else if(0 == strncmp(key, "HEAD", 4))
return "";
}
return generic;
void web::serialCb(String msg) {
msg.replace("\r\n", "<rn>");
mEvts->send(msg.c_str(), "serial", millis());
}

11
tools/esp8266/web.h

@ -11,7 +11,6 @@
#include "ESPAsyncWebServer.h"
#include "app.h"
#include "webApi.h"
#include "tmplProc.h"
class app;
class webApi;
@ -37,17 +36,17 @@ class web {
void onSetup(AsyncWebServerRequest *request);
void showSave(AsyncWebServerRequest *request);
void showVisualization(AsyncWebServerRequest *request);
void showLiveData(AsyncWebServerRequest *request);
void onLive(AsyncWebServerRequest *request);
void showWebApi(AsyncWebServerRequest *request);
void showUpdateForm(AsyncWebServerRequest *request);
void onUpdate(AsyncWebServerRequest *request);
void showUpdate(AsyncWebServerRequest *request);
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void serialCb(String msg);
private:
String replaceHtmlGenericKeys(char *key);
String showUpdateFormCb(char* key);
void onSerial(AsyncWebServerRequest *request);
AsyncWebServer *mWeb;
AsyncEventSource *mEvts;

54
tools/esp8266/webApi.cpp

@ -59,6 +59,23 @@ void webApi::onApi(AsyncWebServerRequest *request) {
}
//-----------------------------------------------------------------------------
void webApi::getNotFound(JsonObject obj, String url) {
JsonObject ep = obj.createNestedObject("avail_endpoints");
ep[F("system")] = url + F("system");
ep[F("statistics")] = url + F("statistics");
ep[F("inverter/list")] = url + F("inverter/list");
ep[F("mqtt")] = url + F("mqtt");
ep[F("ntp")] = url + F("ntp");
ep[F("pinout")] = url + F("pinout");
ep[F("radio")] = url + F("radio");
ep[F("serial")] = url + F("serial");
ep[F("index")] = url + F("index");
ep[F("setup")] = url + F("setup");
ep[F("live")] = url + F("live");
}
//-----------------------------------------------------------------------------
void webApi::getSystem(JsonObject obj) {
obj[F("ssid")] = mSysCfg->stationSsid;
@ -148,23 +165,6 @@ void webApi::getSerial(JsonObject obj) {
}
//-----------------------------------------------------------------------------
void webApi::getNotFound(JsonObject obj, String url) {
JsonObject ep = obj.createNestedObject("avail_endpoints");
ep[F("system")] = url + F("system");
ep[F("statistics")] = url + F("statistics");
ep[F("inverter/list")] = url + F("inverter/list");
ep[F("mqtt")] = url + F("mqtt");
ep[F("ntp")] = url + F("ntp");
ep[F("pinout")] = url + F("pinout");
ep[F("radio")] = url + F("radio");
ep[F("serial")] = url + F("serial");
ep[F("index")] = url + F("index");
ep[F("setup")] = url + F("setup");
ep[F("live")] = url + F("live");
}
//-----------------------------------------------------------------------------
void webApi::getIndex(JsonObject obj) {
getSystem(obj.createNestedObject(F("system")));
@ -216,7 +216,9 @@ void webApi::getSetup(JsonObject obj) {
//-----------------------------------------------------------------------------
void webApi::getLive(JsonObject obj) {
getSystem(obj.createNestedObject(F("system")));
JsonArray invArr = obj.createNestedArray(F("inverter"));
obj["refresh_interval"] = SEND_INTERVAL;
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};
@ -235,18 +237,16 @@ void webApi::getLive(JsonObject obj) {
JsonArray ch = obj2.createNestedArray("ch");
JsonArray ch0 = ch.createNestedArray();
obj2[F("ch_names")][0] = "AC";
for (uint8_t fld = 0; fld < 11; fld++) {
pos = (iv->getPosByChFld(CH0, list[fld]));
if (0xff != pos) {
JsonObject dat = ch0.createNestedObject();
dat[F("value")] = iv->getValue(pos);
dat[F("unit")] = String(iv->getUnit(pos));
dat[F("name")] = String(iv->getFieldName(pos));
}
ch0[fld] = (0xff != pos) ? iv->getValue(pos) : 0.0;
obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos)) : F("n/a");
obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos)) : F("n/a");
}
for(uint8_t j = 1; j <= iv->channels; j ++) {
obj2[F("ch_names")][j-1] = iv->chName[j];
obj2[F("ch_names")][j] = String(iv->chName[j-1]);
JsonArray cur = ch.createNestedArray();
for (uint8_t k = 0; k < 6; k++) {
switch(k) {
@ -258,9 +258,9 @@ void webApi::getLive(JsonObject obj) {
case 5: pos = (iv->getPosByChFld(j, FLD_IRR)); break;
}
cur[k] = (0xff != pos) ? iv->getValue(pos) : 0.0;
if((0 == j) && (0xff != pos)) {
obj2[F("fld_units")][k] = String(iv->getUnit(pos));
obj2[F("fld_names")][k] = String(iv->getFieldName(pos));
if(1 == j) {
obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos)) : F("n/a");
obj[F("fld_names")][k] = (0xff != pos) ? String(iv->getFieldName(pos)) : F("n/a");
}
}
}

2
tools/esp8266/webApi.h

@ -19,6 +19,7 @@ class webApi {
private:
void onApi(AsyncWebServerRequest *request);
void getNotFound(JsonObject obj, String url);
void getSystem(JsonObject obj);
void getStatistics(JsonObject obj);
@ -29,7 +30,6 @@ class webApi {
void getRadio(JsonObject obj);
void getSerial(JsonObject obj);
void getNotFound(JsonObject obj, String url);
void getIndex(JsonObject obj);
void getSetup(JsonObject obj);
void getLive(JsonObject obj);

Loading…
Cancel
Save