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 3 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) if(mApActive)
DBGPRINTLN(F("192.168.1.1")); DBGPRINTLN(F("192.168.1.1"));
else else
DBGPRINTLN(WiFi.localIP()); DBGPRINTLN(WiFi.localIP().toString());
DPRINTLN(DBG_INFO, F("to configure your device")); DPRINTLN(DBG_INFO, F("to configure your device"));
DPRINTLN(DBG_INFO, F("----------------------------------------\n")); DPRINTLN(DBG_INFO, F("----------------------------------------\n"));
} }

3
tools/esp8266/config.h

@ -108,9 +108,6 @@
// default MQTT topic // default MQTT topic
#define DEF_MQTT_TOPIC "inverter" #define DEF_MQTT_TOPIC "inverter"
// changes the style of "/setup" page, visualized = nicer
#define LIVEDATA_VISUALIZED
#if __has_include("config_override.h") #if __has_include("config_override.h")
#include "config_override.h" #include "config_override.h"
#endif #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); http.send(null);
} }
function p() { function p() {
if(http.readyState == 4) if(http.readyState == 4) {
ptr(JSON.parse(http.responseText)); if(null != http.responseText)
ptr(JSON.parse(http.responseText));
}
} }
} }
@ -60,6 +62,13 @@ function sel(name, opt, selId) {
function div(cl) { function div(cl) {
e = document.createElement('div'); 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; return e;
} }

5
tools/esp8266/html/convert.py

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

29
tools/esp8266/html/index.html

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

18
tools/esp8266/html/update.html

@ -4,7 +4,7 @@
<title>Update</title> <title>Update</title>
<link rel="stylesheet" type="text/css" href="style.css"/> <link rel="stylesheet" type="text/css" href="style.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
{#HEAD} <script type="text/javascript" src="api.js"></script>
</head> </head>
<body> <body>
<h1>Update</h1> <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. 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> </div>
<br/><br/> <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>
<div id="footer"> <div id="footer">
<p class="left">&copy 2022</p> <p class="left">&copy 2022</p>
<p class="left"><a href="{#IP}/">Home</a></p> <p class="left"><a href="/">Home</a></p>
<p class="right">AHOY :: {#VERSION}</p> <p class="right" id="version"></p>
<p class="right"><a href="/reboot">Reboot</a></p>
</div> </div>
<script type="text/javascript">
function parseSys(obj) {
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
}
getAjax("/api/system", parseSys);
</script>
</body> </body>
</html> </html>

127
tools/esp8266/html/visualization.html

@ -1,43 +1,116 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<title>Index - {DEVICE}</title> <title>Live</title>
<link rel="stylesheet" type="text/css" href="style.css"/> <link rel="stylesheet" type="text/css" href="style.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<script type="text/javascript"> <script type="text/javascript" src="api.js"></script>
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>
</head> </head>
<body> <body>
<h1>AHOY - {DEVICE}</h1> <h1>AHOY</h1>
<div id="content" class="content"> <div id="content" class="content">
<div id="livedata"></div> <div id="live"></div>
<p>Every {TS}seconds the values are updated</p> <p>Every <span id="refresh"></span> seconds the values are updated</p>
</div> </div>
<div id="footer"> <div id="footer">
<p class="left">&copy 2022</p> <p class="left">&copy 2022</p>
<p class="left"><a href="/">Home</a></p> <p class="left"><a href="/">Home</a></p>
<p class="right">AHOY :: {VERSION}</p> <p class="right" id="version"></p>
</div> </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> </body>
</html> </html>

19
tools/esp8266/include/dbg.h

@ -10,6 +10,7 @@
#define F(sl) (sl) #define F(sl) (sl)
#endif #endif
#include <functional>
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// available levels // available levels
#define DBG_ERROR 1 #define DBG_ERROR 1
@ -32,14 +33,22 @@
#define DBGPRINTLN(str) #define DBGPRINTLN(str)
#else #else
#ifdef ARDUINO #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 #ifndef DSERIAL
#define DSERIAL Serial #define DSERIAL Serial
#endif #endif
template <class T> //template <class T>
inline void DBGPRINT(T str) { DSERIAL.print(str); } inline void DBGPRINT(String str) { DSERIAL.print(str); if(NULL != mCb) mCb(str); }
template <class T> //template <class T>
inline void DBGPRINTLN(T str) { DBGPRINT(str); DBGPRINT(F("\r\n")); } inline void DBGPRINTLN(String str) { DBGPRINT(str); DBGPRINT(F("\r\n")); }
inline void DHEX(uint8_t b) { inline void DHEX(uint8_t b) {
if( b<0x10 ) DSERIAL.print('0'); if( b<0x10 ) DSERIAL.print('0');
DSERIAL.print(b,HEX); DSERIAL.print(b,HEX);
@ -60,6 +69,8 @@
else if( b<0x10000000 ) DSERIAL.print(F("0")); else if( b<0x10000000 ) DSERIAL.print(F("0"));
DSERIAL.print(b,HEX); DSERIAL.print(b,HEX);
} }
#endif #endif
#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/setup_html.h"
#include "html/h/visualization_html.h" #include "html/h/visualization_html.h"
#include "html/h/update_html.h" #include "html/h/update_html.h"
#include "html/h/serial_html.h"
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"}; 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; mStat = stat;
mVersion = version; mVersion = version;
mWeb = new AsyncWebServer(80); mWeb = new AsyncWebServer(80);
//mEvts = new AsyncEventSource("/events"); mEvts = new AsyncEventSource("/events");
mApi = new webApi(mWeb, main, sysCfg, config, stat, version); mApi = new webApi(mWeb, main, sysCfg, config, stat, version);
} }
@ -39,31 +40,34 @@ void web::setup(void) {
DPRINTLN(DBG_VERBOSE, F("app::setup-begin")); DPRINTLN(DBG_VERBOSE, F("app::setup-begin"));
mWeb->begin(); mWeb->begin();
DPRINTLN(DBG_VERBOSE, F("app::setup-on")); DPRINTLN(DBG_VERBOSE, F("app::setup-on"));
mWeb->on("/", HTTP_GET, std::bind(&web::onIndex, 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("/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("/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->on("/favicon.ico", HTTP_GET, std::bind(&web::onFavicon, this, std::placeholders::_1));
mWeb->onNotFound ( std::bind(&web::showNotFound, 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("/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("/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("/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("/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("/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("/live", HTTP_ANY, std::bind(&web::onLive, 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("/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_GET, std::bind(&web::onUpdate, this, std::placeholders::_1));
mWeb->on("/update", HTTP_POST, std::bind(&web::showUpdate, 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)); 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(); mApi->setup();
registerDebugCb(std::bind(&web::serialCb, this, std::placeholders::_1));
} }
@ -74,14 +78,14 @@ void web::loop(void) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
/*void web::onConnect(AsyncEventSourceClient *client) { void web::onConnect(AsyncEventSourceClient *client) {
DPRINTLN(DBG_INFO, "onConnect"); DPRINTLN(DBG_VERBOSE, "onConnect");
if(client->lastId()) 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); 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); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), index_html, index_html_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
request->send(response); 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) { void web::onLive(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("web::showVisualization")); DPRINTLN(DBG_VERBOSE, F("onLive"));
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};
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_VERBOSE, F("web::showWebApi"));
DPRINTLN(DBG_DEBUG, request->arg("plain")); DPRINTLN(DBG_DEBUG, request->arg("plain"));
const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity. 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) { void web::onUpdate(AsyncWebServerRequest *request) {
tmplProc *proc = new tmplProc(request, 850); DPRINTLN(DBG_VERBOSE, F("onUpdate"));
proc->process(update_html, update_html_len, std::bind(&web::showUpdateFormCb, this, std::placeholders::_1));
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) { void web::onSerial(AsyncWebServerRequest *request) {
if(0 == strncmp(key, "VERSION", 7)) return mVersion; DPRINTLN(DBG_VERBOSE, F("onSerial"));
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()));
}
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) { void web::serialCb(String msg) {
String generic = replaceHtmlGenericKeys(key); msg.replace("\r\n", "<rn>");
if(generic.length() == 0) { mEvts->send(msg.c_str(), "serial", millis());
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;
} }

11
tools/esp8266/web.h

@ -11,7 +11,6 @@
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
#include "app.h" #include "app.h"
#include "webApi.h" #include "webApi.h"
#include "tmplProc.h"
class app; class app;
class webApi; class webApi;
@ -37,17 +36,17 @@ class web {
void onSetup(AsyncWebServerRequest *request); void onSetup(AsyncWebServerRequest *request);
void showSave(AsyncWebServerRequest *request); void showSave(AsyncWebServerRequest *request);
void showVisualization(AsyncWebServerRequest *request); void onLive(AsyncWebServerRequest *request);
void showLiveData(AsyncWebServerRequest *request);
void showWebApi(AsyncWebServerRequest *request); void showWebApi(AsyncWebServerRequest *request);
void showUpdateForm(AsyncWebServerRequest *request); void onUpdate(AsyncWebServerRequest *request);
void showUpdate(AsyncWebServerRequest *request); void showUpdate(AsyncWebServerRequest *request);
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void serialCb(String msg);
private: private:
String replaceHtmlGenericKeys(char *key); void onSerial(AsyncWebServerRequest *request);
String showUpdateFormCb(char* key);
AsyncWebServer *mWeb; AsyncWebServer *mWeb;
AsyncEventSource *mEvts; 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) { void webApi::getSystem(JsonObject obj) {
obj[F("ssid")] = mSysCfg->stationSsid; 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) { void webApi::getIndex(JsonObject obj) {
getSystem(obj.createNestedObject(F("system"))); getSystem(obj.createNestedObject(F("system")));
@ -216,7 +216,9 @@ void webApi::getSetup(JsonObject obj) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void webApi::getLive(JsonObject obj) { void webApi::getLive(JsonObject obj) {
getSystem(obj.createNestedObject(F("system")));
JsonArray invArr = obj.createNestedArray(F("inverter")); 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}; 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 ch = obj2.createNestedArray("ch");
JsonArray ch0 = ch.createNestedArray(); JsonArray ch0 = ch.createNestedArray();
obj2[F("ch_names")][0] = "AC";
for (uint8_t fld = 0; fld < 11; fld++) { for (uint8_t fld = 0; fld < 11; fld++) {
pos = (iv->getPosByChFld(CH0, list[fld])); pos = (iv->getPosByChFld(CH0, list[fld]));
if (0xff != pos) { ch0[fld] = (0xff != pos) ? iv->getValue(pos) : 0.0;
JsonObject dat = ch0.createNestedObject(); obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos)) : F("n/a");
dat[F("value")] = iv->getValue(pos); obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos)) : F("n/a");
dat[F("unit")] = String(iv->getUnit(pos));
dat[F("name")] = String(iv->getFieldName(pos));
}
} }
for(uint8_t j = 1; j <= iv->channels; j ++) { 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(); JsonArray cur = ch.createNestedArray();
for (uint8_t k = 0; k < 6; k++) { for (uint8_t k = 0; k < 6; k++) {
switch(k) { switch(k) {
@ -258,9 +258,9 @@ void webApi::getLive(JsonObject obj) {
case 5: pos = (iv->getPosByChFld(j, FLD_IRR)); break; case 5: pos = (iv->getPosByChFld(j, FLD_IRR)); break;
} }
cur[k] = (0xff != pos) ? iv->getValue(pos) : 0.0; cur[k] = (0xff != pos) ? iv->getValue(pos) : 0.0;
if((0 == j) && (0xff != pos)) { if(1 == j) {
obj2[F("fld_units")][k] = String(iv->getUnit(pos)); obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos)) : F("n/a");
obj2[F("fld_names")][k] = String(iv->getFieldName(pos)); 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: private:
void onApi(AsyncWebServerRequest *request); void onApi(AsyncWebServerRequest *request);
void getNotFound(JsonObject obj, String url);
void getSystem(JsonObject obj); void getSystem(JsonObject obj);
void getStatistics(JsonObject obj); void getStatistics(JsonObject obj);
@ -29,7 +30,6 @@ class webApi {
void getRadio(JsonObject obj); void getRadio(JsonObject obj);
void getSerial(JsonObject obj); void getSerial(JsonObject obj);
void getNotFound(JsonObject obj, String url);
void getIndex(JsonObject obj); void getIndex(JsonObject obj);
void getSetup(JsonObject obj); void getSetup(JsonObject obj);
void getLive(JsonObject obj); void getLive(JsonObject obj);

Loading…
Cancel
Save