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){ openRoomView(heater); } document.addEventListener('readystatechange', function () { if (event.target.readyState === "complete") { homeMQTT.getMQTT(); mountMeteogram(); } }); /** * Alles, was in einem Raum haengt, auf einer Seite: Heizung, Rollladen, * Licht. Der Grundriss zeigt Raeume, also sollte ein Klick darauf den Raum * oeffnen - vorher kam nur der Temperaturregler, die Rollladen im selben * Zimmer waren von hier gar nicht erreichbar. * * Welche Geraete dazugehoeren, sagt die Raumzuordnung (actors.room). */ function openRoomView(raum) { const teile = raum.toString().split("_"); document.getElementById("modal-title").innerHTML = teile[0] + " · " + teile.slice(1).join(" ") .replace("ue", "ü").replace("ae", "ä").replace("oe", "ö"); // Der Speichern-Knopf gehoert allen Modals gemeinsam. Ein Klon ohne // Zuhoerer stellt sicher, dass nicht auch der Handler eines vorher // geoeffneten Modals mitfeuert. const alt = document.getElementById("modalSaveBtn"); const btn = alt.cloneNode(true); alt.replaceWith(btn); btn.addEventListener("click", () => sendeSolltemperatur(raum)); const koerper = document.getElementById("modal-body"); koerper.innerHTML = loadingHTML("Wird geladen..."); modalEV.show(); fetch("./ajax/room.php?room=" + encodeURIComponent(raum), { method: "GET", headers: { "X-Requested-From-Modal": "a", "Requested-With-Ajax": "ajax" } }) .then(a => a.text()) .then(html => { koerper.innerHTML = html; const slider = document.getElementById("modal-slider"); if (slider) { bindModalSlider(slider, document.getElementById("modal-slider-label"), wert => wert + " °C"); const stand = mqttData["Raumtemp"]?.[teile[0]]?.[teile[1]]?.["Set Temp[degC]"]; slider.value = typeof stand !== "undefined" ? stand : 10; slider.dispatchEvent(new Event("input")); } // Ohne Thermostat gibt es nichts zu speichern - dann waere der Knopf // eine Falle. btn.hidden = !slider; koerper.querySelectorAll("button[data-cmd]").forEach(k => { k.addEventListener("click", () => schalte(k)); }); }) .catch(fehler => { koerper.innerHTML = "Konnte den Raum nicht laden: " + fehler.message; }); } /** Ein Kommando des angeklickten Knopfes ausfuehren. */ function schalte(knopf) { const zeile = knopf.closest("[data-cmdrow]"); const params = {}; if (zeile) { zeile.querySelectorAll("[data-param]").forEach(f => { params[f.dataset.param] = f.value; }); } knopf.disabled = true; raumRequest("command", { command_id: Number(knopf.dataset.cmd), params: params }) .then(antwort => { knopf.disabled = false; raumMeldung(antwort); }); } function sendeSolltemperatur(raum) { const slider = document.getElementById("modal-slider"); if (!slider) return; raumRequest("temp", { room: raum, temp: Number(slider.value) }) .then(antwort => { raumMeldung(antwort); if (!antwort.error) setTimeout(() => modalEV.hide(), 700); }); } function raumRequest(action, body) { return fetch("./ajax/room.php?action=" + action, { method: "POST", headers: { "Content-Type": "application/json", "Requested-With-Ajax": "ajax" }, body: JSON.stringify(body) }) .then(a => a.json()) .catch(fehler => ({ error: fehler.message })); } /** Rueckmeldung im Modal statt als alert - man schaltet oft mehrmals. */ function raumMeldung(antwort) { const ziel = document.getElementById("roomFeedback"); if (!ziel) return; ziel.className = "small mt-2 " + (antwort.error ? "text-danger" : "text-body-secondary"); ziel.textContent = antwort.error ? antwort.error : "✓ " + antwort.ok; }