Raum-Modal: Heizung, Rollladen und Licht auf einer Seite

Der Grundriss zeigt Raeume, ein Klick darauf zeigte aber nur den
Temperaturregler. Die Rollladen im selben Zimmer waren von dort gar nicht
erreichbar, obwohl die Karte genau sagt, wo sie haengen.

Das Modal zeigt jetzt alles, was dem Raum zugeordnet ist: die
Solltemperatur wie bisher, dazu je Geraet seine Kommandos. Kommandos ohne
Parameter werden Knoepfe nebeneinander - "Auf", "Zu", "Stop" will man
antippen und nicht erst einstellen -, Kommandos mit Parametern bekommen
ihre Eingabefelder und einen Ausloeser. Geraete ohne Kommandos zeigen ihre
Messwerte. Die Reihenfolge der Knoepfe kommt nicht mehr aus dem
Geraete-Suchlauf: die haeufig gebrauchten stehen vorn.

Ausgefuehrt wird ueber restricted/commands.php - das Gegenstueck zu
transports.py, nur fuer den Browser. Dieselbe Zuordnung "welche URL gehoert
zu welchem Weg", nur eben in PHP:

    mqtt://   Nutzlast auf das Topic des Parameters
    wled://   JSON-Vorlage mit Platzhaltern an /json/state
    http://   Abfrageargumente an die Geraete-URL
    Tahoma    exec/apply, erkannt an der Box-Kennung in der URL

Zwei Umsetzungen derselben Sache sind nicht schoen. Die Alternative waere,
den Runner um eine HTTP-Schnittstelle zu erweitern - dann haengt die
Bedienung daran, dass er laeuft - oder das Web-UI Python aufrufen zu
lassen. Beides waere teurer als diese Datei; der Kommentar sagt das an
beiden Stellen.

Der Endpunkt kennt einen Probelauf: mit "probe" beschreibt er, was er
schicken wuerde, ohne es zu schicken. Damit sind alle vier Wege geprueft,
ohne einen Rollladen in Bewegung zu setzen - Tahoma mit und ohne Parameter,
WLED mit gefuellter Vorlage, Shelly Gen1 und MQTT.

ajax/roomtemp.php entfaellt, es ging darin ausschliesslich um den Regler,
den das Raum-Modal jetzt enthaelt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-31 16:43:21 +02:00
co-authored by Claude Opus 5
parent bed1884b04
commit 98a1da6cd5
5 changed files with 543 additions and 78 deletions
+92 -30
View File
@@ -151,7 +151,7 @@ function switchFloor(floor){
var mqttData = {};
function openHeaterSettings(heater){
openModal(heater)
openRoomView(heater);
}
document.addEventListener('readystatechange', function () {
@@ -162,36 +162,98 @@ function switchFloor(floor){
});
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");
/**
* 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", "ö");
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'));
// 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(error => {
modalBodyElement.innerHTML += error.message;
.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;
}