From 1bbe979bcd13adab4f415db926a2f2c766123c7e Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 2 Oct 2023 00:34:57 +0200 Subject: [PATCH] 0.7.64 * moved active power control to modal in `live` view (per inverter) by click on current APC state --- src/CHANGES.md | 3 + src/defines.h | 2 +- src/web/RestApi.h | 22 ++++-- src/web/html/includes/nav.html | 2 +- src/web/html/serial.html | 114 ++------------------------------ src/web/html/setup.html | 2 +- src/web/html/visualization.html | 103 ++++++++++++++++++++++++++++- 7 files changed, 130 insertions(+), 118 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 23d17732..1cc2f00a 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.7.64 - 2023-10-02 +* moved active power control to modal in `live` view (per inverter) by click on current APC state + ## 0.7.63 - 2023-10-01 * fix NRF24 communication #1200 diff --git a/src/defines.h b/src/defines.h index 5938213a..ac96ff43 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 7 -#define VERSION_PATCH 63 +#define VERSION_PATCH 64 //------------------------------------- typedef struct { diff --git a/src/web/RestApi.h b/src/web/RestApi.h index a3a0384c..39c97532 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -105,6 +105,8 @@ class RestApi { getIvVersion(root, request->url().substring(22).toInt()); else if(path.substring(0, 19) == "inverter/radiostat/") getIvStatistis(root, request->url().substring(24).toInt()); + else if(path.substring(0, 16) == "inverter/pwrack/") + getIvPowerLimitAck(root, request->url().substring(21).toInt()); else getNotFound(root, F("http://") + request->host() + F("/api/")); } @@ -276,7 +278,7 @@ class RestApi { void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) { getSysInfo(request, obj.createNestedObject(F("system"))); getGeneric(request, obj.createNestedObject(F("generic"))); - obj[F("html")] = F("Factory Reset

Reboot"); + obj[F("html")] = F("AhoyFactory Reset

Reboot"); } void getHtmlLogout(AsyncWebServerRequest *request, JsonObject obj) { @@ -322,6 +324,15 @@ class RestApi { obj[F("retransmits")] = iv->radioStatistics.retransmits; } + void getIvPowerLimitAck(JsonObject obj, uint8_t id) { + Inverter<> *iv = mSys->getInverterByPos(id); + if(NULL == iv) { + obj[F("error")] = F("inverter not found!"); + return; + } + obj["ack"] = (bool)iv->powerLimitAck; + } + void getInverterList(JsonObject obj) { JsonArray invArr = obj.createNestedArray(F("inverter")); @@ -389,10 +400,10 @@ class RestApi { ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; } } else { - for (uint8_t fld = 0; fld < sizeof(acList); fld++) { - pos = (iv->getPosByChFld(CH0, acList[fld], rec)); - ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; - } + for (uint8_t fld = 0; fld < sizeof(acList); fld++) { + pos = (iv->getPosByChFld(CH0, acList[fld], rec)); + ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; + } } // DC @@ -652,6 +663,7 @@ class RestApi { jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as(); return false; } + jsonOut[F("id")] = jsonIn[F("id")]; if(F("power") == jsonIn[F("cmd")]) accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff); diff --git a/src/web/html/includes/nav.html b/src/web/html/includes/nav.html index 9d6b822c..91de5047 100644 --- a/src/web/html/includes/nav.html +++ b/src/web/html/includes/nav.html @@ -7,7 +7,7 @@
Live - Serial / Control + Webserial Settings Update diff --git a/src/web/html/serial.html b/src/web/html/serial.html index 442ba24a..10d1769e 100644 --- a/src/web/html/serial.html +++ b/src/web/html/serial.html @@ -12,53 +12,13 @@
-
connected:
+
console active:
Uptime:
-
+
-
-
-

Commands

