<!doctype html> <html> <head> <title>Live</title> {#HTML_HEADER} <meta name="apple-mobile-web-app-capable" content="yes"> </head> <body> {#HTML_NAV} <div id="wrapper"> <div id="content"> <div id="live"></div> <p>Every <span id="refresh"></span> seconds the values are updated</p> </div> </div> {#HTML_FOOTER} <script type="text/javascript"> var exeOnce = true; var units, ivEn; var mIvHtml = []; var mNum = 0; var total = Array(5).fill(0); function parseGeneric(obj) { if(true == exeOnce){ parseNav(obj); parseESP(obj); } parseRssi(obj); } function numBig(val, unit, des) { return ml("div", {class: "col-6 col-sm-4 a-c"}, [ ml("div", {class: "row"}, ml("div", {class: "col"}, [ ml("span", {class: "fs-5 fs-md-4"}, String(Math.round(val * 100) / 100)), ml("span", {class: "fs-6 fs-md-7 mx-1"}, unit) ])), ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-9 px-1"}, des) ) ) ]); } function numMid(val, unit, des) { return ml("div", {class: "col-6 col-sm-4 col-md-3 mb-2"}, [ ml("div", {class: "row"}, ml("div", {class: "col"}, [ ml("span", {class: "fs-6"}, String(Math.round(val * 100) / 100)), ml("span", {class: "fs-8 mx-1"}, unit) ])), ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-9"}, des) ) ) ]); } function totals() { for(var i = 0; i < 5; i++) { total[i] = Math.round(total[i] * 100) / 100; } return ml("div", {class: "row mt-3 mb-5"}, ml("div", {class: "col"}, [ ml("div", {class: "p-2 total-h"}, ml("div", {class: "row"}, ml("div", {class: "col mx-2 mx-md-1"}, "TOTAL") ), ), ml("div", {class: "p-2 total-bg"}, [ ml("div", {class: "row"}, [ numBig(total[0], "W", "AC Power"), numBig(total[1], "Wh", "Yield Day"), numBig(total[2], "kWh", "Yield Total") ]), ml("div", {class: "hr"}), ml("div", {class: "row"}, [ numMid(total[3], "W", "DC Power"), numMid(total[4], "var", "Reactive Power") ]) ]) ]) ); } function ivHead(obj) { if(0 != obj.status) { // only add totals if inverter is online total[0] += obj.ch[0][2]; // P_AC total[3] += obj.ch[0][8]; // P_DC total[4] += obj.ch[0][10]; // Q_AC } total[1] += obj.ch[0][7]; // YieldDay total[2] += obj.ch[0][6]; // YieldTotal var t = span(" °C"); var clh = (0 == obj.status) ? "iv-h-dis" : "iv-h"; var clbg = (0 == obj.status) ? "iv-bg-dis" : "iv-bg"; var pwrLimit = "n/a"; if(65535 != obj.power_limit_read) { pwrLimit = obj.power_limit_read + " %"; if(0 != obj.max_pwr) pwrLimit += ", " + (obj.max_pwr * obj.power_limit_read / 100) + "W"; } return ml("div", {class: "row mt-2"}, ml("div", {class: "col"}, [ ml("div", {class: "p-2 " + clh}, ml("div", {class: "row"}, [ 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"}, "Active Power Control: " + pwrLimit), ml("div", {class: "col a-c"}, ml("span", { class: "pointer", onclick: function() { getAjax("/api/inverter/alarm/" + obj.id, parseIvAlarm); }}, ("Alarms: " + obj.alarm_cnt))), ml("div", {class: "col a-r mx-2 mx-md-1"}, String(obj.ch[0][5]) + t.innerText) ]) ), ml("div", {class: "p-2 " + clbg}, [ ml("div", {class: "row"},[ numBig(obj.ch[0][2], "W", "AC Power"), numBig(obj.ch[0][7], "Wh", "Yield Day"), numBig(obj.ch[0][6], "kWh", "Yield Total") ]), ml("div", {class: "hr"}), ml("div", {class: "row mt-2"},[ numMid(obj.ch[0][11], "W", "Max AC Power"), numMid(obj.ch[0][8], "W", "DC Power"), numMid(obj.ch[0][0], "V", "AC Voltage"), numMid(obj.ch[0][1], "A", "AC Current"), numMid(obj.ch[0][3], "Hz", "Frequency"), numMid(obj.ch[0][9], "%", "Efficiency"), numMid(obj.ch[0][10], "var", "Reactive Power"), numMid(obj.ch[0][4], "", "Power Factor") ]) ]) ]) ); } function numCh(val, unit, des) { return ml("div", {class: "col-12 col-sm-6 col-md-12 mb-2"}, [ ml("div", {class: "row"}, ml("div", {class: "col"}, [ ml("span", {class: "fs-6 fs-md-7"}, String(Math.round(val * 100) / 100)), ml("span", {class: "fs-8 mx-2"}, unit) ])), ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-9"}, des) ) ) ]); } function ch(status, name, vals) { var clh = (0 == status) ? "iv-h-dis" : "ch-h"; var clbg = (0 == status) ? "iv-bg-dis" : "ch-bg"; return ml("div", {class: "col-6 col-md-3 mt-2"}, [ ml("div", {class: "p-2 a-c " + clh}, name), ml("div", {class: "p-2 " + clbg}, [ ml("div", {class: "row"}, [ numCh(vals[2], units[2], "DC Power"), numCh(vals[6], units[2], "Max Power"), numCh(vals[5], units[5], "Irradiation"), numCh(vals[3], units[3], "Yield Day"), numCh(vals[4], units[4], "Yield Total"), numCh(vals[0], units[0], "DC Voltage"), numCh(vals[1], units[1], "DC Current") ]) ]) ]); } function tsInfo(ts, gen, rssi) { var ageInfo = "Last received data requested at: "; if(ts > 0) { var date = new Date(ts * 1000); ageInfo += toIsoDateStr(date); } else ageInfo += "nothing received"; if(rssi > -127) { if(gen < 2) ageInfo += " (RSSI: " + ((rssi == -64) ? ">=" : "<") + " -64dBm)"; else ageInfo += " (RSSI: " + rssi + "dBm)"; } return ml("div", {class: "mb-5"}, [ ml("div", {class: "row p-1 ts-h mx-2"}, ml("div", {class: "col"}, "") ), ml("div", {class: "row p-2 ts-bg mx-2"}, ml("div", {class: "col mx-2"}, ageInfo) ) ]); } function parseIv(obj) { mNum++; var chn = []; for(var i = 1; i < obj.ch.length; i++) { var name = obj.ch_name[i]; if(name.length == 0) name = "CHANNEL " + i; if(obj.ch_max_pwr[i] > 0) // show channel only if max mod pwr chn.push(ch(obj.status, name, obj.ch[i])); } mIvHtml.push( ml("div", {}, [ ivHead(obj), ml("div", {class: "row mb-2"}, chn), tsInfo(obj.ts_last_success, obj.generation, obj.rssi) ]) ); var last = true; for(var i = obj.id + 1; i < ivEn.length; i++) { if((i != ivEn.length) && ivEn[i]) { last = false; getAjax("/api/inverter/id/" + i, parseIv); break; } } if(last) { if(mNum > 1) mIvHtml.unshift(totals()); document.getElementById("live").replaceChildren(...mIvHtml); } } function parseIvAlarm(obj) { var html = []; var offs = new Date().getTimezoneOffset() * -60; html.push( ml("div", {class: "row"}, [ ml("div", {class: "col"}, ml("strong", {}, "String")), ml("div", {class: "col"}, ml("strong", {}, "ID")), ml("div", {class: "col"}, ml("strong", {}, "Start")), ml("div", {class: "col"}, ml("strong", {}, "End")) ]) ); for(a of obj.alarm) { if(a.code != 0) { html.push( ml("div", {class: "row"}, [ ml("div", {class: "col mt-3"}, String(a.str)), ml("div", {class: "col mt-3"}, String(a.code)), ml("div", {class: "col mt-3"}, String(toIsoTimeStr(new Date((a.start + offs) * 1000)))), ml("div", {class: "col mt-3"}, String(toIsoTimeStr(new Date((a.end + offs) * 1000)))) ]) ); } } modal("Alarms of inverter #" + obj.iv_id, ml("div", {}, html)); } function parseIvVersion(obj) { var model; switch(obj.generation) { case 0: model = "MI-"; break; case 1: model = "HM-"; break; case 2: model = "HMS-"; break; case 3: model = "HMT-"; break; default: model = "???-"; break; } model += String(obj.max_pwr) + " (Serial: " + obj.serial + ")"; var html = ml("table", {class: "table"}, [ ml("tbody", {}, [ ml("tr", {}, [ ml("th", {}, "Model"), ml("td", {}, model) ]), ml("tr", {}, [ ml("th", {}, "Firmware Version / Build"), ml("td", {}, String(obj.fw_ver) + " (build: " + String(obj.fw_date) + " " + String(obj.fw_time) + ")") ]), ml("tr", {}, [ ml("th", {}, "Hardware Version / Build"), ml("td", {}, (obj.hw_ver/100).toFixed(2) + " (build: " + String(obj.prod_cw) + "/" + String(obj.prod_year) + ")") ]), ml("tr", {}, [ ml("th", {}, "Hardware Number"), ml("td", {}, obj.part_num.toString(16)) ]), ml("tr", {}, [ ml("th", {}, "Bootloader Version"), ml("td", {}, (obj.boot_ver/100).toFixed(2)) ]) ]) ]); modal("Info for inverter " + obj.name, ml("div", {}, html)); } function parse(obj) { if(null != obj) { parseGeneric(obj["generic"]); units = Object.assign({}, obj["fld_units"]); ivEn = Object.values(Object.assign({}, obj["iv"])); mIvHtml = []; mNum = 0; total.fill(0); for(var i = 0; i < obj.iv.length; i++) { if(obj.iv[i]) { getAjax("/api/inverter/id/" + i, parseIv); break; } } if(obj.refresh < 5) obj.refresh = 5; document.getElementById("refresh").innerHTML = obj.refresh; if(true == exeOnce) { window.setInterval("getAjax('/api/live', parse)", obj.refresh * 1000); exeOnce = false; } } else document.getElementById("refresh").innerHTML = "n/a"; } getAjax("/api/live", parse); </script> </body> </html>