Zusammenlegung
- Die sechs Endpunkte getConsData_/getProdData_{month,year,decade}.php sind
ajax/energyHistory.php?series=&range= gewichen, der gemeinsame Aufbau der
Chart-Daten steht in ajax/chartData.php. Die erzeugte SQL wurde gegen alle
sechs Originale abgeglichen.
- Die 13 Raeume der Home-Ansicht stehen nur noch in restricted/rooms.php.
home.php rendert die SVG-Kacheln daraus, homeMQTT.js bekommt dieselbe
Tabelle als JSON (346 -> 222 bzw. 428 -> 197 Zeilen).
- Gemeinsame JS-Helfer in js/solar/common.js; geteilte Bausteine fuer
Wetterkacheln, Chart-Skripte und das Formular der Ladesteuerung.
- Seitenvorlagen sind jetzt .php und werden eingebunden, statt per
file_get_contents zusammengesetzt zu werden.
Fehlerbehebungen
- ?action=weather fuehrte auf eine leere Seite, es gibt jetzt weather.php.
- homeMQTT.js griff auf ein #meteogram zu, das home.php nie hatte, und brach
damit den readystatechange-Handler ab.
- Die Heizungsseite abonnierte weatherStation/#, zeigte die Werte aber nie an.
Ladelast
- Chart.js, MQTT und Meteogramm werden nur noch auf den Seiten geladen, die
sie brauchen; die Seitentabelle dafuer steht in index.php.
- mqtt.js durch die minifizierte Fassung ersetzt (859 -> 359 KB). MQTT.js
5.14.1, byte-identisch mit dem signierten npm-Tarball.
- Rund 3,1 MB ungenutzte Vendor-Reste und AdminLTE-Demobilder entfernt.
- Solar 1796 -> 1240 KB, Home 1796 -> 873 KB, Historie 1796 -> 843 KB.
Speicher im Browser
- Chart.defaults.devicePixelRatio auf 1.5 gedeckelt. Die Zeichenpuffer der
Diagramme waren mit 20-45 MB der groesste Posten der Seite.
- Das Meteogramm-iframe laedt erst beim Hinscrollen.
Realtime-SVG
- Filterbereiche auf das noetige Mass verkleinert und den wirkungslosen
feOffset-Durchgang entfernt: 60 % weniger gerasterte Filterflaeche bei
pixelgleichem Ergebnis.
- Die Fluss-Animation laeuft ueber transform statt stroke-dashoffset, mit
Punkten statt Strichmuster; sie ruht, wo kein Fluss anliegt.
- Der Wertupdate schreibt nur noch bei echter Aenderung und benutzt
textContent statt innerHTML: 9 statt 86 Elemente pro Update, davon keines
mit Filter. describeArc rundet auf volle Grad.
- waterInfo auf scale(0.90) wie die uebrigen Info-Gruppen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
198 lines
7.0 KiB
JavaScript
198 lines
7.0 KiB
JavaScript
|
|
const homeMQTT = {
|
|
getMQTT: function () {
|
|
const id = Math.random().toString(36).substring(7);
|
|
const topic = "#";
|
|
const connection = "wss://mqtt.nas.el-wa.org:443"
|
|
mqttsolarTreeDone = false;
|
|
// const connection = "ws://username:password@37.97.203.138:8083" // Works
|
|
// const connection = "wss://public:public@public.cloud.shiftr.io" // Works
|
|
const client = mqtt.connect(connection, {
|
|
rejectUnauthorized: false,
|
|
});
|
|
|
|
client.on("message", messageReceived);
|
|
client.on("connect", function () {
|
|
client.subscribe("Raumtemp/#");
|
|
});
|
|
client.on("error", function (error) {
|
|
//alert("MQTT Error: " + error);
|
|
});
|
|
client.on('end', function () {
|
|
setTimeout(getMQTT, 5000);
|
|
alert("MQTT Disconnected, try to reconnect in 5 secs.");
|
|
})
|
|
|
|
function getNestedProp(obj, path) {
|
|
return path.split('/').reduce((acc, key) => acc && acc[key], obj);
|
|
}
|
|
function setNestedProp(obj, path, value) {
|
|
var schema = obj; // a moving reference to internal objects within obj
|
|
var pList = path.split('/');
|
|
var len = pList.length;
|
|
for (var i = 0; i < len - 1; i++) {
|
|
var elem = pList[i];
|
|
if (!schema[elem]) schema[elem] = {}
|
|
schema = schema[elem];
|
|
}
|
|
|
|
schema[pList[len - 1]] = value;
|
|
}
|
|
|
|
function messageReceived(topic, message) {
|
|
setNestedProp(mqttData, topic, message);
|
|
setTimeout(function () { homeSVG.updateValuesMQTT(mqttData) }, 200); //give the object tree some time to build up and receive all values
|
|
}
|
|
}
|
|
}
|
|
|
|
const homeSVG = {
|
|
updateCnt: 99,
|
|
fillElementArray: function () {
|
|
|
|
},
|
|
updateValuesMQTT: function (mqttData) {
|
|
// homeRooms liefert restricted/rooms.php (in home.php eingebettet).
|
|
// Jeder Raum bekommt die gleiche Behandlung - Raeume kommen dort dazu,
|
|
// nicht hier.
|
|
homeRooms.forEach(function (room) {
|
|
const values = mqttData["Raumtemp"]?.[room.floor]?.[room.mqtt];
|
|
if (!values) return; // Topic noch nicht empfangen
|
|
const el = (part) => document.getElementById(room.floor + "_" + part);
|
|
|
|
if (typeof (values["Heating"]) != "undefined")
|
|
el(room.id + "_heater").setAttribute("display", values["Heating"] == "false" ? "none" : "");
|
|
if (typeof (values["mode"]) != "undefined")
|
|
el(room.id + "_buffer").setAttribute("display", values["mode"] == "Overheating" ? "" : "none");
|
|
if (typeof (values["Set Temp[degC]"]) != "undefined")
|
|
el("pres_" + room.id).innerHTML = Math.floor(values["Set Temp[degC]"]) + "\u2009\u00b0C";
|
|
if (typeof (values["Temp[degC]"]) != "undefined")
|
|
el("tmp_" + room.id).innerHTML = values["Temp[degC]"] + "\u2009\u00b0C";
|
|
if (typeof (values["rHum[%]"]) != "undefined")
|
|
el("hum_" + room.id).innerHTML = values["rHum[%]"] + "\u2009%rF";
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
var currentFloor = "OG";
|
|
|
|
function addClass(el, classNameToAdd){
|
|
el.className += ' ' + classNameToAdd;
|
|
}
|
|
|
|
function removeClass(el, classNameToRemove){
|
|
var elClass = ' ' + el.className + ' ';
|
|
while(elClass.indexOf(' ' + classNameToRemove + ' ') !== -1){
|
|
elClass = elClass.replace(' ' + classNameToRemove + ' ', '');
|
|
}
|
|
el.className = elClass;
|
|
}
|
|
|
|
function switchTab(newtab){
|
|
newContent = document.getElementById(newtab);
|
|
newTabBtn = document.getElementById(newtab+"-tab");
|
|
removeClass(document.getElementById("actions-OG-tab"),"active");
|
|
removeClass(document.getElementById("actions-EG-tab"),"active");
|
|
removeClass(document.getElementById("actions-UG-tab"),"active");
|
|
|
|
removeClass(document.getElementById("actions-OG"),"active show");
|
|
removeClass(document.getElementById("actions-EG"),"active show");
|
|
removeClass(document.getElementById("actions-UG"),"active show");
|
|
addClass(newTabBtn,"active");
|
|
addClass(newContent,"active show");
|
|
|
|
|
|
}
|
|
|
|
|
|
function switchFloor(floor){
|
|
if(currentFloor == floor)
|
|
return;
|
|
const targetIn = document.getElementById(floor+'_Info');
|
|
const targetOut = document.getElementById(currentFloor+'_Info');
|
|
var blendIn = new KeyframeEffect(
|
|
targetIn, [{opacity: '0'},{opacity: '100'}],
|
|
{
|
|
duration: 500,
|
|
easing: "ease-in-out",
|
|
fill: "forwards",
|
|
iterations: 1,
|
|
}
|
|
);
|
|
var blendOut = new KeyframeEffect(
|
|
targetOut, [{opacity: '100'},{opacity: '0'}],
|
|
{
|
|
duration: 500,
|
|
easing: "ease-in-out",
|
|
fill: "forwards",
|
|
iterations: 1,
|
|
}
|
|
);
|
|
var inAnim = new Animation(blendIn,document.timeline);
|
|
var outAnim = new Animation(blendOut,document.timeline);
|
|
targetIn.setAttribute("display","");
|
|
outAnim.onfinish= (event) => {
|
|
targetOut.setAttribute("display","none");
|
|
};
|
|
inAnim.play();
|
|
outAnim.play();
|
|
currentFloor = floor;
|
|
}
|
|
|
|
|
|
var modalEV = new bootstrap.Modal(document.getElementById('modalEV'), {
|
|
keyboard: false
|
|
});
|
|
var offcanvas = new bootstrap.Offcanvas(document.getElementById('offcanvas'), {
|
|
keyboard: false
|
|
});
|
|
|
|
var mqttData = {};
|
|
|
|
function openHeaterSettings(heater){
|
|
openModal(heater)
|
|
}
|
|
|
|
document.addEventListener('readystatechange', function () {
|
|
if (event.target.readyState === "complete") {
|
|
homeMQTT.getMQTT();
|
|
mountMeteogram();
|
|
}
|
|
});
|
|
|
|
|
|
function openModal(heater) {
|
|
var contentURL = "./ajax/roomtemp.php?heater="+heater;
|
|
let heaterMQTT = heater.toString().split("_");
|
|
document.getElementById("modal-title").innerHTML = "Thermostat "+heater.replace("_"," ").replace("ue","ü").replace("ae","ä").replace("oe","ö");
|
|
modalBodyElement = document.getElementById("modal-body");
|
|
modalBodyElement.innerHTML = loadingHTML("Wird geladen...");
|
|
document.getElementById("modalSaveBtn").addEventListener("click", submitFormAjax);
|
|
document.getElementById("modalSaveBtn").contentURL = contentURL;
|
|
modalEV.show();
|
|
fetch(contentURL, {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-Requested-From-Modal': 'a',
|
|
'Requested-With-Ajax': 'ajax'
|
|
}
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
modalBodyElement.innerHTML = html;
|
|
var slider = document.getElementById("modal-slider");
|
|
var span = document.getElementById("modal-slider-label");
|
|
bindModalSlider(slider, span, value => value + " °C");
|
|
|
|
if(typeof(mqttData["Raumtemp"][heaterMQTT[0]][heaterMQTT[1]]["Set Temp[degC]"])!= "undefined")
|
|
slider.setAttribute("value",mqttData["Raumtemp"][heaterMQTT[0]][heaterMQTT[1]]["Set Temp[degC]"]);
|
|
else
|
|
slider.setAttribute("value",10);
|
|
slider.dispatchEvent(new Event('input'));
|
|
})
|
|
.catch(error => {
|
|
modalBodyElement.innerHTML += error.message;
|
|
});
|
|
}
|