Geraete den Raeumen zuordnen
Beim Anlegen einer Automatik denkt man in Raeumen ("im Bad soll ..."), nicht
in Geraetearten. Ein Raummodell gibt es schon - restricted/rooms.php, die
einzige Quelle fuer die Home-Ansicht -, nur fehlte die Zuordnung Geraet ->
Raum.
Die neue Spalte actors.room haelt sie als "Etage/Raum" (OG/Bad), dazu
"aussen" fuer Carport, Garage und Regensensor. Etage und Raum zusammen,
weil "Bad" allein auf drei Etagen vorkommt. Discovery fasst die Spalte nicht
an - der Upsert setzt nur type und name -, die Zuordnung ueberlebt also
jeden Suchlauf.
Zugeordnet wird ueber das Haus-Symbol in der Karte "Automatismen". Wo der
Raum aus dem MQTT-Topic hervorgeht ("Raumtemp/OG/Bad/Temp[degC]"), schlaegt
die Ansicht ihn vor und uebernimmt ihn auf Knopfdruck - das sind die
dreizehn Thermostate, ohne jedes Raten. Bei allen anderen wird bewusst nicht
geraten: "Bad Links" sagt nichts darueber, auf welcher der drei Etagen
dieses Bad liegt. Noch nicht zugeordnete Geraete stehen in der Tabelle oben.
Die Geraeteauswahl im Editor gruppiert danach nach Raum, in der Reihenfolge
aus rooms.php. Solange niemand zugeordnet hat, bleibt es bei der Gruppierung
nach Geraeteart - eine einzige Gruppe "Nicht zugeordnet" waere ein
Rueckschritt. Und weil auch nach der ersten Zuordnung noch viel ohne Raum
ist, wird dieser Rest weiter nach Geraeteart unterteilt ("Ohne Raum ·
Aussenjalousie (11)").
Anders als bei den Gerätearten behalten Raeume mit nur einem Geraet ihre
Ueberschrift: "OG · Bad" ist auch mit einem einzigen Thermostat genau die
Zeile, die man sucht - und mehr als eines haengt selten in einem Raum.
Die dreizehn Thermostate sind bereits zugeordnet, weil das ihre eigenen
Topics hergeben; alles andere steht auf "-" und will von Hand vergeben
werden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+113
-17
@@ -20,6 +20,7 @@
|
||||
let autoModel = null;
|
||||
let autoDevices = [];
|
||||
let autoLockouts = [];
|
||||
let autoRooms = [];
|
||||
|
||||
// --- Katalog-Helfer ------------------------------------------------------
|
||||
|
||||
@@ -81,30 +82,74 @@ function option(e, gewaehlt) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Geraeteliste nach Geraeteart gruppiert.
|
||||
* Geraeteliste gruppiert - nach Raum, wo zugeordnet, sonst nach Geraeteart.
|
||||
*
|
||||
* Bei ueber fuenfzig Geraeten ist die flache Liste der Engpass: dreizehn
|
||||
* Thermostate und elf Jalousien stehen sonst als vierundzwanzig gleich
|
||||
* aussehende Zeilen untereinander. Arten mit nur einem Geraet bekommen
|
||||
* keine eigene Ueberschrift - fuenfzehn Gruppen mit je einem Eintrag waeren
|
||||
* unuebersichtlicher als die flache Liste. Sie sammeln sich am Ende.
|
||||
* Beim Anlegen einer Automatik denkt man in Raeumen ("im Bad soll..."), nicht
|
||||
* in Geraetearten. Der Raum steht aber nur da, wo er von Hand vergeben wurde
|
||||
* (siehe Zuordnung ueber das Zahnrad in der Karte "Automatismen"); solange
|
||||
* das niemand getan hat, bleibt die Gruppierung nach Art - besser als eine
|
||||
* einzige Gruppe "Nicht zugeordnet".
|
||||
*/
|
||||
function geraeteGruppen(geraete) {
|
||||
const nachArt = new Map();
|
||||
if (!geraete.some(d => d.room)) {
|
||||
return gruppiere(geraete, d => d.type, null, "Einzelne", true);
|
||||
}
|
||||
const gruppen = gruppiere(geraete, d => d.roomLabel, raumReihenfolge(),
|
||||
"Nicht zugeordnet", false);
|
||||
// Was noch keinen Raum hat, bleibt sonst ein einziger Eimer - solange erst
|
||||
// ein Teil zugeordnet ist, waeren das die meisten Geraete. Innerhalb des
|
||||
// Eimers gilt deshalb weiter die Ordnung nach Geraeteart.
|
||||
const letzte = gruppen[gruppen.length - 1];
|
||||
if (!letzte || !letzte.titel.startsWith("Nicht zugeordnet")) return gruppen;
|
||||
gruppen.pop();
|
||||
return gruppen.concat(
|
||||
gruppiere(letzte.geraete, d => d.type, null, "Einzelne", true)
|
||||
.map(g => ({ titel: "Ohne Raum · " + g.titel, geraete: g.geraete })));
|
||||
}
|
||||
|
||||
/** Reihenfolge der Raum-Ueberschriften, wie in rooms.php notiert. */
|
||||
function raumReihenfolge() {
|
||||
return autoRooms.map(r => r.label).concat(["Nicht zugeordnet"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nach einem Merkmal gruppieren.
|
||||
*
|
||||
* `einzelneSammeln` entscheidet, was mit Gruppen aus einem einzigen Geraet
|
||||
* passiert. Bei Gerätearten sollen sie zusammenfallen - fuenfzehn
|
||||
* Ueberschriften mit je einem Eintrag waeren unuebersichtlicher als die
|
||||
* flache Liste. Bei Raeumen nicht: "OG · Bad" ist auch mit einem einzigen
|
||||
* Geraet die Ueberschrift, die man sucht, und in der Wohnung haengt oft nur
|
||||
* ein Thermostat je Raum.
|
||||
*/
|
||||
function gruppiere(geraete, merkmal, reihenfolge, restTitel, einzelneSammeln) {
|
||||
const nach = new Map();
|
||||
geraete.forEach(d => {
|
||||
if (!nachArt.has(d.type)) nachArt.set(d.type, []);
|
||||
nachArt.get(d.type).push(d);
|
||||
const schluessel = merkmal(d) || restTitel;
|
||||
if (!nach.has(schluessel)) nach.set(schluessel, []);
|
||||
nach.get(schluessel).push(d);
|
||||
});
|
||||
const titel = Array.from(nach.keys());
|
||||
titel.sort((a, b) => {
|
||||
if (reihenfolge) {
|
||||
const ia = reihenfolge.indexOf(a), ib = reihenfolge.indexOf(b);
|
||||
if (ia !== ib) return (ia < 0 ? 999 : ia) - (ib < 0 ? 999 : ib);
|
||||
}
|
||||
return a.localeCompare(b, "de");
|
||||
});
|
||||
const gruppen = [];
|
||||
const einzelne = [];
|
||||
Array.from(nachArt.keys()).sort((a, b) => a.localeCompare(b, "de")).forEach(art => {
|
||||
const liste = nachArt.get(art);
|
||||
if (liste.length > 1) gruppen.push({ titel: art + " (" + liste.length + ")", geraete: liste });
|
||||
else einzelne.push(liste[0]);
|
||||
const rest = [];
|
||||
titel.forEach(t => {
|
||||
const liste = nach.get(t);
|
||||
if (!einzelneSammeln || liste.length > 1 || t === restTitel) {
|
||||
gruppen.push({ titel: t + " (" + liste.length + ")", geraete: liste });
|
||||
} else {
|
||||
rest.push(liste[0]);
|
||||
}
|
||||
});
|
||||
if (einzelne.length) {
|
||||
einzelne.sort((a, b) => a.label.localeCompare(b.label, "de"));
|
||||
gruppen.push({ titel: "Einzelne", geraete: einzelne });
|
||||
if (rest.length) {
|
||||
rest.sort((a, b) => a.label.localeCompare(b.label, "de"));
|
||||
gruppen.push({ titel: restTitel + " (" + rest.length + ")", geraete: rest });
|
||||
}
|
||||
return gruppen;
|
||||
}
|
||||
@@ -545,6 +590,7 @@ function loadAutomatic() {
|
||||
const daten = JSON.parse(document.getElementById("autoActionData").textContent);
|
||||
autoDevices = daten.devices;
|
||||
autoLockouts = daten.lockouts;
|
||||
autoRooms = daten.rooms || [];
|
||||
|
||||
const a = daten.automation;
|
||||
autoModel = {
|
||||
@@ -687,6 +733,56 @@ function refreshAutomations(floor) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Zuordnung Geraet -> Raum. Eigenes Modal, weil es kein Teil einer einzelnen
|
||||
* Automatik ist, sondern einmal fuer alle gilt.
|
||||
*/
|
||||
function openRoomModal() {
|
||||
document.getElementById("modal-title").innerHTML = "Geräte den Räumen zuordnen";
|
||||
const alt = document.getElementById("modalSaveBtn");
|
||||
const btn = alt.cloneNode(true);
|
||||
alt.replaceWith(btn);
|
||||
btn.addEventListener("click", speichereRaeume);
|
||||
|
||||
const dialog = document.querySelector("#modalEV .modal-dialog");
|
||||
dialog.classList.add("modal-lg");
|
||||
document.getElementById("modalEV").addEventListener("hidden.bs.modal",
|
||||
() => dialog.classList.remove("modal-lg"), { once: true });
|
||||
|
||||
const koerper = document.getElementById("modal-body");
|
||||
koerper.innerHTML = loadingHTML("Wird geladen...");
|
||||
modalEV.show();
|
||||
|
||||
fetch("./ajax/AutoAction.php?action=rooms", {
|
||||
method: "GET", headers: { "Requested-With-Ajax": "ajax" }
|
||||
})
|
||||
.then(a => a.text())
|
||||
.then(html => {
|
||||
koerper.innerHTML = html;
|
||||
const tipps = document.getElementById("btnRoomTipps");
|
||||
if (tipps) {
|
||||
tipps.addEventListener("click", () => {
|
||||
koerper.querySelectorAll("select[data-tipp]").forEach(s => {
|
||||
if (!s.value) s.value = s.dataset.tipp;
|
||||
});
|
||||
tipps.disabled = true;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(fehler => { koerper.innerHTML = "Konnte die Zuordnung nicht laden: " + fehler.message; });
|
||||
}
|
||||
|
||||
function speichereRaeume() {
|
||||
const rooms = {};
|
||||
document.querySelectorAll("#modal-body select[data-actor]").forEach(s => {
|
||||
rooms[s.dataset.actor] = s.value;
|
||||
});
|
||||
automationRequest("saveRooms", { rooms: rooms }).then(antwort => {
|
||||
if (antwort.error) alert(antwort.error);
|
||||
else modalEV.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAutomation(id) {
|
||||
automationRequest("toggle", { id: id }).then(antwort => {
|
||||
if (antwort.error) alert(antwort.error);
|
||||
|
||||
Reference in New Issue
Block a user