improved html and navi, navi is visible even when API dies #660

reduced maximum allowed JSON size for API to 6000Bytes #660
small fix: output command at `prepareDevInformCmd` #692
improved inverter handling for MQTT #671
This commit is contained in:
lumapu 2023-02-25 01:40:25 +01:00
parent 9ef2df21fa
commit 4f0d365211
21 changed files with 279 additions and 377 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@
.vscode/extensions.json
src/config/config_override.h
src/web/html/h/*
src/web/html/tmp/*
/**/Debug
/**/v16/*
*.db

View file

@ -2,6 +2,12 @@
(starting from release version `0.5.66`)
## 0.5.91
* improved html and navi, navi is visible even when API dies #660
* reduced maximum allowed JSON size for API to 6000Bytes #660
* small fix: output command at `prepareDevInformCmd` #692
* improved inverter handling #671
## 0.5.90
* merged PR #684, #698, #705
* webserial minor overflow fix #660

View file

@ -21,12 +21,6 @@ void app::setup() {
resetSystem();
/*DBGPRINTLN("--- start");
DBGPRINTLN(String(ESP.getFreeHeap()));
DBGPRINTLN(String(ESP.getHeapFragmentation()));
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
mSettings.setup();
mSettings.getPtr(mConfig);
DPRINT(DBG_INFO, F("Settings valid: "));
@ -50,6 +44,7 @@ void app::setup() {
#endif
mSys.addInverters(&mConfig->inst);
mPayload.setup(this, &mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
mPayload.enableSerialDebug(mConfig->serial.debug);
mPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1));
@ -57,10 +52,10 @@ void app::setup() {
mMiPayload.setup(this, &mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
mMiPayload.enableSerialDebug(mConfig->serial.debug);
/*DBGPRINTLN("--- after payload");
DBGPRINTLN("--- after payload");
DBGPRINTLN(String(ESP.getFreeHeap()));
DBGPRINTLN(String(ESP.getHeapFragmentation()));
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
if(!mSys.Radio.isChipConnected())
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
@ -90,10 +85,10 @@ void app::setup() {
regularTickers();
/*DBGPRINTLN("--- end setup");
DBGPRINTLN("--- end setup");
DBGPRINTLN(String(ESP.getFreeHeap()));
DBGPRINTLN(String(ESP.getHeapFragmentation()));
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
}
//-----------------------------------------------------------------------------

View file

@ -250,7 +250,7 @@ class settings {
return false;
}
DynamicJsonDocument json(4500);
DynamicJsonDocument json(5500);
JsonObject root = json.to<JsonObject>();
jsonWifi(root.createNestedObject(F("wifi")), true);
jsonNrf(root.createNestedObject(F("nrf")), true);

View file

@ -13,7 +13,7 @@
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 5
#define VERSION_PATCH 90
#define VERSION_PATCH 91
//-------------------------------------
typedef struct {

View file

@ -157,7 +157,8 @@ class HmPayload {
uint8_t cmd = iv->getQueuedCmd();
DPRINT(DBG_INFO, F("(#"));
DBGPRINT(String(iv->id));
DBGPRINT(F(") prepareDevInformCmd")); // + String(cmd, HEX));
DBGPRINT(F(") prepareDevInformCmd 0x"));
DBGPRINTLN(String(cmd, HEX));
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
mPayload[iv->id].txCmd = cmd;
}

View file

@ -419,17 +419,19 @@ class PubMqtt {
iv = mSys->getInverterByPos(id);
if (NULL == iv)
continue; // skip to next inverter
if (!iv->config->enabled)
continue; // skip to next inverter
rec = iv->getRecordStruct(RealTimeRunData_Debug);
// inverter status
uint8_t status = MQTT_STATUS_NOT_AVAIL_NOT_PROD;
if (iv->config->enabled) {
if (iv->isAvailable(*mUtcTimestamp))
if (iv->isAvailable(*mUtcTimestamp)) {
anyAvail = true;
status = (iv->isProducing(*mUtcTimestamp)) ? MQTT_STATUS_AVAIL_PROD : MQTT_STATUS_AVAIL_NOT_PROD;
}
else // inverter is enabled but not available
allAvail = false;
}
if(mLastIvState[id] != status) {
// if status changed from producing to not producing send last data immediately
@ -439,11 +441,11 @@ class PubMqtt {
mLastIvState[id] = status;
changed = true;
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, mqttStr[MQTT_STR_AVAILABLE]);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name);
snprintf(val, 40, "%d", status);
publish(topic, val, true);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, mqttStr[MQTT_STR_LAST_SUCCESS]);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->config->name);
snprintf(val, 40, "%d", iv->getLastTs(rec));
publish(topic, val, true);
}
@ -451,7 +453,7 @@ class PubMqtt {
if(changed) {
snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE)));
publish(subtopics[MQTT_STATUS], val, true);
publish("status", val, true);
}
return anyAvail;
@ -474,6 +476,7 @@ class PubMqtt {
char topic[7 + MQTT_TOPIC_LEN], val[40];
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
if (iv->getLastTs(rec) > 0) {
for (uint8_t i = 0; i < rec->length; i++) {
bool retained = false;
if (curInfoCmd == RealTimeRunData_Debug) {
@ -494,6 +497,7 @@ class PubMqtt {
yield();
}
}
}
void sendIvData() {
bool anyAvail = processIvStatus();
@ -512,19 +516,25 @@ class PubMqtt {
uint8_t curInfoCmd = mSendList.front();
if ((curInfoCmd != RealTimeRunData_Debug) || !RTRDataHasBeenSent) { // send RTR Data only once
bool sendTotals = (curInfoCmd == RealTimeRunData_Debug);
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
Inverter<> *iv = mSys->getInverterByPos(id);
if (NULL == iv)
continue; // skip to next inverter
if (!iv->config->enabled)
continue; // skip to next inverter
// send RTR Data only if status is available
if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_AVAIL_PROD == mLastIvState[id]))
if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_NOT_AVAIL_NOT_PROD != mLastIvState[id]))
sendData(iv, curInfoCmd);
// calculate total values for RealTimeRunData_Debug
if (curInfoCmd == RealTimeRunData_Debug) {
if (sendTotals) {
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
sendTotals &= (iv->getLastTs(rec) > 0);
if (sendTotals) {
for (uint8_t i = 0; i < rec->length; i++) {
if (CH0 == rec->assign[i].ch) {
switch (rec->assign[i].fieldId) {
@ -543,11 +553,12 @@ class PubMqtt {
}
}
}
}
}
yield();
}
}
if (curInfoCmd == RealTimeRunData_Debug) {
if (sendTotals) {
uint8_t fieldId;
for (uint8_t i = 0; i < 4; i++) {
switch (i) {
@ -565,7 +576,7 @@ class PubMqtt {
fieldId = FLD_PDC;
break;
}
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", mqttStr[MQTT_STR_TOTAL], fields[fieldId]);
snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]);
snprintf(val, 40, "%g", ah::round3(total[i]));
publish(topic, val, true);
}

View file

@ -71,7 +71,7 @@ class RestApi {
mHeapFrag = ESP.getHeapFragmentation();
#endif
AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192);
AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000);
JsonObject root = response->getRoot();
String path = request->url().substring(5);
@ -83,7 +83,6 @@ class RestApi {
else if(path == "reboot") getReboot(root);
else if(path == "statistics") getStatistics(root);
else if(path == "inverter/list") getInverterList(root);
else if(path == "menu") getMenu(root);
else if(path == "index") getIndex(root);
else if(path == "setup") getSetup(root);
else if(path == "setup/networks") getNetworks(root);
@ -183,10 +182,13 @@ class RestApi {
}
void getGeneric(JsonObject obj) {
obj[F("version")] = String(mApp->getVersion());
obj[F("build")] = String(AUTO_GIT_HASH);
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
obj[F("ts_uptime")] = mApp->getUptime();
obj[F("menu_prot")] = mApp->getProtection();
obj[F("menu_maskH")] = ((mConfig->sys.protectionMask >> 8) & 0xff);
obj[F("menu_maskL")] = ((mConfig->sys.protectionMask ) & 0xff);
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
#if defined(ESP32)
obj[F("esp_type")] = F("ESP32");
@ -244,7 +246,6 @@ class RestApi {
}
void getHtmlSystem(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getSysInfo(obj.createNestedObject(F("system")));
getGeneric(obj.createNestedObject(F("generic")));
obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
@ -252,7 +253,6 @@ class RestApi {
}
void getHtmlLogout(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic")));
obj[F("refresh")] = 3;
obj[F("refresh_url")] = "/";
@ -260,7 +260,6 @@ class RestApi {
}
void getHtmlSave(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic")));
obj[F("refresh")] = 2;
obj[F("refresh_url")] = "/setup";
@ -268,7 +267,6 @@ class RestApi {
}
void getReboot(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic")));
obj[F("refresh")] = 10;
obj[F("refresh_url")] = "/";
@ -377,54 +375,9 @@ class RestApi {
obj[F("pinDisp1")] = mConfig->plugin.display.pin1;
}
void getMenu(JsonObject obj) {
uint8_t i = 0;
uint16_t mask = (mApp->getProtection()) ? mConfig->sys.protectionMask : 0;
if(!CHECK_MASK(mask, PROT_MASK_LIVE)) {
obj[F("name")][i] = "Live";
obj[F("link")][i++] = "/live";
}
if(!CHECK_MASK(mask, PROT_MASK_SERIAL)) {
obj[F("name")][i] = "Serial / Control";
obj[F("link")][i++] = "/serial";
}
if(!CHECK_MASK(mask, PROT_MASK_SETUP)) {
obj[F("name")][i] = "Settings";
obj[F("link")][i++] = "/setup";
}
obj[F("name")][i++] = "-";
obj[F("name")][i] = "REST API";
obj[F("link")][i] = "/api";
obj[F("trgt")][i++] = "_blank";
obj[F("name")][i++] = "-";
if(!CHECK_MASK(mask, PROT_MASK_UPDATE)) {
obj[F("name")][i] = "Update";
obj[F("link")][i++] = "/update";
}
if(!CHECK_MASK(mask, PROT_MASK_SYSTEM)) {
obj[F("name")][i] = "System";
obj[F("link")][i++] = "/system";
}
obj[F("name")][i++] = "-";
obj[F("name")][i] = "Documentation";
obj[F("link")][i] = "https://ahoydtu.de";
obj[F("trgt")][i++] = "_blank";
if(strlen(mConfig->sys.adminPwd) > 0) {
obj[F("name")][i++] = "-";
if(mApp->getProtection()) {
obj[F("name")][i] = "Login";
obj[F("link")][i++] = "/login";
} else {
obj[F("name")][i] = "Logout";
obj[F("link")][i++] = "/logout";
}
}
}
void getIndex(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic")));
obj[F("ts_now")] = mApp->getTimestamp();
obj[F("ts_sunrise")] = mApp->getSunrise();
obj[F("ts_sunset")] = mApp->getSunset();
@ -473,10 +426,9 @@ class RestApi {
}
void getSetup(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic")));
getSysInfo(obj.createNestedObject(F("system")));
getInverterList(obj.createNestedObject(F("inverter")));
//getInverterList(obj.createNestedObject(F("inverter")));
getMqtt(obj.createNestedObject(F("mqtt")));
getNtp(obj.createNestedObject(F("ntp")));
getSun(obj.createNestedObject(F("sun")));
@ -492,7 +444,6 @@ class RestApi {
}
void getLive(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic")));
JsonArray invArr = obj.createNestedArray(F("inverter"));
obj["refresh_interval"] = mConfig->nrf.sendInterval;

View file

@ -38,18 +38,21 @@ function topnav() {
toggle("topnav");
}
function parseMenu(obj) {
var e = document.getElementById("topnav");
e.innerHTML = "";
for(var i = 0; i < obj["name"].length; i ++) {
if(obj["name"][i] == "-")
e.appendChild(span("", ["seperator"]));
else {
var l = link(obj["link"][i], obj["name"][i], obj["trgt"][i]);
if(obj["link"][i] == window.location.pathname)
function parseNav(obj) {
for(i = 0; i < 7; i++) {
var l = document.getElementById("nav"+i);
if(window.location.pathname == "/" + l.href.split('/').pop())
l.classList.add("active");
e.appendChild(l);
}
if(obj["menu_protEn"]) {
if(obj["menu_prot"]) {
if((((obj["menu_mask"] >> i) & 0x01) == 0x01) || (1 == i))
l.classList.remove("hide");
} else if(0 == i)
l.classList.remove("hide");
} else if(i > 1)
l.classList.remove("hide");
}
}

View file

@ -2,10 +2,82 @@ import re
import os
import gzip
import glob
import shutil
import pkg_resources
from datetime import date
from pathlib import Path
from dulwich import porcelain
def convert2Header(inFile):
required_pkgs = {'dulwich'}
installed_pkgs = {pkg.key for pkg in pkg_resources.working_set}
missing_pkgs = required_pkgs - installed_pkgs
if missing_pkgs:
env.Execute('"$PYTHONEXE" -m pip install dulwich')
def get_git_sha():
try:
build_version = porcelain.describe('../../../') # refers to the repository root dir
except:
build_version = "g0000000"
build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version[1:] + "\\\""
#print ("Firmware Revision: " + build_version)
return (build_flag)
def readVersion(path):
f = open(path, "r")
lines = f.readlines()
f.close()
today = date.today()
search = ["_MAJOR", "_MINOR", "_PATCH"]
version = today.strftime("%y%m%d") + "_ahoy_"
ver = ""
for line in lines:
if(line.find("VERSION_") != -1):
for s in search:
p = line.find(s)
if(p != -1):
version += line[p+13:].rstrip() + "."
ver += line[p+13:].rstrip() + "."
return ver[:-1]
def htmlParts(file, header, nav, footer, version):
p = "";
f = open(file, "r")
lines = f.readlines()
f.close();
f = open(header, "r")
h = f.read().strip()
f.close()
f = open(nav, "r")
n = f.read().strip()
f.close()
f = open(footer, "r")
fo = f.read().strip()
f.close()
for line in lines:
line = line.replace("{#HTML_HEADER}", h)
line = line.replace("{#HTML_NAV}", n)
line = line.replace("{#HTML_FOOTER}", fo)
p += line
#placeholders
link = '<a target="_blank" href="https://github.com/lumapu/ahoy/commits/' + get_git_sha() + '">GIT SHA: ' + get_git_sha() + ' :: ' + version + '</a>'
p = p.replace("{#VERSION}", version)
p = p.replace("{#VERSION_GIT}", link)
f = open("tmp/" + file, "w")
f.write(p);
f.close();
return p
def convert2Header(inFile, version):
fileType = inFile.split(".")[1]
define = inFile.split(".")[0].upper()
define2 = inFile.split(".")[1].upper()
@ -17,10 +89,15 @@ def convert2Header(inFile):
Path("html/h").mkdir(exist_ok=True)
else:
outName = "h/" + inFileVarName + ".h"
Path("h").mkdir(exist_ok=True)
data = ""
if fileType == "ico":
f = open(inFile, "rb")
data = f.read()
f.close()
else:
if fileType == "html":
data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", version)
else:
f = open(inFile, "r")
data = f.read()
@ -53,13 +130,17 @@ def convert2Header(inFile):
f.close()
# delete all files in the 'h' dir
dir = 'h'
wd = 'h'
if os.getcwd()[-4:] != "html":
dir = "web/html/" + dir
wd = "web/html/" + wd
if os.path.exists(dir):
for f in os.listdir(dir):
os.remove(os.path.join(dir, f))
if os.path.exists(wd):
for f in os.listdir(wd):
os.remove(os.path.join(wd, f))
wd += "/tmp"
if os.path.exists(wd):
for f in os.listdir(wd):
os.remove(os.path.join(wd, f))
# grab all files with following extensions
if os.getcwd()[-4:] != "html":
@ -69,6 +150,11 @@ files_grabbed = []
for files in types:
files_grabbed.extend(glob.glob(files))
Path("h").mkdir(exist_ok=True)
Path("tmp").mkdir(exist_ok=True) # created to check if webpages are valid with all replacements
shutil.copyfile("style.css", "tmp/style.css")
version = readVersion("../../defines.h")
# go throw the array
for val in files_grabbed:
convert2Header(val)
convert2Header(val, version)

View file

@ -0,0 +1,16 @@
<div id="footer">
<div class="left">
<a href="https://ahoydtu.de" target="_blank">AhoyDTU &copy 2023</a>
<ul>
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
</ul>
</div>
<div class="right">
<ul>
<li>{#VERSION}</li>
<li><span id="esp_type"></span></li>
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
</ul>
</div>
</div>

View file

@ -0,0 +1,3 @@
<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>

View file

@ -0,0 +1,27 @@
<div class="topnav">
<a href="/" class="title">AhoyDTU</a>
<a href="javascript:void(0);" class="icon" onclick="topnav()">
<span></span>
<span></span>
<span></span>
</a>
<div id="topnav" class="mobile">
<div id="topnav" class="mobile">
<a id="nav2" class="hide" href="/live">Live</a>
<a id="nav3" class="hide" href="/serial">Serial / Control</a>
<a id="nav4" class="hide" href="/setup">Settings</a>
<span class="seperator"></span>
<a id="nav5" class="hide" href="/update">Update</a>
<a id="nav6" class="hide" href="/system">System</a>
<span class="seperator"></span>
<a id="nav7" href="/api" target="_blank">REST API</a>
<a id="nav8" href="https://ahoydtu.de" target="_blank">Documentation</a>
<span class="seperator"></span>
<a id="nav0" class="hide" href="/login">Login</a>
<a id="nav1" class="hide" href="/logout">Logout</a>
</div>
</div>
<div id="wifiicon" class="info"></div>
</div>

View file

@ -2,36 +2,12 @@
<html>
<head>
<title>Index</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>
{#HTML_HEADER}
</head>
<body>
<div class="topnav">
<a href="/" class="title">AhoyDTU</a>
<a href="javascript:void(0);" class="icon" onclick="topnav()">
<span></span>
<span></span>
<span></span>
</a>
<div id="topnav" class="hide"></div>
<div id="wifiicon" class="info"></div>
</div>
{#HTML_NAV}
<div id="wrapper">
<div id="content">
<script>
function promptFunction() {
var Text = prompt("This project was started from https://www.mikrocontroller.net/topic/525778 this discussion.\n\n" +
"The Hoymiles protocol was decrypted through the voluntary efforts of many participants. ahoy, among others, was developed based on this work.\n" +
"The software was developed to the best of our knowledge and belief. Nevertheless, no liability can be accepted for a malfunction or guarantee loss of the inverter.\n\n" +
"Ahoy is freely available. If you paid money for the software, you probably got ripped off.\n\nPlease type in 'YeS', you are accept our Disclaim. You should then save your config.", "");
if (Text != "YeS")
promptFunction();
else
return true;
}
</script>
<p>
<span class="des">Uptime: </span><span id="uptime"></span><br/>
<span class="des">ESP-Time: </span><span id="date"></span>
@ -60,22 +36,7 @@
</div>
</div>
</div>
<div id="footer">
<div class="left">
<a href="https://ahoydtu.de" target="_blank">AhoyDTU &copy 2023</a>
<ul>
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
</ul>
</div>
<div class="right">
<ul>
<li><span id="version"></span></li>
<li><span id="esp_type"></span></li>
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
</ul>
</div>
</div>
{#HTML_FOOTER}
<script type="text/javascript">
var exeOnce = true;
var tickCnt = 0;
@ -108,10 +69,10 @@
function parseGeneric(obj) {
// Disclaimer
//if(obj["disclaimer"] == false) sessionStorage.setItem("gDisclaimer", promptFunction());
if(exeOnce){
/*if(exeOnce){
parseVersion(obj);
parseESP(obj);
}
}*/
parseRssi(obj);
}
@ -203,7 +164,7 @@
document.getElementById("iv").replaceChildren(p);
}
function parseWarnInfo(warn, success, version) {
function parseWarnInfo(warn, success) {
var p = div(["none"]);
for(var w of warn) {
p.append(svg(iconWarn, 20, 20, "#F70", "icon"), span(w), br());
@ -216,10 +177,10 @@
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span(commInfo), br());
if(null != release) {
if(getVerInt(version) < getVerInt(release))
if(getVerInt("{#VERSION}") < getVerInt(release))
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("Update available, current released version: " + release), br());
else if(getVerInt(version) > getVerInt(release))
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("You are using development version " + version +". In case of issues you may want to try the current stable release: " + release), br());
else if(getVerInt("{#VERSION}") > getVerInt(release))
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("You are using development version {#VERSION}. In case of issues you may want to try the current stable release: " + release), br());
else
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("You are using the current stable release: " + release), br());
}
@ -239,11 +200,11 @@
function parse(obj) {
if(null != obj) {
if(exeOnce)
parseMenu(obj["menu"]);
parseNav(obj["generic"]);
parseGeneric(obj["generic"]);
parseSys(obj);
parseIv(obj["inverter"]);
parseWarnInfo(obj["warnings"], obj["infos"], obj["generic"]["version"]);
parseWarnInfo(obj["warnings"], obj["infos"]);
if(exeOnce) {
window.setInterval("tick()", 1000);
exeOnce = false;

View file

@ -2,9 +2,7 @@
<html>
<head>
<title>Login</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>
{#HTML_HEADER}
</head>
<body>
<div id="wrapper">
@ -18,25 +16,6 @@
</div>
</div>
</div>
<div id="footer">
<div class="left">
<a href="https://ahoydtu.de" target="_blank">AhoyDTU &copy 2023</a>
<ul>
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
</ul>
</div>
<div class="right">
<span id="version"></span><br/><br/>
<a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a>
</div>
</div>
<script type="text/javascript">
function parse(obj) {
parseVersion(obj["general"]);
}
getAjax("/api/generic", parse);
</script>
{#HTML_FOOTER}
</body>
</html>

View file

@ -2,21 +2,10 @@
<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>
{#HTML_HEADER}
</head>
<body>
<div class="topnav">
<a href="/" class="title">AhoyDTU</a>
<a href="javascript:void(0);" class="icon" onclick="topnav()">
<span></span>
<span></span>
<span></span>
</a>
<div id="topnav" class="hide"></div>
<div id="wifiicon" class="info"></div>
</div>
{#HTML_NAV}
<div id="wrapper">
<div id="content">
<div class="serial">
@ -53,22 +42,7 @@
</div>
</div>
</div>
<div id="footer">
<div class="left">
<a href="https://ahoydtu.de" target="_blank">AhoyDTU &copy 2023</a>
<ul>
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
</ul>
</div>
<div class="right">
<ul>
<li><span id="version"></span></li>
<li><span id="esp_type"></span></li>
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
</ul>
</div>
</div>
{#HTML_FOOTER}
<script type="text/javascript">
var mAutoScroll = true;
var con = document.getElementById("serial");
@ -87,7 +61,7 @@
parseRssi(obj);
if(true == exeOnce) {
parseVersion(obj);
parseNav(obj);
parseESP(obj);
window.setInterval("getAjax('/api/generic', parseGeneric)", 10000);
exeOnce = false;
@ -96,7 +70,6 @@
}
function parse(root) {
parseMenu(root["menu"]);
select = document.getElementById('InvID');
if(null == root) return;

View file

@ -2,9 +2,7 @@
<html>
<head>
<title>Setup</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>
{#HTML_HEADER}
<script type="text/javascript">
function load() {
for(it of document.getElementsByClassName("s_collapsible")) {
@ -18,16 +16,7 @@
</script>
</head>
<body onload="load()">
<div class="topnav">
<a href="/" class="title">AhoyDTU</a>
<a href="javascript:void(0);" class="icon" onclick="topnav()">
<span></span>
<span></span>
<span></span>
</a>
<div id="topnav" class="hide"></div>
<div id="wifiicon" class="info"></div>
</div>
{#HTML_NAV}
<div id="wrapper">
<div id="content">
<form method="post" action="/save">
@ -224,22 +213,7 @@
</form>
</div>
</div>
<div id="footer">
<div class="left">
<a href="https://ahoydtu.de" target="_blank">AhoyDTU &copy 2023</a>
<ul>
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
</ul>
</div>
<div class="right">
<ul>
<li><span id="version"></span></li>
<li><span id="esp_type"></span></li>
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
</ul>
</div>
</div>
{#HTML_FOOTER}
<script type="text/javascript">
var highestId = 0;
var maxInv = 0;
@ -426,7 +400,7 @@
iv.append(
lbl(id + "Name", "Name*"),
inp(id + "Name", obj["name"], 32, ["text"], null, "text", "[A-Za-z0-9./#$%&=+_-]+", "Invalid input")
inp(id + "Name", obj["name"], 16, ["text"], null, "text", "[A-Za-z0-9./#$%&=+_-]+", "Invalid input")
);
for(var j of [
@ -479,7 +453,7 @@
}
function parseGeneric(obj) {
parseVersion(obj);
parseNav(obj);
parseESP(obj);
parseRssi(obj);
}
@ -568,11 +542,9 @@
function parse(root) {
if(null != root) {
parseMenu(root["menu"]);
parseSys(root["system"]);
parseGeneric(root["generic"]);
parseStaticIp(root["static_ip"]);
parseIv(root["inverter"]);
parseMqtt(root["mqtt"]);
parseNtp(root["ntp"]);
parseSun(root["sun"]);
@ -580,6 +552,7 @@
parseRadio(root["radio"]);
parseSerial(root["serial"]);
parseDisplay(root["display"], root["system"]["esp_type"]);
getAjax("/api/inverter/list", parseIv);
}
}

View file

@ -50,6 +50,10 @@ h2 {
top: 5px;
}
.topnav .mobile {
display: none;
}
svg.icon {
vertical-align: middle;
display: inline-block;
@ -131,7 +135,7 @@ span.seperator {
}
.hide {
display: none;
display: none !important;
}
@media only screen and (min-width: 992px) {
@ -152,7 +156,7 @@ span.seperator {
padding-left: 24px !important;
}
.topnav .hide {
.topnav .mobile {
display: block;
}

View file

@ -2,21 +2,10 @@
<html>
<head>
<title>System</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>
{#HTML_HEADER}
</head>
<body>
<div class="topnav">
<a href="/" class="title">AhoyDTU</a>
<a href="javascript:void(0);" class="icon" onclick="topnav()">
<span></span>
<span></span>
<span></span>
</a>
<div id="topnav" class="hide"></div>
<div id="wifiicon" class="info"></div>
</div>
{#HTML_NAV}
<div id="wrapper">
<div id="content">
<pre id="stat"></pre>
@ -26,25 +15,10 @@
<div id="html" class="mt-3 mb-3"></div>
</div>
</div>
<div id="footer">
<div class="left">
<a href="https://ahoydtu.de" target="_blank">AhoyDTU &copy 2023</a>
<ul>
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
</ul>
</div>
<div class="right">
<ul>
<li><span id="version"></span></li>
<li><span id="esp_type"></span></li>
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
</ul>
</div>
</div>
{#HTML_FOOTER}
<script type="text/javascript">
function parseGeneric(obj) {
parseVersion(obj);
parseNav(obj);
parseESP(obj);
parseRssi(obj);
}
@ -123,7 +97,6 @@
function parse(obj) {
if(null != obj) {
parseMenu(obj["menu"]);
parseGeneric(obj["generic"]);
if(null != obj["refresh"]) {

View file

@ -2,21 +2,10 @@
<html>
<head>
<title>Update</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>
{#HTML_HEADER}
</head>
<body>
<div class="topnav">
<a href="/" class="title">AhoyDTU</a>
<a href="javascript:void(0);" class="icon" onclick="topnav()">
<span></span>
<span></span>
<span></span>
</a>
<div id="topnav" class="hide"></div>
<div id="wifiicon" class="info"></div>
</div>
{#HTML_NAV}
<div id="wrapper">
<div id="content">
<form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
@ -25,43 +14,21 @@
</form>
</div>
</div>
<div id="footer">
<div class="left">
<a href="https://ahoydtu.de" target="_blank">AhoyDTU &copy 2023</a>
<ul>
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
</ul>
</div>
<div class="right">
<ul>
<li><span id="version"></span></li>
<li><span id="esp_type"></span></li>
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
</ul>
</div>
</div>
{#HTML_FOOTER}
<script type="text/javascript">
function parseGeneric(obj) {
parseVersion(obj);
parseNav(obj);
parseESP(obj);
parseRssi(obj);
}
function parse(obj) {
if(null != obj) {
parseMenu(obj["menu"]);
parseGeneric(obj["generic"]);
}
}
function hide() {
document.getElementById("form").submit();
var e = document.getElementById("content");
e.replaceChildren(span("update started"));
}
getAjax("/api/index", parse);
getAjax("/api/generic", parseGeneric);
</script>
</body>
</html>

View file

@ -2,50 +2,24 @@
<html>
<head>
<title>Live</title>
<link rel="stylesheet" type="text/css" href="style.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
{#HTML_HEADER}
<meta name="apple-mobile-web-app-capable" content="yes">
<script type="text/javascript" src="api.js"></script>
</head>
<body>
<div class="topnav">
<a href="/" class="title">AhoyDTU</a>
<a href="javascript:void(0);" class="icon" onclick="topnav()">
<span></span>
<span></span>
<span></span>
</a>
<div id="topnav" class="hide"></div>
<div id="wifiicon" class="info"></div>
</div>
{#HTML_NAV}
<div id="wrapper">
<div id="content">
<div id="live"></div>
<p>Every <span id="refresh"></span> seconds the values are updated</p>
</div>
</div>
<div id="footer">
<div class="left">
<a href="https://ahoydtu.de" target="_blank">AhoyDTU &copy 2023</a>
<ul>
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
<li><a href="https://github.com/lumapu/ahoy" target="_blank">Github</a></li>
</ul>
</div>
<div class="right">
<ul>
<li><span id="version"></span></li>
<li><span id="esp_type"></span></li>
<li><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de" target="_blank" >CC BY-NC-SA 3.0</a></li>
</ul>
</div>
</div>
{#HTML_FOOTER}
<script type="text/javascript">
var exeOnce = true;
function parseGeneric(obj) {
if(true == exeOnce){
parseVersion(obj);
parseNav(obj);
parseESP(obj);
}
parseRssi(obj);
@ -133,8 +107,6 @@
function parse(obj) {
if(null != obj) {
if(true == exeOnce)
parseMenu(obj["menu"]);
parseGeneric(obj["generic"]);
parseIv(obj["inverter"], obj);
document.getElementById("refresh").innerHTML = obj["refresh_interval"];