mirror of https://github.com/lumapu/ahoy.git
Browse Source
* converted /update to xhr * started web serial console /serial * /save does not work yet - not debuggedpull/283/head
16 changed files with 306 additions and 391 deletions
@ -0,0 +1,3 @@ |
|||||
|
#include "dbg.h" |
||||
|
|
||||
|
DBG_CB mCb = NULL; |
@ -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">© 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> |
@ -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">© 2022</p> |
<p class="left">© 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> |
||||
|
@ -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__*/ |
|
Loading…
Reference in new issue