-
-
-
Select Inverter
-
-
-
-
Power Limit Command
-
- -
-
-
-
Power Limit Value
-
-
-
-
-
-
-
-
Control Inverter
-
- - - -
-
-
-
Ctrl result
-
n/a
-
{#HTML_FOOTER} @@ -84,23 +44,11 @@ parseESP(obj); window.setInterval("getAjax('/api/generic', parseGeneric)", 10000); exeOnce = false; - getAjax("/api/inverter/list", parse); + setTimeOffset(); } } - function parse(root) { - select = document.getElementById('InvID'); - - if(null == root) return; - root = root.inverter; - for(var i = 0; i < root.length; i++) { - inv = root[i]; - var opt = document.createElement('option'); - opt.value = inv.id; - opt.innerHTML = inv.name; - select.appendChild(opt); - } - + function setTimeOffset() { // set time offset for serial console var obj = new Object(); obj.cmd = "serial_utc_offset"; @@ -119,12 +67,12 @@ if (!!window.EventSource) { var source = new EventSource('/events'); source.addEventListener('open', function(e) { - document.getElementById("connected").style.backgroundColor = "#0c0"; + document.getElementById("active").style.backgroundColor = "#0c0"; }, false); source.addEventListener('error', function(e) { if (e.target.readyState != EventSource.OPEN) { - document.getElementById("connected").style.backgroundColor = "#f00"; + document.getElementById("active").style.backgroundColor = "#f00"; } }, false); @@ -135,56 +83,6 @@ }, false); } - - function ctrlCb(obj) { - var e = document.getElementById("result"); - if(obj["success"]) - e.innerHTML = "ok"; - else - e.innerHTML = "Error: " + obj["error"]; - } - - function get_selected_iv() { - var e = document.getElementById("InvID"); - return parseInt(e.value); - } - - const wrapper = document.getElementById('power'); - - wrapper.addEventListener('click', (event) => { - var obj = new Object(); - obj.id = get_selected_iv(); - obj.cmd = "power"; - - switch (event.target.value) { - default: - case "Turn On": - obj.val = 1; - break; - case "Turn Off": - obj.val = 0; - break; - } - - getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)); - }); - - document.getElementById("sendpwrlim").addEventListener("click", function() { - var val = parseInt(document.getElementsByName('pwrlimval')[0].value); - var cmd = document.getElementsByName('pwrlimctrl')[0].value; - - if(isNaN(val)) { - document.getElementById("result").textContent = "value is missing"; - return; - } - - var obj = new Object(); - obj.id = get_selected_iv(); - obj.cmd = cmd; - obj.val = val; - getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)); - }); - getAjax("/api/generic", parseGeneric); diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 8b28da14..d58aa226 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -683,7 +683,7 @@ if(!obj["pwd_set"]) e.value = ""; var d = document.getElementById("prot_mask"); - var a = ["Index", "Live", "Serial / Console", "Settings", "Update", "System"]; + var a = ["Index", "Live", "Webserial", "Settings", "Update", "System"]; var el = []; for(var i = 0; i < 6; i++) { var chk = ((obj["prot_mask"] & (1 << i)) == (1 << i)); diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index d790147c..24153389 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -20,6 +20,7 @@ var mIvHtml = []; var mNum = 0; var total = Array(5).fill(0); + var tPwrAck; function parseGeneric(obj) { if(true == exeOnce){ @@ -113,8 +114,10 @@ ml("div", {class: "col mx-2 mx-md-1"}, ml("span", { class: "pointer", onclick: function() { getAjax("/api/inverter/version/" + obj.id, parseIvVersion); }}, obj.name)), - ml("div", {class: "col a-c d-none d-sm-block"}, "Active Power Control: " + pwrLimit), - ml("div", {class: "col a-c d-block d-sm-none"}, "APC: " + pwrLimit), + ml("div", {class: "col a-c", onclick: function() {limitModal(obj)}}, [ + ml("span", {class: "d-none d-sm-block pointer"}, "Active Power Control: " + pwrLimit), + ml("span", {class: "d-block d-sm-none pointer"}, "APC: " + pwrLimit) + ]), ml("div", {class: "col a-c"}, ml("span", { class: "pointer", onclick: function() { getAjax("/api/inverter/alarm/" + obj.id, parseIvAlarm); }}, ("Alarms: " + obj.alarm_cnt))), @@ -305,6 +308,102 @@ modal("Radio statistics for inverter " + obj.name, ml("div", {}, html)); } + function limitModal(obj) { + var opt = [["pct", "%"], ["watt", "W"]]; + var html = ml("div", {}, [ + ml("div", {class: "row mb-3"}, [ + ml("div", {class: "col-12 col-sm-5 my-2"}, "Limit Value"), + ml("div", {class: "col-8 col-sm-5"}, ml("input", {name: "limit", type: "number"}, "")), + ml("div", {class: "col-4 col-sm-2"}, sel("type", opt, "pct")) + ]), + ml("div", {class: "row mb-3"}, [ + ml("div", {class: "col-8 col-sm-5"}, "Keep limit over inverter restart"), + ml("div", {class: "col-4 col-sm-7"}, ml("input", {type: "checkbox", name: "keep"})) + ]), + ml("div", {class: "row my-3"}, + ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "Apply", class: "btn", onclick: function() { + applyLimit(obj.id); + }}, null)) + ), + ml("div", {class: "row my-4"}, [ + ml("div", {class: "col-12 col-sm-5 my-2"}, "Control"), + ml("div", {class: "col col-sm-7 a-r"}, [ + ml("input", {type: "button", value: "restart", class: "btn", onclick: function() { + applyCtrl(obj.id, "restart"); + }}, null), + ml("input", {type: "button", value: "turn off", class: "btn mx-1", onclick: function() { + applyCtrl(obj.id, "power", 0); + }}, null), + ml("input", {type: "button", value: "turn on", class: "btn", onclick: function() { + applyCtrl(obj.id, "power", 1); + }}, null) + ]) + ]), + ml("div", {class: "row mt-1"}, [ + ml("div", {class: "col-12 col-sm-5 my-2"}, "Result"), + ml("div", {class: "col-sm-7 my-2"}, ml("span", {name: "pwrres"}, "-")) + ]) + ]); + modal("Active Power Control for inverter " + obj.name, html); + } + + function applyLimit(id) { + var cmd = "limit_"; + if(!document.getElementsByName("keep")[0].checked) + cmd += "non"; + cmd += "persistent_"; + if(document.getElementsByName("type")[0].value == "pct") + cmd += "relative"; + else + cmd += "absolute"; + + var val = document.getElementsByName("limit")[0].value; + if(isNaN(val)) + val = 100; + + var obj = new Object(); + obj.id = id; + obj.cmd = cmd; + obj.val = val; + getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)); + } + + function applyCtrl(id, cmd, val=0) { + var obj = new Object(); + obj.id = id; + obj.cmd = cmd; + obj.val = val; + getAjax("/api/ctrl", ctrlCb2, "POST", JSON.stringify(obj)); + } + + function ctrlCb(obj) { + var e = document.getElementsByName("pwrres")[0]; + if(obj.success) { + e.innerHTML = "received command, waiting for inverter acknowledge ..."; + tPwrAck = window.setInterval("getAjax('/api/inverter/pwrack/" + obj.id + "', updatePwrAck)", 1000); + } + else + e.innerHTML = "Error: " + obj["error"]; + } + + function ctrlCb2(obj) { + var e = document.getElementsByName("pwrres")[0]; + if(obj.success) + e.innerHTML = "command received"; + else + e.innerHTML = "Error: " + obj["error"]; + } + + function updatePwrAck(obj) { + if(!obj.ack) + return; + var e = document.getElementsByName("pwrres")[0]; + clearInterval(tPwrAck); + if(null == e) + return; + e.innerHTML = "inverter acknowledged active power control command"; + } + function parse(obj) { if(null != obj) { parseGeneric(obj["generic"]);