diff --git a/src/CHANGES.md b/src/CHANGES.md index a610cf59..3c3c553a 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.2 - 2023-11-08 +* beautified inverter settings in `setup` (preperation for future, settings become more inverter dependent) + ## 0.8.1 - 2023-11-05 * added tx channel heuristics (per inverter) * fix statistics counter diff --git a/src/config/config.h b/src/config/config.h index da027fd6..f03ac702 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -158,7 +158,7 @@ #define MAX_RF_PAYLOAD_SIZE 32 // maximum total payload buffers (must be greater than the number of received frame fragments) -#define MAX_PAYLOAD_ENTRIES 10 +#define MAX_PAYLOAD_ENTRIES 20 // number of seconds since last successful response, before inverter is marked inactive #define INVERTER_INACT_THRES_SEC 5*60 diff --git a/src/defines.h b/src/defines.h index 1a865fa7..8e8b1f61 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 1 +#define VERSION_PATCH 2 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 6cb2567e..023ae772 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -118,7 +118,6 @@ class Inverter { record_t recordHwInfo; // structure for simple (hardware) info values record_t recordConfig; // structure for system config values record_t recordAlarm; // structure for alarm values - bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null) bool isConnected; // shows if inverter was successfully identified (fw version and hardware info) InverterStatus status; // indicates the current inverter status std::array lastAlarm; // holds last 10 alarms @@ -142,7 +141,6 @@ class Inverter { actPowerLimit = 0xffff; // init feedback from inverter to -1 mDevControlRequest = false; devControlCmd = InitDataState; - initialized = false; alarmMesIndex = 0; isConnected = false; status = InverterStatus::OFF; @@ -193,7 +191,6 @@ class Inverter { initAssignment(&recordConfig, SystemConfigPara); initAssignment(&recordAlarm, AlarmData); toRadioId(); - initialized = true; } uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId, record_t<> *rec) { diff --git a/src/hm/hmSystem.h b/src/hm/hmSystem.h index a8ca2371..de0d5911 100644 --- a/src/hm/hmSystem.h +++ b/src/hm/hmSystem.h @@ -114,7 +114,7 @@ class HmSystem { DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos")); if(pos >= MAX_INVERTER) return NULL; - else if((mInverter[pos].initialized && mInverter[pos].config->serial.u64 != 0ULL) || false == check) + else if((mInverter[pos].config->serial.u64 != 0ULL) || (false == check)) return &mInverter[pos]; else return NULL; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 071030ba..2267661e 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -124,7 +124,7 @@ class RestApi { void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { DPRINTLN(DBG_VERBOSE, "onApiPostBody"); - DynamicJsonDocument json(200); + DynamicJsonDocument json(800); AsyncJsonResponse* response = new AsyncJsonResponse(false, 200); JsonObject root = response->getRoot(); @@ -708,8 +708,35 @@ class RestApi { mApp->setTimestamp(0); // 0: update ntp flag else if(F("serial_utc_offset") == jsonIn[F("cmd")]) mTimezoneOffset = jsonIn[F("val")]; - else if(F("discovery_cfg") == jsonIn[F("cmd")]) { + else if(F("discovery_cfg") == jsonIn[F("cmd")]) mApp->setMqttDiscoveryFlag(); // for homeassistant + else if(F("save_iv") == jsonIn[F("cmd")]) { + Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")], false); + iv->config->enabled = jsonIn[F("en")]; + iv->config->serial.u64 = jsonIn[F("ser")]; + snprintf(iv->config->name, MAX_NAME_LENGTH, "%s", jsonIn[F("name")].as()); + + for(uint8_t i = 0; i < 6; i++) { + iv->config->chMaxPwr[i] = jsonIn[F("ch")][i][F("pwr")]; + iv->config->yieldCor[i] = jsonIn[F("ch")][i][F("yld")]; + snprintf(iv->config->chName[i], MAX_NAME_LENGTH, "%s", jsonIn[F("ch")][i][F("name")].as()); + } + + switch(iv->config->serial.b[4]) { + case 0x24: + case 0x22: + case 0x21: iv->type = INV_TYPE_1CH; iv->channels = 1; break; + + case 0x44: + case 0x42: + case 0x41: iv->type = INV_TYPE_2CH; iv->channels = 2; break; + + case 0x64: + case 0x62: + case 0x61: iv->type = INV_TYPE_4CH; iv->channels = 4; break; + default: break; + } + mApp->saveSettings(false); // without reboot } else { jsonOut[F("error")] = F("unknown cmd"); return false; diff --git a/src/web/html/api.js b/src/web/html/api.js index c9590d89..8c0ba54e 100644 --- a/src/web/html/api.js +++ b/src/web/html/api.js @@ -1,6 +1,4 @@ -/** - * SVG ICONS - */ +/* SVG ICONS - https://icons.getbootstrap.com */ iconWifi1 = [ "M11.046 10.454c.226-.226.185-.605-.1-.75A6.473 6.473 0 0 0 8 9c-1.06 0-2.062.254-2.946.704-.285.145-.326.524-.1.75l.015.015c.16.16.407.19.611.09A5.478 5.478 0 0 1 8 10c.868 0 1.69.201 2.42.56.203.1.45.07.611-.091l.015-.015zM9.06 12.44c.196-.196.198-.52-.04-.66A1.99 1.99 0 0 0 8 11.5a1.99 1.99 0 0 0-1.02.28c-.238.14-.236.464-.04.66l.706.706a.5.5 0 0 0 .707 0l.708-.707z" @@ -34,6 +32,15 @@ iconSuccessFull = [ "M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" ]; +iconGear = [ + "M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z" +]; + +iconDel = [ + "M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z", + "M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" +]; + /** * GENERIC FUNCTIONS */ @@ -119,7 +126,7 @@ function parseRssi(obj) { icon = iconWifi1; else if(obj["wifi_rssi"] <= -70) icon = iconWifi2; - document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "wifi", obj["wifi_rssi"])); + document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "icon-fg", obj["wifi_rssi"])); } function toIsoDateStr(d) { @@ -181,6 +188,10 @@ function tr(val1, val2) { ]); } +function badge(success, text, second="error") { + return ml("span", {class: "badge badge-" + ((success) ? "success" : second)}, text); +} + function des(val) { e = document.createElement('p'); e.classList.add("subdes"); diff --git a/src/web/html/colorDark.css b/src/web/html/colorDark.css index 3d9d167a..65100721 100644 --- a/src/web/html/colorDark.css +++ b/src/web/html/colorDark.css @@ -16,7 +16,7 @@ --secondary: #0072c8; --nav-active: #555; --footer-bg: #282828; - --modal-bg: #666; + --modal-bg: #282828; --invalid-bg: #400; diff --git a/src/web/html/setup.html b/src/web/html/setup.html index b9697141..2ec5d2b5 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -140,14 +140,7 @@
Inverter
-
-
-
-
-
-

