mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-16 10:26:10 +02:00
Merge branch 'development03' into dev03
This commit is contained in:
commit
637459ab9e
19 changed files with 665 additions and 247 deletions
|
@ -209,24 +209,32 @@ time_t ahoywifi::getNtpTime(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void ahoywifi::scanAvailNetworks(void) {
|
||||||
|
int n = WiFi.scanComplete();
|
||||||
|
if(n == -2)
|
||||||
|
WiFi.scanNetworks(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void ahoywifi::getAvailNetworks(JsonObject obj) {
|
void ahoywifi::getAvailNetworks(JsonObject obj) {
|
||||||
JsonArray nets = obj.createNestedArray("networks");
|
JsonArray nets = obj.createNestedArray("networks");
|
||||||
|
|
||||||
int n = WiFi.scanComplete();
|
int n = WiFi.scanComplete();
|
||||||
if(n == -2) {
|
if(n > 0) {
|
||||||
WiFi.scanNetworks(true);
|
int sort[n];
|
||||||
} else if(n) {
|
for (int i = 0; i < n; i++)
|
||||||
|
sort[i] = i;
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
for (int j = i + 1; j < n; j++)
|
||||||
|
if (WiFi.RSSI(sort[j]) > WiFi.RSSI(sort[i]))
|
||||||
|
std::swap(sort[i], sort[j]);
|
||||||
for (int i = 0; i < n; ++i) {
|
for (int i = 0; i < n; ++i) {
|
||||||
nets[i]["ssid"] = WiFi.SSID(i);
|
nets[i]["ssid"] = WiFi.SSID(sort[i]);
|
||||||
nets[i]["rssi"] = WiFi.RSSI(i);
|
nets[i]["rssi"] = WiFi.RSSI(sort[i]);
|
||||||
// TODO: does github workflow use another version of this library?
|
|
||||||
// ahoywifi.cpp:223:38: error: 'class WiFiClass' has no member named 'isHidden'
|
|
||||||
//nets[i]["hidden"] = WiFi.isHidden(i) ? true : false;
|
|
||||||
}
|
}
|
||||||
WiFi.scanDelete();
|
WiFi.scanDelete();
|
||||||
if(WiFi.scanComplete() == -2)
|
|
||||||
WiFi.scanNetworks(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ class ahoywifi {
|
||||||
bool setupStation(uint32_t timeout);
|
bool setupStation(uint32_t timeout);
|
||||||
bool getApActive(void);
|
bool getApActive(void);
|
||||||
time_t getNtpTime(void);
|
time_t getNtpTime(void);
|
||||||
|
void scanAvailNetworks(void);
|
||||||
void getAvailNetworks(JsonObject obj);
|
void getAvailNetworks(JsonObject obj);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -23,6 +23,7 @@ app::app() {
|
||||||
loadDefaultConfig();
|
loadDefaultConfig();
|
||||||
|
|
||||||
mSys = new HmSystemType();
|
mSys = new HmSystemType();
|
||||||
|
mSys->enableDebug();
|
||||||
mShouldReboot = false;
|
mShouldReboot = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +171,23 @@ void app::loop(void) {
|
||||||
|
|
||||||
mMqtt.sendMsg("uptime", val);
|
mMqtt.sendMsg("uptime", val);
|
||||||
|
|
||||||
|
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||||
|
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||||
|
if(NULL != iv) {
|
||||||
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
char topic[32 + MAX_NAME_LENGTH], val[32];
|
||||||
|
if (!iv->isAvailable(mUtcTimestamp, rec) && !iv->isProducing(mUtcTimestamp, rec)){
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
|
||||||
|
snprintf(val, 32, DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED);
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name);
|
||||||
|
snprintf(val, 32, "0");
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef __MQTT_TEST__
|
#ifdef __MQTT_TEST__
|
||||||
// für einfacheren Test mit MQTT, den MQTT abschnitt in 10 Sekunden wieder ausführen
|
// für einfacheren Test mit MQTT, den MQTT abschnitt in 10 Sekunden wieder ausführen
|
||||||
mMqttTicker = mMqttInterval - 10;
|
mMqttTicker = mMqttInterval - 10;
|
||||||
|
@ -487,19 +505,6 @@ void app::processPayload(bool retransmit) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mMqttActive) {
|
|
||||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
|
||||||
char topic[32 + MAX_NAME_LENGTH], val[32];
|
|
||||||
if (!iv->isAvailable(mUtcTimestamp, rec) && !iv->isProducing(mUtcTimestamp, rec)) {
|
|
||||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
|
|
||||||
snprintf(val, 32, DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED);
|
|
||||||
mMqtt.sendMsg(topic, val);
|
|
||||||
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name);
|
|
||||||
snprintf(val, 32, "0");
|
|
||||||
mMqtt.sendMsg(topic, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -626,6 +631,12 @@ bool app::getWifiApActive(void) {
|
||||||
return mWifi->getApActive();
|
return mWifi->getApActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void app::scanAvailNetworks(void) {
|
||||||
|
mWifi->scanAvailNetworks();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void app::getAvailNetworks(JsonObject obj) {
|
void app::getAvailNetworks(JsonObject obj) {
|
||||||
mWifi->getAvailNetworks(obj);
|
mWifi->getAvailNetworks(obj);
|
||||||
|
@ -880,6 +891,7 @@ void app::setupMqtt(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void app::resetPayload(Inverter<> *iv) {
|
void app::resetPayload(Inverter<> *iv) {
|
||||||
DPRINTLN(DBG_INFO, "resetPayload: id: " + String(iv->id));
|
DPRINTLN(DBG_INFO, "resetPayload: id: " + String(iv->id));
|
||||||
|
@ -892,6 +904,8 @@ void app::resetPayload(Inverter<> *iv) {
|
||||||
mPayload[iv->id].ts = mUtcTimestamp;
|
mPayload[iv->id].ts = mUtcTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
void app::calculateSunriseSunset() {
|
void app::calculateSunriseSunset() {
|
||||||
// Source: https://en.wikipedia.org/wiki/Sunrise_equation#Complete_calculation_on_Earth
|
// Source: https://en.wikipedia.org/wiki/Sunrise_equation#Complete_calculation_on_Earth
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ class app {
|
||||||
void saveValues(void);
|
void saveValues(void);
|
||||||
void resetPayload(Inverter<>* iv);
|
void resetPayload(Inverter<>* iv);
|
||||||
bool getWifiApActive(void);
|
bool getWifiApActive(void);
|
||||||
|
void scanAvailNetworks(void);
|
||||||
void getAvailNetworks(JsonObject obj);
|
void getAvailNetworks(JsonObject obj);
|
||||||
|
|
||||||
uint8_t getIrqPin(void) {
|
uint8_t getIrqPin(void) {
|
||||||
|
@ -171,6 +172,7 @@ class app {
|
||||||
inline bool mqttIsConnected(void) { return mMqtt.isConnected(); }
|
inline bool mqttIsConnected(void) { return mMqtt.isConnected(); }
|
||||||
inline bool getSettingsValid(void) { return mSettingsValid; }
|
inline bool getSettingsValid(void) { return mSettingsValid; }
|
||||||
inline bool getRebootRequestState(void) { return mShowRebootRequest; }
|
inline bool getRebootRequestState(void) { return mShowRebootRequest; }
|
||||||
|
inline uint32_t getMqttTxCnt(void) { return mMqtt.getTxCnt(); }
|
||||||
|
|
||||||
HmSystemType *mSys;
|
HmSystemType *mSys;
|
||||||
bool mShouldReboot;
|
bool mShouldReboot;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 5
|
#define VERSION_MINOR 5
|
||||||
#define VERSION_PATCH 22
|
#define VERSION_PATCH 25
|
||||||
|
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
|
|
@ -1,4 +1,33 @@
|
||||||
function toggle(id, hide) {
|
/**
|
||||||
|
* GENERIC FUNCTIONS
|
||||||
|
*/
|
||||||
|
|
||||||
|
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)
|
||||||
|
l.classList.add("active");
|
||||||
|
e.appendChild(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseVersion(obj) {
|
||||||
|
document.getElementById("version").appendChild(
|
||||||
|
link("https://github.com/lumapu/ahoy/commits/" + obj["build"], "Git SHA: " + obj["build"] + " :: " + obj["version"], "_blank")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHide(id, hide) {
|
||||||
var elm = document.getElementById(id);
|
var elm = document.getElementById(id);
|
||||||
if(hide) {
|
if(hide) {
|
||||||
if(!elm.classList.contains("hide"))
|
if(!elm.classList.contains("hide"))
|
||||||
|
@ -8,6 +37,15 @@ function toggle(id, hide) {
|
||||||
elm.classList.remove('hide');
|
elm.classList.remove('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function toggle(id) {
|
||||||
|
var e = document.getElementById(id);
|
||||||
|
if(!e.classList.contains("hide"))
|
||||||
|
e.classList.add("hide");
|
||||||
|
else
|
||||||
|
e.classList.remove('hide');
|
||||||
|
}
|
||||||
|
|
||||||
function getAjax(url, ptr, method="GET", json=null) {
|
function getAjax(url, ptr, method="GET", json=null) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
if(xhr != null) {
|
if(xhr != null) {
|
||||||
|
@ -27,6 +65,10 @@ function getAjax(url, ptr, method="GET", json=null) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CREATE DOM FUNCTIONS
|
||||||
|
*/
|
||||||
|
|
||||||
function des(val) {
|
function des(val) {
|
||||||
e = document.createElement('p');
|
e = document.createElement('p');
|
||||||
e.classList.add("subdes");
|
e.classList.add("subdes");
|
||||||
|
@ -68,6 +110,13 @@ function sel(name, opt, selId) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selDelAllOpt(sel) {
|
||||||
|
var i, l = sel.options.length - 1;
|
||||||
|
for(i = l; i >= 0; i--) {
|
||||||
|
sel.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function opt(val, html) {
|
function opt(val, html) {
|
||||||
o = document.createElement('option');
|
o = document.createElement('option');
|
||||||
o.value = val;
|
o.value = val;
|
||||||
|
@ -93,3 +142,13 @@ function span(val, cl=null, id=null) {
|
||||||
function br() {
|
function br() {
|
||||||
return document.createElement('br');
|
return document.createElement('br');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function link(dst, text, target=null) {
|
||||||
|
var a = document.createElement('a');
|
||||||
|
var t = document.createTextNode(text);
|
||||||
|
a.href = dst;
|
||||||
|
if(null != target)
|
||||||
|
a.target = target;
|
||||||
|
a.appendChild(t);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
|
@ -7,9 +7,18 @@
|
||||||
<script type="text/javascript" src="api.js"></script>
|
<script type="text/javascript" src="api.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>AHOY</h1>
|
<div class="topnav">
|
||||||
<div id="content" class="content">
|
<a href="/" class="title">AhoyDTU</a>
|
||||||
<SCRIPT>
|
<a href="javascript:void(0);" class="icon" onclick="topnav()">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<div id="topnav" class="hide"></div>
|
||||||
|
</div>
|
||||||
|
<div id="wrapper">
|
||||||
|
<div id="content">
|
||||||
|
<script>
|
||||||
function promptFunction() {
|
function promptFunction() {
|
||||||
var Text = prompt("This project was started from https://www.mikrocontroller.net/topic/525778 this discussion.\n\n" +
|
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 Hoymiles protocol was decrypted through the voluntary efforts of many participants. ahoy, among others, was developed based on this work.\n" +
|
||||||
|
@ -21,15 +30,7 @@
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
</SCRIPT>
|
</script>
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="/live">Visualization</a><br/>
|
|
||||||
<br/>
|
|
||||||
<a href="/setup">Setup</a><br/>
|
|
||||||
<br/>
|
|
||||||
<a href="/serial">Webserial & Commands</a><br/>
|
|
||||||
</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>
|
||||||
<div id="sun">
|
<div id="sun">
|
||||||
|
@ -46,26 +47,35 @@
|
||||||
<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 id="note">
|
<div id="note">
|
||||||
New updates can be found on Github: <a href="https://github.com/lumapu/ahoy" target="_blank">https://github.com/lumapu/ahoy</a><br/>
|
|
||||||
<br/>
|
|
||||||
Please report issues in <a href="https://github.com/lumapu/ahoy/issues">Github</a><br/>
|
|
||||||
Discuss with us on <a href="https://discord.gg/WzhxEY62mB">Discord</a><br/>
|
Discuss with us on <a href="https://discord.gg/WzhxEY62mB">Discord</a><br/>
|
||||||
Support this project: <a href="https://paypal.me/lupusch">Donate</a><br/>
|
<h2>Support this project:</h2>
|
||||||
<p class="lic"><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de">Creative Commons - https://creativecommons.org/licenses/by-nc-sa/3.0/de/</a><br/>
|
<ul>
|
||||||
Check the licenses which are published on <a href="https://github.com/lumapu/ahoy">https://github.com/lumapu/ahoy</a> as well</p><br/>
|
<li>Report <a href="https://github.com/lumapu/ahoy/issues" target="_blank">issues</a></li>
|
||||||
|
<li>Contribute to <a href="https://github.com/lumapu/ahoy/blob/main/tools/esp8266/User_Manual.md" target="_blank">documentation</a></li>
|
||||||
|
<li>Test <a href="https://github.com/lumapu/ahoy/actions/workflows/compile_development.yml" target="_blank">development firmware</a></li>
|
||||||
|
<li>make a <a href="https://paypal.me/lupusch" target="_blank">donation</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="lic">
|
||||||
This project was started from <a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion. (Mikrocontroller.net)</a>
|
This project was started from <a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion. (Mikrocontroller.net)</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<p class="left">© 2022</p>
|
<div class="left">
|
||||||
<p class="left"><a href="/update">Update Firmware</a></p>
|
AhoyDTU © 2022
|
||||||
<p class="right" id="version"></p>
|
<ul>
|
||||||
<p class="right"><a href="/reboot">Reboot</a></p>
|
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
|
||||||
<p class="right"><a href="/api">REST API</a></p>
|
<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>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var mIntervalSet = false;
|
var exeOnce = true;
|
||||||
|
|
||||||
function apiCb(obj) {
|
function apiCb(obj) {
|
||||||
var e = document.getElementById("apiResult");
|
var e = document.getElementById("apiResult");
|
||||||
|
@ -88,8 +98,8 @@
|
||||||
function parseSys(obj) {
|
function parseSys(obj) {
|
||||||
// Disclaimer
|
// Disclaimer
|
||||||
//if(obj["disclaimer"] == false) sessionStorage.setItem("gDisclaimer", promptFunction());
|
//if(obj["disclaimer"] == false) sessionStorage.setItem("gDisclaimer", promptFunction());
|
||||||
|
if(true == exeOnce)
|
||||||
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
|
parseVersion(obj);
|
||||||
document.getElementById("wifi_rssi").innerHTML = obj["wifi_rssi"];
|
document.getElementById("wifi_rssi").innerHTML = obj["wifi_rssi"];
|
||||||
|
|
||||||
var date = new Date(obj["ts_now"] * 1000);
|
var date = new Date(obj["ts_now"] * 1000);
|
||||||
|
@ -170,14 +180,16 @@
|
||||||
|
|
||||||
function parse(obj) {
|
function parse(obj) {
|
||||||
if(null != obj) {
|
if(null != obj) {
|
||||||
|
if(true == exeOnce)
|
||||||
|
parseMenu(obj["menu"]);
|
||||||
parseSys(obj["system"]);
|
parseSys(obj["system"]);
|
||||||
parseStat(obj["statistics"]);
|
parseStat(obj["statistics"]);
|
||||||
parseIv(obj["inverter"]);
|
parseIv(obj["inverter"]);
|
||||||
parseWarnInfo(obj["warnings"], obj["infos"]);
|
parseWarnInfo(obj["warnings"], obj["infos"]);
|
||||||
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
|
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
|
||||||
if(false == mIntervalSet) {
|
if(true == exeOnce) {
|
||||||
window.setInterval("getAjax('/api/index', parse)", obj["refresh_interval"] * 1000);
|
window.setInterval("getAjax('/api/index', parse)", obj["refresh_interval"] * 1000);
|
||||||
mIntervalSet = true;
|
exeOnce = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -7,8 +7,17 @@
|
||||||
<script type="text/javascript" src="api.js"></script>
|
<script type="text/javascript" src="api.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Serial Console</h1>
|
<div class="topnav">
|
||||||
<div id="content" class="content">
|
<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>
|
||||||
|
<div id="wrapper">
|
||||||
|
<div id="content">
|
||||||
<div class="serial">
|
<div class="serial">
|
||||||
<textarea id="serial" cols="80" rows="20" readonly></textarea><br/>
|
<textarea id="serial" cols="80" rows="20" readonly></textarea><br/>
|
||||||
connected: <span class="dot" id="connected"></span>
|
connected: <span class="dot" id="connected"></span>
|
||||||
|
@ -52,15 +61,24 @@
|
||||||
<p>Ctrl result: <span id="result">n/a</span></p>
|
<p>Ctrl result: <span id="result">n/a</span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<p class="left">© 2022</p>
|
<div class="left">
|
||||||
<p class="left"><a href="/">Home</a></p>
|
AhoyDTU © 2022
|
||||||
<p class="right" id="version"></p>
|
<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>
|
</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");
|
||||||
var mIntervalSet = false;
|
var exeOnce = true;
|
||||||
|
|
||||||
function parseSys(obj) {
|
function parseSys(obj) {
|
||||||
var up = obj["ts_uptime"];
|
var up = obj["ts_uptime"];
|
||||||
|
@ -73,14 +91,15 @@
|
||||||
+ ("0"+min).substr(-2) + ":"
|
+ ("0"+min).substr(-2) + ":"
|
||||||
+ ("0"+sec).substr(-2);
|
+ ("0"+sec).substr(-2);
|
||||||
|
|
||||||
if(false == mIntervalSet) {
|
if(true == exeOnce) {
|
||||||
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
|
parseVersion(obj);
|
||||||
window.setInterval("getAjax('/api/system', parseSys)", 10000);
|
window.setInterval("getAjax('/api/system', parseSys)", 10000);
|
||||||
mIntervalSet = true;
|
exeOnce = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
|
@ -18,10 +18,18 @@
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body onload="load()">
|
<body onload="load()">
|
||||||
<h1>Setup</h1>
|
<div class="topnav">
|
||||||
<div id="setup" class="content">
|
<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>
|
||||||
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<a class="erase" href="/erase">ERASE SETTINGS (not WiFi)</a>
|
<a class="btn" href="/erase">ERASE SETTINGS (not WiFi)</a>
|
||||||
|
|
||||||
<form method="post" action="/save">
|
<form method="post" action="/save">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
@ -36,9 +44,11 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="des">WiFi</legend>
|
<legend class="des">WiFi</legend>
|
||||||
<p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p>
|
<p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p>
|
||||||
|
<label for="scanbtn">Search Networks</label>
|
||||||
|
<input type="button" name="scanbtn" id="scanbtn" class="btn" value="scan" onclick="scan()"/><br/>
|
||||||
<label for="networks">Avail Networks</label>
|
<label for="networks">Avail Networks</label>
|
||||||
<select name="networks" id="networks" onChange="selNet()">
|
<select name="networks" id="networks" onChange="selNet()">
|
||||||
<option value="-1">scanning ...</option>
|
<option value="-1">not scanned</option>
|
||||||
</select>
|
</select>
|
||||||
<label for="ssid">SSID</label>
|
<label for="ssid">SSID</label>
|
||||||
<input type="text" name="ssid" class="text"/>
|
<input type="text" name="ssid" class="text"/>
|
||||||
|
@ -52,7 +62,7 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="des">Inverter</legend>
|
<legend class="des">Inverter</legend>
|
||||||
<div id="inverter"></div><br/>
|
<div id="inverter"></div><br/>
|
||||||
<input type="button" id="btnAdd" value="Add Inverter"/>
|
<input type="button" id="btnAdd" class="btn" value="Add Inverter"/>
|
||||||
<p class="subdes">General</p>
|
<p class="subdes">General</p>
|
||||||
<label for="invInterval">Interval [s]</label>
|
<label for="invInterval">Interval [s]</label>
|
||||||
<input type="text" class="text" name="invInterval"/>
|
<input type="text" class="text" name="invInterval"/>
|
||||||
|
@ -139,11 +149,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<p class="left"><a href="/">Home</a></p>
|
<div class="left">
|
||||||
<p class="left"><a href="/update">Update Firmware</a></p>
|
AhoyDTU © 2022
|
||||||
<p class="right" id="version"></p>
|
<ul>
|
||||||
<p class="right"><a href="/factory">Factory Reset</a></p>
|
<li><a href="https://discord.gg/WzhxEY62mB" target="_blank">Discord</a></li>
|
||||||
<p class="right"><a href="/reboot">Reboot</a></p>
|
<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>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var highestId = 0;
|
var highestId = 0;
|
||||||
|
@ -156,6 +172,15 @@
|
||||||
ivHtml(JSON.parse('{"name":"","serial":"","channels":4,"ch_max_power":[0,0,0,0],"ch_name":["","","",""]}'), highestId + 1);
|
ivHtml(JSON.parse('{"name":"","serial":"","channels":4,"ch_max_power":[0,0,0,0],"ch_name":["","","",""]}'), highestId + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function apiCbWifi(obj) {
|
||||||
|
var e = document.getElementById("networks");
|
||||||
|
selDelAllOpt(e);
|
||||||
|
if(obj["success"])
|
||||||
|
e.appendChild(opt("-1", "scanning ..."))
|
||||||
|
else
|
||||||
|
e.appendChild(opt("-1", "Error: " + obj["error"]));
|
||||||
|
}
|
||||||
|
|
||||||
function apiCbNtp(obj) {
|
function apiCbNtp(obj) {
|
||||||
var e = document.getElementById("apiResultNtp");
|
var e = document.getElementById("apiResultNtp");
|
||||||
if(obj["success"])
|
if(obj["success"])
|
||||||
|
@ -180,6 +205,13 @@
|
||||||
getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj));
|
getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scan() {
|
||||||
|
var obj = new Object();
|
||||||
|
obj.cmd = "scan_wifi";
|
||||||
|
getAjax("/api/setup", apiCbWifi, "POST", JSON.stringify(obj));
|
||||||
|
setTimeout(function() {getAjax('/api/setup/networks', listNetworks)}, 7000);
|
||||||
|
}
|
||||||
|
|
||||||
function syncTime() {
|
function syncTime() {
|
||||||
var obj = new Object();
|
var obj = new Object();
|
||||||
obj.cmd = "sync_ntp";
|
obj.cmd = "sync_ntp";
|
||||||
|
@ -197,13 +229,14 @@
|
||||||
var e = document.getElementsByName(id + "Addr")[0];
|
var e = document.getElementsByName(id + "Addr")[0];
|
||||||
e.value = "";
|
e.value = "";
|
||||||
e.dispatchEvent(new Event("keyup"));
|
e.dispatchEvent(new Event("keyup"));
|
||||||
|
e.dispatchEvent(new Event("change"));
|
||||||
document.getElementsByName(id + "Name")[0].value = "";
|
document.getElementsByName(id + "Name")[0].value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function ivHtml(obj, id) {
|
function ivHtml(obj, id) {
|
||||||
highestId = id;
|
highestId = id;
|
||||||
if(highestId == (maxInv - 1))
|
if(highestId == (maxInv - 1))
|
||||||
toggle("btnAdd", true);
|
setHide("btnAdd", true);
|
||||||
iv = document.getElementById("inverter");
|
iv = document.getElementById("inverter");
|
||||||
iv.appendChild(des("Inverter " + id));
|
iv.appendChild(des("Inverter " + id));
|
||||||
id = "inv" + id;
|
id = "inv" + id;
|
||||||
|
@ -212,28 +245,32 @@
|
||||||
iv.appendChild(lbl(id + "Addr", "Address*"));
|
iv.appendChild(lbl(id + "Addr", "Address*"));
|
||||||
var addr = inp(id + "Addr", obj["serial"], 12)
|
var addr = inp(id + "Addr", obj["serial"], 12)
|
||||||
iv.appendChild(addr);
|
iv.appendChild(addr);
|
||||||
addr.addEventListener("keyup", (e) => {
|
['keyup', 'change'].forEach(function(evt) {
|
||||||
|
|
||||||
|
addr.addEventListener(evt, (e) => {
|
||||||
var serial = addr.value.substring(0,4);
|
var serial = addr.value.substring(0,4);
|
||||||
var max = 0;
|
var max = 0;
|
||||||
for(var i=0;i<4;i++) {
|
for(var i=0;i<4;i++) {
|
||||||
toggle(id+"ModPwr"+i, true);
|
setHide(id+"ModPwr"+i, true);
|
||||||
toggle(id+"ModName"+i, true);
|
setHide(id+"ModName"+i, true);
|
||||||
}
|
}
|
||||||
toggle("lbl"+id+"ModPwr", true);
|
setHide("lbl"+id+"ModPwr", true);
|
||||||
toggle("lbl"+id+"ModName", true);
|
setHide("lbl"+id+"ModName", true);
|
||||||
|
|
||||||
if(serial == "1161") max = 4;
|
if(serial === "1161") max = 4;
|
||||||
else if(serial == "1141") max = 2;
|
else if(serial === "1141") max = 2;
|
||||||
else if(serial == "1121") max = 1;
|
else if(serial === "1121") max = 1;
|
||||||
|
else max = 0;
|
||||||
|
|
||||||
for(var i=0;i<max;i++) {
|
|
||||||
toggle(id+"ModPwr"+i, false);
|
|
||||||
toggle(id+"ModName"+i, false);
|
|
||||||
}
|
|
||||||
if(max != 0) {
|
if(max != 0) {
|
||||||
toggle("lbl"+id+"ModPwr", false);
|
for(var i=0;i<max;i++) {
|
||||||
toggle("lbl"+id+"ModName", false);
|
setHide(id+"ModPwr"+i, false);
|
||||||
|
setHide(id+"ModName"+i, false);
|
||||||
}
|
}
|
||||||
|
setHide("lbl"+id+"ModPwr", false);
|
||||||
|
setHide("lbl"+id+"ModName", false);
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
for(var i of [["Name", "name", "Name*", 32]]) {
|
for(var i of [["Name", "name", "Name*", 32]]) {
|
||||||
|
@ -268,7 +305,7 @@
|
||||||
function parseSys(obj) {
|
function parseSys(obj) {
|
||||||
for(var i of [["device", "device_name"], ["ssid", "ssid"]])
|
for(var i of [["device", "device_name"], ["ssid", "ssid"]])
|
||||||
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
||||||
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
|
parseVersion(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIv(obj) {
|
function parseIv(obj) {
|
||||||
|
@ -372,6 +409,7 @@
|
||||||
|
|
||||||
function parse(root) {
|
function parse(root) {
|
||||||
if(null != root) {
|
if(null != root) {
|
||||||
|
parseMenu(root["menu"]);
|
||||||
parseSys(root["system"]);
|
parseSys(root["system"]);
|
||||||
parseIv(root["inverter"]);
|
parseIv(root["inverter"]);
|
||||||
parseMqtt(root["mqtt"]);
|
parseMqtt(root["mqtt"]);
|
||||||
|
@ -381,27 +419,25 @@
|
||||||
parseRadio(root["radio"]);
|
parseRadio(root["radio"]);
|
||||||
parseSerial(root["serial"]);
|
parseSerial(root["serial"]);
|
||||||
}
|
}
|
||||||
getAjax('/api/setup/networks', listNetworks);
|
|
||||||
window.setInterval("getAjax('/api/setup/networks', listNetworks)", 7000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function listNetworks(root) {
|
function listNetworks(root) {
|
||||||
if(root["networks"].length > 0) {
|
|
||||||
var s = document.getElementById("networks");
|
var s = document.getElementById("networks");
|
||||||
var i, l = s.options.length - 1;
|
selDelAllOpt(s);
|
||||||
for(i = l; i >= 0; i--) {
|
if(root["networks"].length > 0) {
|
||||||
s.remove(i);
|
s.appendChild(opt("-1", "please select network"));
|
||||||
}
|
|
||||||
|
|
||||||
for(i = 0; i < root["networks"].length; i++) {
|
for(i = 0; i < root["networks"].length; i++) {
|
||||||
s.appendChild(opt(root["networks"][i]["ssid"], root["networks"][i]["ssid"] + " (" + root["networks"][i]["rssi"] + " dBm)"));
|
s.appendChild(opt(root["networks"][i]["ssid"], root["networks"][i]["ssid"] + " (" + root["networks"][i]["rssi"] + " dBm)"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
s.appendChild(opt("-1", "no network found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function selNet() {
|
function selNet() {
|
||||||
var s = document.getElementById("networks");
|
var s = document.getElementById("networks");
|
||||||
var e = document.getElementsByName("ssid")[0];
|
var e = document.getElementsByName("ssid")[0];
|
||||||
|
if(-1 != s.value)
|
||||||
e.value = s.value;
|
e.value = s.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,158 @@
|
||||||
h1 {
|
|
||||||
margin: 0;
|
|
||||||
padding: 20pt;
|
|
||||||
font-size: 22pt;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #006ec0;
|
|
||||||
display: block;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
font-family: Arial;
|
font-family: Arial;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav {
|
||||||
|
background-color: #333;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav a {
|
||||||
|
color: #fff;
|
||||||
|
padding: 14px 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 17px;
|
||||||
|
display: block;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#topnav a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav a.icon {
|
||||||
|
background: #333;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav a:hover {
|
||||||
|
background-color: #044e86 !important;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
background-color: #006ec0;
|
||||||
|
color: #fff !important;
|
||||||
|
padding-left: 80px !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav .icon span {
|
||||||
|
display: block;
|
||||||
|
width: 30px;
|
||||||
|
height: 3px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav .active {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.seperator {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
margin: 5px 0px 5px;
|
||||||
|
background-color: #494949;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
padding: 50px 20px 120px 20px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
height: 120px;
|
||||||
|
margin-top: -120px;
|
||||||
|
background-color: #555;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer .right {
|
||||||
|
color: #bbb;
|
||||||
|
margin: 23px 25px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer .left {
|
||||||
|
color: #bbb;
|
||||||
|
margin: 23px 0px 0px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer ul {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer ul li, #footer a {
|
||||||
|
color: #bbb;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-left: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer a:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 992px) {
|
||||||
|
.topnav {
|
||||||
|
width: 230px !important;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav a.icon {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav a {
|
||||||
|
padding: 14px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav .title {
|
||||||
|
padding-left: 24px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav .hide {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
padding: 15px 15px 120px 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer .left {
|
||||||
|
margin-left: 250px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** old CSS below **/
|
||||||
|
|
||||||
p {
|
p {
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
|
@ -31,17 +170,17 @@ p.lic, p.lic a {
|
||||||
}
|
}
|
||||||
|
|
||||||
.s_active, .s_collapsible:hover {
|
.s_active, .s_collapsible:hover {
|
||||||
background-color: #006ec0;
|
background-color: #044e86;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.s_content {
|
.s_content {
|
||||||
display: none;
|
display: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.s_collapsible {
|
.s_collapsible {
|
||||||
background-color: #044e86;
|
background-color: #006ec0;
|
||||||
color: white;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 18px;
|
padding: 18px;
|
||||||
|
@ -65,10 +204,6 @@ p.lic, p.lic a {
|
||||||
margin: 0 0 7px 12px;
|
margin: 0 0 7px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:link, a:visited {
|
a:link, a:visited {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
|
@ -79,37 +214,15 @@ a:hover, a:focus {
|
||||||
color: #f00;
|
color: #f00;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.erase {
|
a.btn {
|
||||||
background-color: #006ec0;
|
background-color: #006ec0;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 7px;
|
padding: 7px 15px 7px 15px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
a.btn:hover {
|
||||||
padding: 15px 15px 60px 15px;
|
background-color: #044e86 !important;
|
||||||
}
|
|
||||||
|
|
||||||
#footer {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0px;
|
|
||||||
height: 45px;
|
|
||||||
background-color: #006ec0;
|
|
||||||
width: 100%;
|
|
||||||
border-top: 5px solid #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
#footer p, #footer a {
|
|
||||||
color: #fff;
|
|
||||||
padding: 0 7px 0 7px;
|
|
||||||
font-size: 10pt !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content {
|
|
||||||
background-color: #fff;
|
|
||||||
padding-bottom: 65px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select {
|
input, select {
|
||||||
|
@ -160,6 +273,10 @@ label {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
54
tools/esp8266/html/system.html
Normal file
54
tools/esp8266/html/system.html
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<!doctype html>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
<div id="wrapper">
|
||||||
|
<div id="content">
|
||||||
|
<a href="/factory" class="btn">Factory Reset</a><br/>
|
||||||
|
<br/>
|
||||||
|
<a href="/reboot" class="btn">Reboot</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
<div class="left">
|
||||||
|
AhoyDTU © 2022
|
||||||
|
<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 parseSys(obj) {
|
||||||
|
parseVersion(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(obj) {
|
||||||
|
if(null != obj) {
|
||||||
|
parseMenu(obj["menu"]);
|
||||||
|
parseSys(obj["system"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAjax("/api/index", parse);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -7,8 +7,17 @@
|
||||||
<script type="text/javascript" src="api.js"></script>
|
<script type="text/javascript" src="api.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Update</h1>
|
<div class="topnav">
|
||||||
<div id="content" class="content">
|
<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>
|
||||||
|
<div id="wrapper">
|
||||||
|
<div id="content">
|
||||||
<div>
|
<div>
|
||||||
Make sure that you have noted all your settings before starting an update. New versions may have changed their memory layout which can break your existing settings.<br/>
|
Make sure that you have noted all your settings before starting an update. New versions may have changed their memory layout which can break your existing settings.<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
@ -19,17 +28,33 @@
|
||||||
<input type="file" name="update"><input type="submit" value="Update">
|
<input type="file" name="update"><input type="submit" value="Update">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<p class="left">© 2022</p>
|
<div class="left">
|
||||||
<p class="left"><a href="/">Home</a></p>
|
AhoyDTU © 2022
|
||||||
<p class="right" id="version"></p>
|
<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>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function parseSys(obj) {
|
function parseSys(obj) {
|
||||||
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
|
parseVersion(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAjax("/api/system", parseSys);
|
function parse(obj) {
|
||||||
|
if(null != obj) {
|
||||||
|
parseMenu(obj["menu"]);
|
||||||
|
parseSys(obj["system"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAjax("/api/index", parse);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -8,20 +8,40 @@
|
||||||
<script type="text/javascript" src="api.js"></script>
|
<script type="text/javascript" src="api.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>AHOY</h1>
|
<div class="topnav">
|
||||||
<div id="content" class="content">
|
<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>
|
||||||
|
<div id="wrapper">
|
||||||
|
<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 id="footer">
|
<div id="footer">
|
||||||
<p class="left">© 2022</p>
|
<div class="left">
|
||||||
<p class="left"><a href="/">Home</a></p>
|
AhoyDTU © 2022
|
||||||
<p class="right" id="version"></p>
|
<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>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var intervalSet = false;
|
var exeOnce = true;
|
||||||
|
|
||||||
function parseSys(obj) {
|
function parseSys(obj) {
|
||||||
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
|
if(true == exeOnce)
|
||||||
|
parseVersion(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIv(obj, root) {
|
function parseIv(obj, root) {
|
||||||
|
@ -108,12 +128,14 @@
|
||||||
|
|
||||||
function parse(obj) {
|
function parse(obj) {
|
||||||
if(null != obj) {
|
if(null != obj) {
|
||||||
|
if(true == exeOnce)
|
||||||
|
parseMenu(obj["menu"]);
|
||||||
parseSys(obj["system"]);
|
parseSys(obj["system"]);
|
||||||
parseIv(obj["inverter"], obj);
|
parseIv(obj["inverter"], obj);
|
||||||
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
|
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
|
||||||
if(false == intervalSet) {
|
if(true == exeOnce) {
|
||||||
window.setInterval("getAjax('/api/live', parse)", obj["refresh_interval"] * 1000);
|
window.setInterval("getAjax('/api/live', parse)", obj["refresh_interval"] * 1000);
|
||||||
intervalSet = true;
|
exeOnce = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -25,6 +25,9 @@ class mqtt {
|
||||||
mClient = new PubSubClient(mEspClient);
|
mClient = new PubSubClient(mEspClient);
|
||||||
mAddressSet = false;
|
mAddressSet = false;
|
||||||
|
|
||||||
|
mLastReconnect = 0;
|
||||||
|
mTxCnt = 0;
|
||||||
|
|
||||||
memset(mDevName, 0, DEVNAME_LEN);
|
memset(mDevName, 0, DEVNAME_LEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +53,7 @@ class mqtt {
|
||||||
char top[64];
|
char top[64];
|
||||||
snprintf(top, 64, "%s/%s", mCfg->topic, topic);
|
snprintf(top, 64, "%s/%s", mCfg->topic, topic);
|
||||||
sendMsg2(top, msg, false);
|
sendMsg2(top, msg, false);
|
||||||
|
mTxCnt++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendMsg2(const char *topic, const char *msg, boolean retained) {
|
void sendMsg2(const char *topic, const char *msg, boolean retained) {
|
||||||
|
@ -75,6 +79,10 @@ class mqtt {
|
||||||
mClient->loop();
|
mClient->loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t getTxCnt(void) {
|
||||||
|
return mTxCnt;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void reconnect(void) {
|
void reconnect(void) {
|
||||||
DPRINTLN(DBG_DEBUG, F("mqtt.h:reconnect"));
|
DPRINTLN(DBG_DEBUG, F("mqtt.h:reconnect"));
|
||||||
|
@ -85,8 +93,8 @@ class mqtt {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
boolean resub = false;
|
boolean resub = false;
|
||||||
if(!mClient->connected() && (millis() - lastReconnect) > MQTT_RECONNECT_DELAY ) {
|
if(!mClient->connected() && (millis() - mLastReconnect) > MQTT_RECONNECT_DELAY ) {
|
||||||
lastReconnect = millis();
|
mLastReconnect = millis();
|
||||||
if(strlen(mDevName) > 0) {
|
if(strlen(mDevName) > 0) {
|
||||||
// der Server und der Port müssen neu gesetzt werden,
|
// der Server und der Port müssen neu gesetzt werden,
|
||||||
// da ein MQTT_CONNECTION_LOST -3 die Werte zerstört hat.
|
// da ein MQTT_CONNECTION_LOST -3 die Werte zerstört hat.
|
||||||
|
@ -118,7 +126,8 @@ class mqtt {
|
||||||
bool mAddressSet;
|
bool mAddressSet;
|
||||||
mqttConfig_t *mCfg;
|
mqttConfig_t *mCfg;
|
||||||
char mDevName[DEVNAME_LEN];
|
char mDevName[DEVNAME_LEN];
|
||||||
unsigned long lastReconnect = 0;
|
uint32_t mLastReconnect;
|
||||||
|
uint32_t mTxCnt;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /*__MQTT_H_*/
|
#endif /*__MQTT_H_*/
|
||||||
|
|
|
@ -21,7 +21,7 @@ def get_firmware_specifier_build_flag():
|
||||||
except:
|
except:
|
||||||
build_version = "g0000000"
|
build_version = "g0000000"
|
||||||
|
|
||||||
build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version + "\\\""
|
build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version[1:] + "\\\""
|
||||||
print ("Firmware Revision: " + build_version)
|
print ("Firmware Revision: " + build_version)
|
||||||
return (build_flag)
|
return (build_flag)
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#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"
|
#include "html/h/serial_html.h"
|
||||||
|
#include "html/h/system_html.h"
|
||||||
|
|
||||||
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"};
|
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq"};
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ void web::setup(void) {
|
||||||
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::onReboot, this, std::placeholders::_1));
|
mWeb->on("/reboot", HTTP_ANY, std::bind(&web::onReboot, this, std::placeholders::_1));
|
||||||
|
mWeb->on("/system", HTTP_ANY, std::bind(&web::onSystem, 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));
|
||||||
|
|
||||||
|
@ -160,8 +162,18 @@ void web::showNotFound(AsyncWebServerRequest *request) {
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::onReboot(AsyncWebServerRequest *request) {
|
void web::onReboot(AsyncWebServerRequest *request) {
|
||||||
request->send(200, F("text/html"), F("<!doctype html><html><head><title>Rebooting ...</title><meta http-equiv=\"refresh\" content=\"10; URL=/\"></head><body>rebooting ... auto reload after 10s</body></html>"));
|
|
||||||
mMain->mShouldReboot = true;
|
mMain->mShouldReboot = true;
|
||||||
|
request->send(200, F("text/html"), F("<!doctype html><html><head><title>Reboot</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta http-equiv=\"refresh\" content=\"10; URL=/\"></head><body>reboot. Autoreload after 10 seconds</body></html>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void web::onSystem(AsyncWebServerRequest *request) {
|
||||||
|
DPRINTLN(DBG_VERBOSE, F("onSystem"));
|
||||||
|
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len);
|
||||||
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ class web {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onSerial(AsyncWebServerRequest *request);
|
void onSerial(AsyncWebServerRequest *request);
|
||||||
|
void onSystem(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
AsyncWebServer *mWeb;
|
AsyncWebServer *mWeb;
|
||||||
AsyncEventSource *mEvts;
|
AsyncEventSource *mEvts;
|
||||||
|
|
|
@ -48,6 +48,7 @@ void webApi::onApi(AsyncWebServerRequest *request) {
|
||||||
if(path == "system") getSystem(root);
|
if(path == "system") getSystem(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);
|
||||||
|
@ -241,8 +242,29 @@ void webApi::getSerial(JsonObject obj) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void webApi::getMenu(JsonObject obj) {
|
||||||
|
obj["name"][0] = "Live";
|
||||||
|
obj["link"][0] = "/live";
|
||||||
|
obj["name"][1] = "Serial Console";
|
||||||
|
obj["link"][1] = "/serial";
|
||||||
|
obj["name"][2] = "Settings";
|
||||||
|
obj["link"][2] = "/setup";
|
||||||
|
obj["name"][3] = "-";
|
||||||
|
obj["name"][4] = "REST API";
|
||||||
|
obj["link"][4] = "/api";
|
||||||
|
obj["trgt"][4] = "_blank";
|
||||||
|
obj["name"][5] = "-";
|
||||||
|
obj["name"][6] = "Update";
|
||||||
|
obj["link"][6] = "/update";
|
||||||
|
obj["name"][7] = "System";
|
||||||
|
obj["link"][7] = "/system";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void webApi::getIndex(JsonObject obj) {
|
void webApi::getIndex(JsonObject obj) {
|
||||||
|
getMenu(obj.createNestedObject(F("menu")));
|
||||||
getSystem(obj.createNestedObject(F("system")));
|
getSystem(obj.createNestedObject(F("system")));
|
||||||
getStatistics(obj.createNestedObject(F("statistics")));
|
getStatistics(obj.createNestedObject(F("statistics")));
|
||||||
obj["refresh_interval"] = SEND_INTERVAL;
|
obj["refresh_interval"] = SEND_INTERVAL;
|
||||||
|
@ -275,12 +297,13 @@ void webApi::getIndex(JsonObject obj) {
|
||||||
if(!mApp->getSettingsValid())
|
if(!mApp->getSettingsValid())
|
||||||
info.add(F("your settings are invalid"));
|
info.add(F("your settings are invalid"));
|
||||||
if(mApp->mqttIsConnected())
|
if(mApp->mqttIsConnected())
|
||||||
info.add(F("MQTT is connected"));
|
info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void webApi::getSetup(JsonObject obj) {
|
void webApi::getSetup(JsonObject obj) {
|
||||||
|
getMenu(obj.createNestedObject(F("menu")));
|
||||||
getSystem(obj.createNestedObject(F("system")));
|
getSystem(obj.createNestedObject(F("system")));
|
||||||
getInverterList(obj.createNestedObject(F("inverter")));
|
getInverterList(obj.createNestedObject(F("inverter")));
|
||||||
getMqtt(obj.createNestedObject(F("mqtt")));
|
getMqtt(obj.createNestedObject(F("mqtt")));
|
||||||
|
@ -300,6 +323,7 @@ void webApi::getNetworks(JsonObject obj) {
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void webApi::getLive(JsonObject obj) {
|
void webApi::getLive(JsonObject obj) {
|
||||||
|
getMenu(obj.createNestedObject(F("menu")));
|
||||||
getSystem(obj.createNestedObject(F("system")));
|
getSystem(obj.createNestedObject(F("system")));
|
||||||
JsonArray invArr = obj.createNestedArray(F("inverter"));
|
JsonArray invArr = obj.createNestedArray(F("inverter"));
|
||||||
obj["refresh_interval"] = SEND_INTERVAL;
|
obj["refresh_interval"] = SEND_INTERVAL;
|
||||||
|
@ -435,7 +459,9 @@ bool webApi::setCtrl(DynamicJsonDocument jsonIn, JsonObject jsonOut) {
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
bool webApi::setSetup(DynamicJsonDocument jsonIn, JsonObject jsonOut) {
|
bool webApi::setSetup(DynamicJsonDocument jsonIn, JsonObject jsonOut) {
|
||||||
if(F("set_time") == jsonIn[F("cmd")])
|
if(F("scan_wifi"))
|
||||||
|
mApp->scanAvailNetworks();
|
||||||
|
else if(F("set_time") == jsonIn[F("cmd")])
|
||||||
mApp->setTimestamp(jsonIn[F("ts")]);
|
mApp->setTimestamp(jsonIn[F("ts")]);
|
||||||
else if(F("sync_ntp") == jsonIn[F("cmd")])
|
else if(F("sync_ntp") == jsonIn[F("cmd")])
|
||||||
mApp->setTimestamp(0); // 0: update ntp flag
|
mApp->setTimestamp(0); // 0: update ntp flag
|
||||||
|
|
|
@ -42,6 +42,7 @@ class webApi {
|
||||||
void getRadio(JsonObject obj);
|
void getRadio(JsonObject obj);
|
||||||
void getSerial(JsonObject obj);
|
void getSerial(JsonObject obj);
|
||||||
|
|
||||||
|
void getMenu(JsonObject obj);
|
||||||
void getIndex(JsonObject obj);
|
void getIndex(JsonObject obj);
|
||||||
void getSetup(JsonObject obj);
|
void getSetup(JsonObject obj);
|
||||||
void getNetworks(JsonObject obj);
|
void getNetworks(JsonObject obj);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue