Raeume ueber ihre feste Nummer: actors.room_id, Raum-Modal, Thermostat-Zweig
Geraete hingen ueber den Text "Etage/Raum" an ihrem Raum, das Raum-Modal zerlegte "EG_Bad" am Unterstrich, und das Thermostat-Topic wurde aus Etage und Name zusammengesetzt. Ein umbenannter Raum haette still alle Geraete, seinen Temperaturregler und sein Heizsymbol verloren. - actors.room (Text) -> actors.room_id, Fremdschluessel auf rooms, beim Loeschen eines Raums SET NULL. Alle 59 Zuordnungen uebernommen. - automations.floor: ENUM(UG,EG,OG,AG) -> Verweis auf floors. Eine neue Etage kann damit auch Automatiken tragen. - room.php nimmt ?room=<Nummer>; Solltemperatur geht an rooms.thermostat + /changeSetTemp. - homeMQTT.js: Heizsymbol, Regler und Ist-Werte ueber mqttZweig(thermostat) statt mqttData.Raumtemp[Etage][Name]. - Raumvorschlaege in den Einstellungen: Messwerte unter dem Thermostat- Zweig eines Raums statt fest Raumtemp/<Etage>/<Name>. - Schluessel in raumListe(), Katalog und Maske ist die Raumnummer als Text. Geprueft gegen den Stand davor: Startseite gleich bis auf das Klickziel, jede der 25 Kacheln oeffnet ihren eigenen Raum; Geraeteliste, Vorschlaege, Editor-Katalog und die eigenen Geraete je Kachel gleich. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+49
-39
@@ -333,9 +333,12 @@ function anzeigeName($name)
|
||||
/**
|
||||
* Waehlbare Raeume: die Raeume der Home-Ansicht.
|
||||
*
|
||||
* Einzige Quelle bleibt restricted/rooms.php - ein neuer Raum ist dort eine
|
||||
* Zeile und taucht hier automatisch auf. Der Schluessel ist "Etage/Raum",
|
||||
* weil "Bad" allein auf drei Etagen vorkommt.
|
||||
* Einzige Quelle bleibt restricted/rooms.php (dahinter homeMesh.rooms) - ein
|
||||
* neuer Raum taucht hier automatisch auf. Der Schluessel ist die feste
|
||||
* Raumnummer als Text, so wie actors.room_id sie traegt. Frueher war es
|
||||
* "Etage/Raum" - ein umbenannter Raum verlor damit still alle Geraete. Als
|
||||
* Text, weil er im Browser mit dem Wert einer Auswahlliste verglichen wird,
|
||||
* und der ist immer einer.
|
||||
*
|
||||
* Frueher stand hier ein Pseudo-Raum "aussen" fuer alles, was zu keinem Raum
|
||||
* der Wohnung gehoert - Carport, Pergola, Garage, die Wechselrichter. Das war
|
||||
@@ -353,7 +356,7 @@ function raumListe()
|
||||
$liste = [];
|
||||
foreach (allRooms() as $raum) {
|
||||
$liste[] = [
|
||||
"key" => $raum["floor"] . "/" . $raum["mqtt"],
|
||||
"key" => strval($raum["nr"]),
|
||||
"label" => $raum["floor"] . " · " . $raum["mqtt"],
|
||||
"floor" => $raum["floor"],
|
||||
"room" => $raum["mqtt"],
|
||||
@@ -365,17 +368,17 @@ function raumListe()
|
||||
/** Anzeigename eines gespeicherten Raumschluessels. */
|
||||
function raumBeschriftung($key)
|
||||
{
|
||||
if (!$key) {
|
||||
if ($key === null || $key === "") {
|
||||
return "Nicht zugeordnet";
|
||||
}
|
||||
foreach (raumListe() as $raum) {
|
||||
if ($raum["key"] === $key) {
|
||||
if ($raum["key"] === strval($key)) {
|
||||
return $raum["label"];
|
||||
}
|
||||
}
|
||||
// Raum aus rooms.php entfernt - der Eintrag am Geraet bleibt stehen,
|
||||
// damit eine Umbenennung dort keine Zuordnung stillschweigend loescht.
|
||||
return $key . " (unbekannt)";
|
||||
// Kann nur noch an der Datenbank vorbei entstehen: fk_actors_room setzt
|
||||
// die Zuordnung beim Loeschen eines Raums auf NULL.
|
||||
return "Raum " . $key . " (unbekannt)";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,43 +392,46 @@ function raumBeschriftung($key)
|
||||
*/
|
||||
function raumEinordnung($key)
|
||||
{
|
||||
if (!$key) {
|
||||
if ($key === null || $key === "") {
|
||||
return ["floor" => "Ohne Raum", "room" => ""];
|
||||
}
|
||||
foreach (raumListe() as $raum) {
|
||||
if ($raum["key"] === $key) {
|
||||
if ($raum["key"] === strval($key)) {
|
||||
return ["floor" => $raum["floor"], "room" => $raum["room"]];
|
||||
}
|
||||
}
|
||||
return ["floor" => "Unbekannter Raum", "room" => $key];
|
||||
return ["floor" => "Unbekannter Raum", "room" => strval($key)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Raumvorschlaege aus den MQTT-Topics.
|
||||
*
|
||||
* Die Thermostate tragen ihren Raum im Topic: "Raumtemp/OG/Bad/Temp[degC]".
|
||||
* Das ist keine Rateaufgabe, sondern dieselbe Schreibweise wie in rooms.php,
|
||||
* und deckt dreizehn Geraete ohne Zutun ab. Bei allen anderen wird bewusst
|
||||
* nicht geraten: "Bad Links" sagt nichts darueber, auf welcher der drei
|
||||
* Etagen dieses Bad liegt.
|
||||
* Ein Thermostat meldet unter dem Zweig, der am Raum eingetragen ist
|
||||
* (rooms.thermostat, etwa "Raumtemp/OG/Bad"). Ein Messwert darunter gehoert
|
||||
* also zu diesem Raum - das ist keine Rateaufgabe und deckt die Thermostate
|
||||
* ohne Zutun ab. Bei allen anderen wird bewusst nicht geraten: "Bad Links"
|
||||
* sagt nichts darueber, auf welcher Etage dieses Bad liegt.
|
||||
*/
|
||||
function raumVorschlaege()
|
||||
{
|
||||
$bekannt = [];
|
||||
foreach (raumListe() as $raum) {
|
||||
$bekannt[strtolower($raum["key"])] = $raum["key"];
|
||||
$zweige = [];
|
||||
foreach (allRooms() as $raum) {
|
||||
if (!empty($raum["thermostat"])) {
|
||||
$zweige[strtolower(rtrim($raum["thermostat"], "/")) . "/"] = strval($raum["nr"]);
|
||||
}
|
||||
}
|
||||
$res = meshDb()->query("SELECT actor_id, url FROM actor_states
|
||||
WHERE url LIKE 'Raumtemp/%/%/%'");
|
||||
if (!$zweige) {
|
||||
return [];
|
||||
}
|
||||
$res = meshDb()->query("SELECT actor_id, url FROM actor_states WHERE url LIKE '%/%'");
|
||||
$vorschlag = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$teile = explode("/", $row["url"]);
|
||||
if (count($teile) < 3) {
|
||||
continue;
|
||||
}
|
||||
$key = strtolower($teile[1] . "/" . $teile[2]);
|
||||
if (isset($bekannt[$key])) {
|
||||
$vorschlag[intval($row["actor_id"])] = $bekannt[$key];
|
||||
$url = strtolower($row["url"]);
|
||||
foreach ($zweige as $zweig => $key) {
|
||||
if (strpos($url, $zweig) === 0) {
|
||||
$vorschlag[intval($row["actor_id"])] = $key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $vorschlag;
|
||||
@@ -436,7 +442,7 @@ function saveRooms($zuordnung)
|
||||
{
|
||||
$db = meshDb();
|
||||
$erlaubt = array_column(raumListe(), "key");
|
||||
$stmt = $db->prepare("UPDATE actors SET room = ? WHERE id = ?");
|
||||
$stmt = $db->prepare("UPDATE actors SET room_id = ? WHERE id = ?");
|
||||
$anzahl = 0;
|
||||
foreach ($zuordnung as $actorId => $key) {
|
||||
$actorId = intval($actorId);
|
||||
@@ -447,8 +453,8 @@ function saveRooms($zuordnung)
|
||||
if ($key !== "" && !in_array($key, $erlaubt, true)) {
|
||||
throw new InvalidArgumentException("Unbekannter Raum: " . $key);
|
||||
}
|
||||
$wert = $key === "" ? null : $key;
|
||||
$stmt->bind_param("si", $wert, $actorId);
|
||||
$wert = $key === "" ? null : intval($key);
|
||||
$stmt->bind_param("ii", $wert, $actorId);
|
||||
$stmt->execute();
|
||||
$anzahl++;
|
||||
}
|
||||
@@ -475,7 +481,8 @@ function geraeteListe()
|
||||
$tipps = raumVorschlaege();
|
||||
|
||||
$geraete = [];
|
||||
$res = $db->query("SELECT id, name, type, url, room FROM actors");
|
||||
// room_id als Text: derselbe Schluessel wie in raumListe(), siehe dort.
|
||||
$res = $db->query("SELECT id, name, type, url, CAST(room_id AS CHAR) AS room FROM actors");
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$id = intval($row["id"]);
|
||||
$wo = raumEinordnung($row["room"]);
|
||||
@@ -488,6 +495,7 @@ function geraeteListe()
|
||||
"room" => $row["room"],
|
||||
"roomLabel" => raumBeschriftung($row["room"]),
|
||||
"floor" => $wo["floor"],
|
||||
"raumName" => $wo["room"],
|
||||
"tipp" => $tipps[$id] ?? null,
|
||||
"messwerte" => 0,
|
||||
"kommandos" => 0,
|
||||
@@ -542,17 +550,19 @@ function geraeteListe()
|
||||
$reihenfolge = array_flip($GLOBALS["floors"]);
|
||||
$liste = array_values($geraete);
|
||||
usort($liste, function ($a, $b) use ($reihenfolge) {
|
||||
$wa = explode("/", (string)$a["room"], 2);
|
||||
$wb = explode("/", (string)$b["room"], 2);
|
||||
// Geraete ohne Raum nach oben: sie sind das, was Arbeit macht.
|
||||
$ra = ($a["room"] === null || $a["room"] === "") ? -1 : ($reihenfolge[$wa[0]] ?? 98);
|
||||
$rb = ($b["room"] === null || $b["room"] === "") ? -1 : ($reihenfolge[$wb[0]] ?? 98);
|
||||
$ra = ($a["room"] === null || $a["room"] === "") ? -1 : ($reihenfolge[$a["floor"]] ?? 98);
|
||||
$rb = ($b["room"] === null || $b["room"] === "") ? -1 : ($reihenfolge[$b["floor"]] ?? 98);
|
||||
if ($ra !== $rb) {
|
||||
return $ra <=> $rb;
|
||||
}
|
||||
$raum = strcoll($wa[1] ?? "", $wb[1] ?? "");
|
||||
$raum = strcoll($a["raumName"], $b["raumName"]);
|
||||
return $raum !== 0 ? $raum : strcoll($a["label"], $b["label"]);
|
||||
});
|
||||
// raumName war nur fuer die Sortierung da; die Maske liest roomLabel.
|
||||
foreach ($liste as $i => $g) {
|
||||
unset($liste[$i]["raumName"]);
|
||||
}
|
||||
return $liste;
|
||||
}
|
||||
|
||||
@@ -657,7 +667,7 @@ function deviceCatalog()
|
||||
$devices = [];
|
||||
|
||||
$labels = geraeteBeschriftungen();
|
||||
$res = $db->query("SELECT id, name, type, url, room FROM actors");
|
||||
$res = $db->query("SELECT id, name, type, url, CAST(room_id AS CHAR) AS room FROM actors");
|
||||
$zeilen = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$zeilen[] = $row;
|
||||
|
||||
Reference in New Issue
Block a user