Note

-

A 'max module power' value of '0' disables the channel in 'live' view

-
+

General

@@ -451,8 +444,6 @@ [1, "high active"], ]; - const re = /1[0,1,3][2,4,6,8][1,2,4].*/; - window.onload = function() { for(it of document.getElementsByClassName("s_collapsible")) { it.addEventListener("click", function() { @@ -471,10 +462,6 @@ }); } - document.getElementById("btnAdd").addEventListener("click", function() { - ivHtml(JSON.parse('{"enabled":true,"name":"","serial":"","channels":6,"ch_max_pwr":[0,0,0,0,0,0],"ch_name":["","","","","",""],"ch_yield_cor":[0,0,0,0,0,0]}')); - }); - function apiCbWifi(obj) { var e = document.getElementById("networks"); selDelAllOpt(e); @@ -573,98 +560,6 @@ return null; } - function ivHtml(obj) { - var id = getFreeId(); - if(null == id) { - setHide("btnAdd", true); - return; - } - - var iv = ml("div", {id: "inv" + id}, null); - document.getElementById("inverter").appendChild(iv); - iv.appendChild(des("Inverter " + id)); - id = "inv" + id; - - var addr = ml("input", {name: id + "Addr", class: "text", type: "number", max: 138999999999, value: obj["serial"]}, null); - - iv.append( - mlCb(id + "Enable", "Communication Enable", obj["enabled"]), - mlE("Serial Number (12 digits)*", addr) - ); - - ['keyup', 'change'].forEach(function(evt) { - addr.addEventListener(evt, (e) => { - var serial = addr.value.substring(0,4); - var max = 0; - for(var i=0;i<6;i++) { - setHide(id+"ModPwr"+i, true); - setHide(id+"ModName"+i, true); - setHide(id+"YieldCor"+i, true); - } - setHide("row"+id+"ModPwr", true); - setHide("row"+id+"ModName", true); - setHide("row"+id+"YieldCor", true); - - if(serial.charAt(0) == 1) { - if((serial.charAt(1) == 0) || (serial.charAt(1) == 1) || (serial.charAt(1) == 3)) { - if((serial.charAt(3) == 1) || (serial.charAt(3) == 2) || (serial.charAt(3) == 4)) { - switch(serial.charAt(2)) { - default: - case "2": max = 1; break; - case "4": max = 2; break; - case "6": max = 4; break; - case "8": max = 6; break; - } - } - } - } - - if(max != 0) { - for(var i=0;i { + var serial = ser.value.substring(0,4); + var max = 1; + for(var i = 0; i < 6; i++) { + setHide("ch"+i, true); + } + + if(serial.charAt(0) == 1) { + if((serial.charAt(1) == 0) || (serial.charAt(1) == 1) || (serial.charAt(1) == 3)) { + if((serial.charAt(3) == 1) || (serial.charAt(3) == 2) || (serial.charAt(3) == 4)) { + switch(serial.charAt(2)) { + default: + case "2": max = 1; break; + case "4": max = 2; break; + case "6": max = 4; break; + case "8": max = 6; break; + } + } + } + } + for(var i = 0; i < max; i++) { + setHide("ch"+i, false); + } + }) + }); + + modal("Edit inverter", html); + ser.dispatchEvent(new Event('change')); + + function ivSave() { + var o = new Object(); + o.cmd = "save_iv"; + o.id = obj.id; + o.ser = parseInt(document.getElementsByName("ser")[0].value, 16); + o.name = document.getElementsByName("name")[0].value; + o.en = document.getElementsByName("enable")[0].checked; + o.ch = []; + for(let i = 0; i < 6; i++) { + var q = new Object(); + q.pwr = document.getElementsByName("ch_p"+i)[0].value; + q.name = document.getElementsByName("ch_n"+i)[0].value; + q.yld = document.getElementsByName("yld_c"+i)[0].value; + o.ch.push(q); + } + getAjax("/api/setup", cb, "POST", JSON.stringify(o)); + } + + function cb(obj) { + var e = document.getElementById("res"); + if(!obj.success) + e.innerHTML = "error while saving"; + else { + modalClose(); + getAjax("/api/inverter/list", parseIv); + } + } + } + + function ivDel(obj) { + var html = ml("div", {class: "row"}, [ + ml("div", {class: "col-9"}, "do you realy want to delete inverter " + obj.name + "?"), + ml("div", {class: "col-3 a-r"}, ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "yes", class: "btn", onclick: function() { del(); }}, null))) + ]); + modal("Delete inverter " + obj.name, html); + + function del() { + var o = new Object(); + o.cmd = "save_iv"; + o.id = obj.id; + o.ser = 0; + o.name = ""; + o.en = false; + o.ch = []; + for(let i = 0; i < 6; i++) { + var q = new Object(); + q.pwr = 0; + q.name = ""; + q.yld = 0; + o.ch.push(q); + } + getAjax("/api/setup", cb, "POST", JSON.stringify(o)); + } + + function cb(obj) { + if(obj.success) { + modalClose(); + getAjax("/api/inverter/list", parseIv); + } + } + } + function parseMqtt(obj) { for(var i of [["Addr", "broker"], ["Port", "port"], ["ClientId", "clientId"], ["User", "user"], ["Pwd", "pwd"], ["Topic", "topic"], ["Interval", "interval"]]) document.getElementsByName("mqtt"+i[0])[0].value = obj[i[1]]; diff --git a/src/web/html/style.css b/src/web/html/style.css index 95cbff3b..8c40c780 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -94,8 +94,8 @@ svg.icon { fill: var(--success); } -.wifi { - fill: var(--fg2); +.icon-fg { + fill: var(--fg); } .title { @@ -708,7 +708,7 @@ div.hr { width: 100%; background-color: var(--modal-bg); background-clip: padding-box; - border: 1px solid rgba(0,0,0,.2); + border: 1px solid var(--fg); flex-direction: column; } diff --git a/src/web/html/system.html b/src/web/html/system.html index 27ad1801..7eddd7b0 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -40,10 +40,6 @@ ); } - function badge(success, text, second="error") { - return ml("span", {class: "badge badge-" + ((success) ? "success" : second)}, text); - } - function headline(text) { return ml("div", {class: "head p-2 mt-3"}, ml("div", {class: "row"}, ml("div", {class: "col a-c"}, text))) } diff --git a/src/web/web.h b/src/web/web.h index fabbd604..002c3cbc 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -487,44 +487,6 @@ class Web { request->arg("ipGateway").toCharArray(buf, 20); ah::ip2Arr(mConfig->sys.ip.gateway, buf); - // inverter - Inverter<> *iv; - for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { - iv = mSys->getInverterByPos(i, false); - // enable communication - iv->config->enabled = (request->arg("inv" + String(i) + "Enable") == "on"); - // address - request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); - if (strlen(buf) == 0) - memset(buf, 0, 20); - iv->config->serial.u64 = ah::Serial2u64(buf); - switch(iv->config->serial.b[4]) { - case 0x24: - case 0x22: - case 0x21: iv->type = INV_TYPE_1CH; iv->channels = 1; break; - - case 0x44: - case 0x42: - case 0x41: iv->type = INV_TYPE_2CH; iv->channels = 2; break; - - case 0x64: - case 0x62: - case 0x61: iv->type = INV_TYPE_4CH; iv->channels = 4; break; - default: break; - } - - // name - request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH); - - // max channel power / name - for (uint8_t j = 0; j < 6; j++) { - iv->config->yieldCor[j] = request->arg("inv" + String(i) + "YieldCor" + String(j)).toDouble(); - iv->config->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff; - request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH); - } - iv->initialized = true; - } - if (request->arg("invInterval") != "") mConfig->nrf.sendInterval = request->arg("invInterval").toInt(); mConfig->inst.rstYieldMidNight = (request->arg("invRstMid") == "on");