Browse Source

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
pull/729/head
lumapu 2 years ago
parent
commit
4f0d365211
  1. 1
      .gitignore
  2. 6
      src/CHANGES.md
  3. 15
      src/app.cpp
  4. 2
      src/config/settings.h
  5. 2
      src/defines.h
  6. 3
      src/hm/hmPayload.h
  7. 99
      src/publisher/pubMqtt.h
  8. 61
      src/web/RestApi.h
  9. 27
      src/web/html/api.js
  10. 110
      src/web/html/convert.py
  11. 16
      src/web/html/includes/footer.html
  12. 3
      src/web/html/includes/header.html
  13. 27
      src/web/html/includes/nav.html
  14. 61
      src/web/html/index.html
  15. 25
      src/web/html/login.html
  16. 35
      src/web/html/serial.html
  17. 39
      src/web/html/setup.html
  18. 8
      src/web/html/style.css
  19. 35
      src/web/html/system.html
  20. 43
      src/web/html/update.html
  21. 36
      src/web/html/visualization.html

1
.gitignore

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

6
src/CHANGES.md

@ -2,6 +2,12 @@
(starting from release version `0.5.66`) (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 ## 0.5.90
* merged PR #684, #698, #705 * merged PR #684, #698, #705
* webserial minor overflow fix #660 * webserial minor overflow fix #660

15
src/app.cpp

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

2
src/config/settings.h

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

2
src/defines.h

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

3
src/hm/hmPayload.h

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

99
src/publisher/pubMqtt.h

@ -406,7 +406,7 @@ class PubMqtt {
return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : stateClasses[deviceFieldAssignment[pos].stateClsId]; return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : stateClasses[deviceFieldAssignment[pos].stateClsId];
} }
bool processIvStatus() { bool processIvStatus() {
// returns true if any inverter is available // returns true if any inverter is available
bool allAvail = true; // shows if all enabled inverters are available bool allAvail = true; // shows if all enabled inverters are available
bool anyAvail = false; // shows if at least one enabled inverter is available bool anyAvail = false; // shows if at least one enabled inverter is available
@ -419,17 +419,19 @@ class PubMqtt {
iv = mSys->getInverterByPos(id); iv = mSys->getInverterByPos(id);
if (NULL == iv) if (NULL == iv)
continue; // skip to next inverter continue; // skip to next inverter
if (!iv->config->enabled)
continue; // skip to next inverter
rec = iv->getRecordStruct(RealTimeRunData_Debug); rec = iv->getRecordStruct(RealTimeRunData_Debug);
// inverter status // inverter status
uint8_t status = MQTT_STATUS_NOT_AVAIL_NOT_PROD; 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; status = (iv->isProducing(*mUtcTimestamp)) ? MQTT_STATUS_AVAIL_PROD : MQTT_STATUS_AVAIL_NOT_PROD;
else // inverter is enabled but not available
allAvail = false;
} }
else // inverter is enabled but not available
allAvail = false;
if(mLastIvState[id] != status) { if(mLastIvState[id] != status) {
// if status changed from producing to not producing send last data immediately // if status changed from producing to not producing send last data immediately
@ -439,11 +441,11 @@ class PubMqtt {
mLastIvState[id] = status; mLastIvState[id] = status;
changed = true; 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); snprintf(val, 40, "%d", status);
publish(topic, val, true); 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)); snprintf(val, 40, "%d", iv->getLastTs(rec));
publish(topic, val, true); publish(topic, val, true);
} }
@ -451,7 +453,7 @@ class PubMqtt {
if(changed) { if(changed) {
snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); 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; return anyAvail;
@ -474,24 +476,26 @@ class PubMqtt {
char topic[7 + MQTT_TOPIC_LEN], val[40]; char topic[7 + MQTT_TOPIC_LEN], val[40];
record_t<> *rec = iv->getRecordStruct(curInfoCmd); record_t<> *rec = iv->getRecordStruct(curInfoCmd);
for (uint8_t i = 0; i < rec->length; i++) { if (iv->getLastTs(rec) > 0) {
bool retained = false; for (uint8_t i = 0; i < rec->length; i++) {
if (curInfoCmd == RealTimeRunData_Debug) { bool retained = false;
switch (rec->assign[i].fieldId) { if (curInfoCmd == RealTimeRunData_Debug) {
case FLD_YT: switch (rec->assign[i].fieldId) {
case FLD_YD: case FLD_YT:
if ((rec->assign[i].ch == CH0) && (!iv->isProducing(*mUtcTimestamp))) // avoids returns to 0 on restart case FLD_YD:
continue; if ((rec->assign[i].ch == CH0) && (!iv->isProducing(*mUtcTimestamp))) // avoids returns to 0 on restart
retained = true; continue;
break; retained = true;
break;
}
} }
}
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]);
snprintf(val, 40, "%g", ah::round3(iv->getValue(i, rec))); snprintf(val, 40, "%g", ah::round3(iv->getValue(i, rec)));
publish(topic, val, retained); publish(topic, val, retained);
yield(); yield();
}
} }
} }
@ -512,42 +516,49 @@ class PubMqtt {
uint8_t curInfoCmd = mSendList.front(); uint8_t curInfoCmd = mSendList.front();
if ((curInfoCmd != RealTimeRunData_Debug) || !RTRDataHasBeenSent) { // send RTR Data only once if ((curInfoCmd != RealTimeRunData_Debug) || !RTRDataHasBeenSent) { // send RTR Data only once
bool sendTotals = (curInfoCmd == RealTimeRunData_Debug);
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
Inverter<> *iv = mSys->getInverterByPos(id); Inverter<> *iv = mSys->getInverterByPos(id);
if (NULL == iv) if (NULL == iv)
continue; // skip to next inverter continue; // skip to next inverter
if (!iv->config->enabled)
continue; // skip to next inverter
// send RTR Data only if status is available // 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); sendData(iv, curInfoCmd);
// calculate total values for RealTimeRunData_Debug // calculate total values for RealTimeRunData_Debug
if (curInfoCmd == RealTimeRunData_Debug) { if (sendTotals) {
record_t<> *rec = iv->getRecordStruct(curInfoCmd); record_t<> *rec = iv->getRecordStruct(curInfoCmd);
for (uint8_t i = 0; i < rec->length; i++) { sendTotals &= (iv->getLastTs(rec) > 0);
if (CH0 == rec->assign[i].ch) { if (sendTotals) {
switch (rec->assign[i].fieldId) { for (uint8_t i = 0; i < rec->length; i++) {
case FLD_PAC: if (CH0 == rec->assign[i].ch) {
total[0] += iv->getValue(i, rec); switch (rec->assign[i].fieldId) {
break; case FLD_PAC:
case FLD_YT: total[0] += iv->getValue(i, rec);
total[1] += iv->getValue(i, rec); break;
break; case FLD_YT:
case FLD_YD: total[1] += iv->getValue(i, rec);
total[2] += iv->getValue(i, rec); break;
break; case FLD_YD:
case FLD_PDC: total[2] += iv->getValue(i, rec);
total[3] += iv->getValue(i, rec); break;
break; case FLD_PDC:
total[3] += iv->getValue(i, rec);
break;
}
} }
} }
} }
yield();
} }
yield();
} }
if (curInfoCmd == RealTimeRunData_Debug) { if (sendTotals) {
uint8_t fieldId; uint8_t fieldId;
for (uint8_t i = 0; i < 4; i++) { for (uint8_t i = 0; i < 4; i++) {
switch (i) { switch (i) {
@ -565,7 +576,7 @@ class PubMqtt {
fieldId = FLD_PDC; fieldId = FLD_PDC;
break; 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])); snprintf(val, 40, "%g", ah::round3(total[i]));
publish(topic, val, true); publish(topic, val, true);
} }

61
src/web/RestApi.h

@ -71,7 +71,7 @@ class RestApi {
mHeapFrag = ESP.getHeapFragmentation(); mHeapFrag = ESP.getHeapFragmentation();
#endif #endif
AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
String path = request->url().substring(5); String path = request->url().substring(5);
@ -83,7 +83,6 @@ class RestApi {
else if(path == "reboot") getReboot(root); else if(path == "reboot") getReboot(root);
else if(path == "statistics") getStatistics(root); else if(path == "statistics") getStatistics(root);
else if(path == "inverter/list") getInverterList(root); else if(path == "inverter/list") getInverterList(root);
else if(path == "menu") getMenu(root);
else if(path == "index") getIndex(root); else if(path == "index") getIndex(root);
else if(path == "setup") getSetup(root); else if(path == "setup") getSetup(root);
else if(path == "setup/networks") getNetworks(root); else if(path == "setup/networks") getNetworks(root);
@ -183,10 +182,13 @@ class RestApi {
} }
void getGeneric(JsonObject obj) { void getGeneric(JsonObject obj) {
obj[F("version")] = String(mApp->getVersion());
obj[F("build")] = String(AUTO_GIT_HASH); obj[F("build")] = String(AUTO_GIT_HASH);
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI(); obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
obj[F("ts_uptime")] = mApp->getUptime(); 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) #if defined(ESP32)
obj[F("esp_type")] = F("ESP32"); obj[F("esp_type")] = F("ESP32");
@ -244,7 +246,6 @@ class RestApi {
} }
void getHtmlSystem(JsonObject obj) { void getHtmlSystem(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getSysInfo(obj.createNestedObject(F("system"))); getSysInfo(obj.createNestedObject(F("system")));
getGeneric(obj.createNestedObject(F("generic"))); 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>"); 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) { void getHtmlLogout(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
obj[F("refresh")] = 3; obj[F("refresh")] = 3;
obj[F("refresh_url")] = "/"; obj[F("refresh_url")] = "/";
@ -260,7 +260,6 @@ class RestApi {
} }
void getHtmlSave(JsonObject obj) { void getHtmlSave(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
obj[F("refresh")] = 2; obj[F("refresh")] = 2;
obj[F("refresh_url")] = "/setup"; obj[F("refresh_url")] = "/setup";
@ -268,7 +267,6 @@ class RestApi {
} }
void getReboot(JsonObject obj) { void getReboot(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
obj[F("refresh")] = 10; obj[F("refresh")] = 10;
obj[F("refresh_url")] = "/"; obj[F("refresh_url")] = "/";
@ -377,54 +375,9 @@ class RestApi {
obj[F("pinDisp1")] = mConfig->plugin.display.pin1; 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) { void getIndex(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
obj[F("ts_now")] = mApp->getTimestamp(); obj[F("ts_now")] = mApp->getTimestamp();
obj[F("ts_sunrise")] = mApp->getSunrise(); obj[F("ts_sunrise")] = mApp->getSunrise();
obj[F("ts_sunset")] = mApp->getSunset(); obj[F("ts_sunset")] = mApp->getSunset();
@ -473,10 +426,9 @@ class RestApi {
} }
void getSetup(JsonObject obj) { void getSetup(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
getSysInfo(obj.createNestedObject(F("system"))); getSysInfo(obj.createNestedObject(F("system")));
getInverterList(obj.createNestedObject(F("inverter"))); //getInverterList(obj.createNestedObject(F("inverter")));
getMqtt(obj.createNestedObject(F("mqtt"))); getMqtt(obj.createNestedObject(F("mqtt")));
getNtp(obj.createNestedObject(F("ntp"))); getNtp(obj.createNestedObject(F("ntp")));
getSun(obj.createNestedObject(F("sun"))); getSun(obj.createNestedObject(F("sun")));
@ -492,7 +444,6 @@ class RestApi {
} }
void getLive(JsonObject obj) { void getLive(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(obj.createNestedObject(F("generic")));
JsonArray invArr = obj.createNestedArray(F("inverter")); JsonArray invArr = obj.createNestedArray(F("inverter"));
obj["refresh_interval"] = mConfig->nrf.sendInterval; obj["refresh_interval"] = mConfig->nrf.sendInterval;

27
src/web/html/api.js

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

110
src/web/html/convert.py

@ -2,10 +2,82 @@ import re
import os import os
import gzip import gzip
import glob import glob
import shutil
import pkg_resources
from datetime import date
from pathlib import Path from pathlib import Path
from dulwich import porcelain
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();
def convert2Header(inFile): 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] fileType = inFile.split(".")[1]
define = inFile.split(".")[0].upper() define = inFile.split(".")[0].upper()
define2 = inFile.split(".")[1].upper() define2 = inFile.split(".")[1].upper()
@ -17,14 +89,19 @@ def convert2Header(inFile):
Path("html/h").mkdir(exist_ok=True) Path("html/h").mkdir(exist_ok=True)
else: else:
outName = "h/" + inFileVarName + ".h" outName = "h/" + inFileVarName + ".h"
Path("h").mkdir(exist_ok=True)
data = ""
if fileType == "ico": if fileType == "ico":
f = open(inFile, "rb") f = open(inFile, "rb")
data = f.read()
f.close()
else: else:
f = open(inFile, "r") if fileType == "html":
data = f.read() data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", version)
f.close() else:
f = open(inFile, "r")
data = f.read()
f.close()
if fileType == "css": if fileType == "css":
data = data.replace('\n', '') data = data.replace('\n', '')
@ -53,13 +130,17 @@ def convert2Header(inFile):
f.close() f.close()
# delete all files in the 'h' dir # delete all files in the 'h' dir
dir = 'h' wd = 'h'
if os.getcwd()[-4:] != "html": if os.getcwd()[-4:] != "html":
dir = "web/html/" + dir wd = "web/html/" + wd
if os.path.exists(dir): if os.path.exists(wd):
for f in os.listdir(dir): for f in os.listdir(wd):
os.remove(os.path.join(dir, f)) 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 # grab all files with following extensions
if os.getcwd()[-4:] != "html": if os.getcwd()[-4:] != "html":
@ -69,6 +150,11 @@ files_grabbed = []
for files in types: for files in types:
files_grabbed.extend(glob.glob(files)) 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 # go throw the array
for val in files_grabbed: for val in files_grabbed:
convert2Header(val) convert2Header(val, version)

16
src/web/html/includes/footer.html

@ -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>

3
src/web/html/includes/header.html

@ -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>

27
src/web/html/includes/nav.html

@ -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>

61
src/web/html/index.html

@ -2,36 +2,12 @@
<html> <html>
<head> <head>
<title>Index</title> <title>Index</title>
<link rel="stylesheet" type="text/css" href="style.css"/> {#HTML_HEADER}
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="api.js"></script>
</head> </head>
<body> <body>
<div class="topnav"> {#HTML_NAV}
<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>
<div id="wrapper"> <div id="wrapper">
<div id="content"> <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> <p>
<span class="des">Uptime: </span><span id="uptime"></span><br/> <span class="des">Uptime: </span><span id="uptime"></span><br/>
<span class="des">ESP-Time: </span><span id="date"></span> <span class="des">ESP-Time: </span><span id="date"></span>
@ -60,22 +36,7 @@
</div> </div>
</div> </div>
</div> </div>
<div id="footer"> {#HTML_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>
<script type="text/javascript"> <script type="text/javascript">
var exeOnce = true; var exeOnce = true;
var tickCnt = 0; var tickCnt = 0;
@ -108,10 +69,10 @@
function parseGeneric(obj) { function parseGeneric(obj) {
// Disclaimer // Disclaimer
//if(obj["disclaimer"] == false) sessionStorage.setItem("gDisclaimer", promptFunction()); //if(obj["disclaimer"] == false) sessionStorage.setItem("gDisclaimer", promptFunction());
if(exeOnce){ /*if(exeOnce){
parseVersion(obj); parseVersion(obj);
parseESP(obj); parseESP(obj);
} }*/
parseRssi(obj); parseRssi(obj);
} }
@ -203,7 +164,7 @@
document.getElementById("iv").replaceChildren(p); document.getElementById("iv").replaceChildren(p);
} }
function parseWarnInfo(warn, success, version) { function parseWarnInfo(warn, success) {
var p = div(["none"]); var p = div(["none"]);
for(var w of warn) { for(var w of warn) {
p.append(svg(iconWarn, 20, 20, "#F70", "icon"), span(w), br()); 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()); p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span(commInfo), br());
if(null != release) { 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()); p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("Update available, current released version: " + release), br());
else if(getVerInt(version) > getVerInt(release)) 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()); 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 else
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("You are using the current stable release: " + release), br()); 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) { function parse(obj) {
if(null != obj) { if(null != obj) {
if(exeOnce) if(exeOnce)
parseMenu(obj["menu"]); parseNav(obj["generic"]);
parseGeneric(obj["generic"]); parseGeneric(obj["generic"]);
parseSys(obj); parseSys(obj);
parseIv(obj["inverter"]); parseIv(obj["inverter"]);
parseWarnInfo(obj["warnings"], obj["infos"], obj["generic"]["version"]); parseWarnInfo(obj["warnings"], obj["infos"]);
if(exeOnce) { if(exeOnce) {
window.setInterval("tick()", 1000); window.setInterval("tick()", 1000);
exeOnce = false; exeOnce = false;

25
src/web/html/login.html

@ -2,9 +2,7 @@
<html> <html>
<head> <head>
<title>Login</title> <title>Login</title>
<link rel="stylesheet" type="text/css" href="style.css"/> {#HTML_HEADER}
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="api.js"></script>
</head> </head>
<body> <body>
<div id="wrapper"> <div id="wrapper">
@ -18,25 +16,6 @@
</div> </div>
</div> </div>
</div> </div>
<div id="footer"> {#HTML_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>
</body> </body>
</html> </html>

35
src/web/html/serial.html

@ -2,21 +2,10 @@
<html> <html>
<head> <head>
<title>Serial Console</title> <title>Serial Console</title>
<link rel="stylesheet" type="text/css" href="style.css"/> {#HTML_HEADER}
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="api.js"></script>
</head> </head>
<body> <body>
<div class="topnav"> {#HTML_NAV}
<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>
<div id="wrapper"> <div id="wrapper">
<div id="content"> <div id="content">
<div class="serial"> <div class="serial">
@ -53,22 +42,7 @@
</div> </div>
</div> </div>
</div> </div>
<div id="footer"> {#HTML_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>
<script type="text/javascript"> <script type="text/javascript">
var mAutoScroll = true; var mAutoScroll = true;
var con = document.getElementById("serial"); var con = document.getElementById("serial");
@ -87,7 +61,7 @@
parseRssi(obj); parseRssi(obj);
if(true == exeOnce) { if(true == exeOnce) {
parseVersion(obj); parseNav(obj);
parseESP(obj); parseESP(obj);
window.setInterval("getAjax('/api/generic', parseGeneric)", 10000); window.setInterval("getAjax('/api/generic', parseGeneric)", 10000);
exeOnce = false; exeOnce = false;
@ -96,7 +70,6 @@
} }
function parse(root) { function parse(root) {
parseMenu(root["menu"]);
select = document.getElementById('InvID'); select = document.getElementById('InvID');
if(null == root) return; if(null == root) return;

39
src/web/html/setup.html

@ -2,9 +2,7 @@
<html> <html>
<head> <head>
<title>Setup</title> <title>Setup</title>
<link rel="stylesheet" type="text/css" href="style.css"/> {#HTML_HEADER}
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="api.js"></script>
<script type="text/javascript"> <script type="text/javascript">
function load() { function load() {
for(it of document.getElementsByClassName("s_collapsible")) { for(it of document.getElementsByClassName("s_collapsible")) {
@ -18,16 +16,7 @@
</script> </script>
</head> </head>
<body onload="load()"> <body onload="load()">
<div class="topnav"> {#HTML_NAV}
<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>
<div id="wrapper"> <div id="wrapper">
<div id="content"> <div id="content">
<form method="post" action="/save"> <form method="post" action="/save">
@ -224,22 +213,7 @@
</form> </form>
</div> </div>
</div> </div>
<div id="footer"> {#HTML_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>
<script type="text/javascript"> <script type="text/javascript">
var highestId = 0; var highestId = 0;
var maxInv = 0; var maxInv = 0;
@ -426,7 +400,7 @@
iv.append( iv.append(
lbl(id + "Name", "Name*"), 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 [ for(var j of [
@ -479,7 +453,7 @@
} }
function parseGeneric(obj) { function parseGeneric(obj) {
parseVersion(obj); parseNav(obj);
parseESP(obj); parseESP(obj);
parseRssi(obj); parseRssi(obj);
} }
@ -568,11 +542,9 @@
function parse(root) { function parse(root) {
if(null != root) { if(null != root) {
parseMenu(root["menu"]);
parseSys(root["system"]); parseSys(root["system"]);
parseGeneric(root["generic"]); parseGeneric(root["generic"]);
parseStaticIp(root["static_ip"]); parseStaticIp(root["static_ip"]);
parseIv(root["inverter"]);
parseMqtt(root["mqtt"]); parseMqtt(root["mqtt"]);
parseNtp(root["ntp"]); parseNtp(root["ntp"]);
parseSun(root["sun"]); parseSun(root["sun"]);
@ -580,6 +552,7 @@
parseRadio(root["radio"]); parseRadio(root["radio"]);
parseSerial(root["serial"]); parseSerial(root["serial"]);
parseDisplay(root["display"], root["system"]["esp_type"]); parseDisplay(root["display"], root["system"]["esp_type"]);
getAjax("/api/inverter/list", parseIv);
} }
} }

8
src/web/html/style.css

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

35
src/web/html/system.html

@ -2,21 +2,10 @@
<html> <html>
<head> <head>
<title>System</title> <title>System</title>
<link rel="stylesheet" type="text/css" href="style.css"/> {#HTML_HEADER}
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="api.js"></script>
</head> </head>
<body> <body>
<div class="topnav"> {#HTML_NAV}
<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>
<div id="wrapper"> <div id="wrapper">
<div id="content"> <div id="content">
<pre id="stat"></pre> <pre id="stat"></pre>
@ -26,25 +15,10 @@
<div id="html" class="mt-3 mb-3"></div> <div id="html" class="mt-3 mb-3"></div>
</div> </div>
</div> </div>
<div id="footer"> {#HTML_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>
<script type="text/javascript"> <script type="text/javascript">
function parseGeneric(obj) { function parseGeneric(obj) {
parseVersion(obj); parseNav(obj);
parseESP(obj); parseESP(obj);
parseRssi(obj); parseRssi(obj);
} }
@ -123,7 +97,6 @@
function parse(obj) { function parse(obj) {
if(null != obj) { if(null != obj) {
parseMenu(obj["menu"]);
parseGeneric(obj["generic"]); parseGeneric(obj["generic"]);
if(null != obj["refresh"]) { if(null != obj["refresh"]) {

43
src/web/html/update.html

@ -2,21 +2,10 @@
<html> <html>
<head> <head>
<title>Update</title> <title>Update</title>
<link rel="stylesheet" type="text/css" href="style.css"/> {#HTML_HEADER}
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="api.js"></script>
</head> </head>
<body> <body>
<div class="topnav"> {#HTML_NAV}
<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>
<div id="wrapper"> <div id="wrapper">
<div id="content"> <div id="content">
<form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8"> <form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
@ -25,43 +14,21 @@
</form> </form>
</div> </div>
</div> </div>
<div id="footer"> {#HTML_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>
<script type="text/javascript"> <script type="text/javascript">
function parseGeneric(obj) { function parseGeneric(obj) {
parseVersion(obj); parseNav(obj);
parseESP(obj); parseESP(obj);
parseRssi(obj); parseRssi(obj);
} }
function parse(obj) {
if(null != obj) {
parseMenu(obj["menu"]);
parseGeneric(obj["generic"]);
}
}
function hide() { function hide() {
document.getElementById("form").submit(); document.getElementById("form").submit();
var e = document.getElementById("content"); var e = document.getElementById("content");
e.replaceChildren(span("update started")); e.replaceChildren(span("update started"));
} }
getAjax("/api/index", parse); getAjax("/api/generic", parseGeneric);
</script> </script>
</body> </body>
</html> </html>

36
src/web/html/visualization.html

@ -2,50 +2,24 @@
<html> <html>
<head> <head>
<title>Live</title> <title>Live</title>
<link rel="stylesheet" type="text/css" href="style.css"/> {#HTML_HEADER}
<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" src="api.js"></script>
</head> </head>
<body> <body>
<div class="topnav"> {#HTML_NAV}
<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>
<div id="wrapper"> <div id="wrapper">
<div id="content"> <div id="content">
<div id="live"></div> <div id="live"></div>
<p>Every <span id="refresh"></span> seconds the values are updated</p> <p>Every <span id="refresh"></span> seconds the values are updated</p>
</div> </div>
</div> </div>
<div id="footer"> {#HTML_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>
<script type="text/javascript"> <script type="text/javascript">
var exeOnce = true; var exeOnce = true;
function parseGeneric(obj) { function parseGeneric(obj) {
if(true == exeOnce){ if(true == exeOnce){
parseVersion(obj); parseNav(obj);
parseESP(obj); parseESP(obj);
} }
parseRssi(obj); parseRssi(obj);
@ -133,8 +107,6 @@
function parse(obj) { function parse(obj) {
if(null != obj) { if(null != obj) {
if(true == exeOnce)
parseMenu(obj["menu"]);
parseGeneric(obj["generic"]); parseGeneric(obj["generic"]);
parseIv(obj["inverter"], obj); parseIv(obj["inverter"], obj);
document.getElementById("refresh").innerHTML = obj["refresh_interval"]; document.getElementById("refresh").innerHTML = obj["refresh_interval"];

Loading…
Cancel
Save