diff --git a/ajax/AutoAction.php b/ajax/AutoAction.php index d55b855..e3064d8 100644 --- a/ajax/AutoAction.php +++ b/ajax/AutoAction.php @@ -33,7 +33,7 @@ function jsonAntwort($data, $code = 200) } $action = $_GET["action"] ?? "editor"; -$floor = in_array($_GET["floor"] ?? "", ["UG", "EG", "OG"], true) ? $_GET["floor"] : ""; +$floor = in_array($_GET["floor"] ?? "", $floors, true) ? $_GET["floor"] : ""; // --- Schreibende Aufrufe ------------------------------------------------- if ($_SERVER["REQUEST_METHOD"] === "POST") { @@ -234,9 +234,9 @@ $nutzdaten = json_encode([ die Reiter, damit der Rueckfall auf den ersten Eintrag dieselbe Etage trifft wie die Startseite. --> diff --git a/homeMesh_automations.sql b/homeMesh_automations.sql index 71a20d4..412f689 100644 --- a/homeMesh_automations.sql +++ b/homeMesh_automations.sql @@ -29,7 +29,7 @@ SET NAMES utf8mb4; CREATE TABLE IF NOT EXISTS `automations` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT 'Anzeigename, z.B. Kinder zu', - `floor` enum('UG','EG','OG') NOT NULL DEFAULT 'OG' COMMENT 'Reiter in der Uebersicht. Ohne Etage waere die Automatik in keinem davon zu sehen, deshalb gibt es keinen leeren Wert', + `floor` enum('UG','EG','OG','AG') NOT NULL DEFAULT 'OG' COMMENT 'Reiter in der Uebersicht. Ohne Etage waere die Automatik in keinem davon zu sehen, deshalb gibt es keinen leeren Wert. AG ist das Aussengelaende - eine Etage ohne Grundriss, siehe restricted/rooms.php', `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Aktiv oder pausiert', `window_from` time NOT NULL DEFAULT '00:00:00' COMMENT 'Aktiver Zeitraum, Beginn', `window_to` time NOT NULL DEFAULT '23:59:00' COMMENT 'Aktiver Zeitraum, Ende. Kleiner als window_from = ueber Mitternacht', diff --git a/js/solar/autoActionFuncs.js b/js/solar/autoActionFuncs.js index 50d279d..e5d9e98 100644 --- a/js/solar/autoActionFuncs.js +++ b/js/solar/autoActionFuncs.js @@ -765,9 +765,17 @@ function automationRequest(action, body) { .catch(error => ({ error: error.message })); } -/** Die Tabelle einer Etage neu holen. Ohne Etage alle drei. */ +/** + * Die Tabelle einer Etage neu holen. Ohne Etage alle, die es gibt. + * + * Welche das sind, sagen die Reiter - home.php baut sie aus der Raumtabelle. + * Eine feste Liste hier haette beim Aussengelaende still eine Etage + * ausgelassen: die Tabelle waere leer geblieben, ohne Fehlermeldung. + */ function refreshAutomations(floor) { - const etagen = floor ? [floor] : ["UG", "EG", "OG"]; + const etagen = floor ? [floor] + : Array.from(document.querySelectorAll("#actions-tabContent .tab-pane")) + .map(pane => pane.id.replace(/^actions-/, "")); etagen.forEach(etage => { const ziel = document.getElementById("actions-" + etage + "-list"); if (!ziel) return; diff --git a/js/solar/homeMQTT.js b/js/solar/homeMQTT.js index 6f6549a..333eebe 100644 --- a/js/solar/homeMQTT.js +++ b/js/solar/homeMQTT.js @@ -134,20 +134,20 @@ function removeClass(el, classNameToRemove){ el.className = elClass; } +// Die Etagen stehen nicht mehr im Code: was es gibt, sagen die Reiter, die +// home.php aus der Raumtabelle gebaut hat. Vorher standen OG, EG und UG hier +// sechsmal, und eine vierte Etage haette sechs neue Zeilen gebraucht. 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"); + const newContent = document.getElementById(newtab); + const newTabBtn = document.getElementById(newtab+"-tab"); + document.querySelectorAll("#actions-tab .nav-link").forEach(function(btn){ + removeClass(btn,"active"); + }); + document.querySelectorAll("#actions-tabContent .tab-pane").forEach(function(pane){ + removeClass(pane,"active show"); + }); addClass(newTabBtn,"active"); addClass(newContent,"active show"); - - } diff --git a/restricted/automations.php b/restricted/automations.php index b510224..e848965 100644 --- a/restricted/automations.php +++ b/restricted/automations.php @@ -17,10 +17,6 @@ require_once(__DIR__ . "/mysql.php"); require_once(__DIR__ . "/rooms.php"); -// Geraete, die zu keinem Raum der Wohnung gehoeren - Carport, Garage, -// Regensensor. Kein Raum in rooms.php, aber auch nicht "unzugeordnet". -const RAUM_AUSSEN = "aussen"; - /** Verbindung zur Geraete- und Automatik-Datenbank. */ function meshDb() { @@ -220,12 +216,17 @@ function anzeigeName($name) } /** - * Waehlbare Raeume: die Raeume der Home-Ansicht plus "Aussen". + * 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. * + * Frueher stand hier ein Pseudo-Raum "aussen" fuer alles, was zu keinem Raum + * der Wohnung gehoert - Carport, Pergola, Garage, die Wechselrichter. Das war + * ein einziger Topf mit sechzehn Geraeten. Inzwischen ist das Aussengelaende + * die Etage AG mit eigenen Raeumen, und dieser Sonderfall ist keiner mehr. + * * Etage und Raum stehen zusaetzlich einzeln daneben: die Geraeteauswahl im * Editor gruppiert nach der einen und sortiert nach dem anderen, und der * Schluessel soll dafuer nicht erst im JavaScript wieder auseinandergenommen @@ -243,11 +244,6 @@ function raumListe() "room" => $raum["mqtt"], ]; } - // Aussen zaehlt als eigene Etage mit genau einem Raum: Carport, Garage und - // Regensensor liegen auf keinem Stockwerk, sollen aber ihre eigene - // Ueberschrift bekommen und nicht unter "Ohne Raum" fallen. - $liste[] = ["key" => RAUM_AUSSEN, "label" => "Außen", - "floor" => "Außen", "room" => ""]; return $liste; } @@ -521,7 +517,7 @@ function emptyAutomation($floor) "name" => "", // Aufgerufen wird der Editor immer aus einem Etagenreiter. Fehlt die // Angabe trotzdem, gilt dieselbe Etage wie auf der Startseite. - "floor" => in_array($floor, ["UG", "EG", "OG"], true) ? $floor : "OG", + "floor" => in_array($floor, $GLOBALS["floors"], true) ? $floor : "OG", "enabled" => true, "window_from" => "00:00", "window_to" => "23:59", @@ -624,7 +620,7 @@ function saveAutomation($data) throw new InvalidArgumentException("Bitte einen Namen vergeben."); } $floor = strval($data["floor"] ?? ""); - if (!in_array($floor, ["UG", "EG", "OG"], true)) { + if (!in_array($floor, $GLOBALS["floors"], true)) { // Nicht stillschweigend auf "" zurueckfallen: die Automatik wuerde // dann in keinem der drei Reiter mehr auftauchen. throw new InvalidArgumentException("Bitte eine Etage wählen."); diff --git a/restricted/header.php b/restricted/header.php index fe3cc64..d9f821d 100644 --- a/restricted/header.php +++ b/restricted/header.php @@ -3,6 +3,10 @@

+ -
Wird geladen...
+
Wird geladen...
- -
" id="actions-EG" role="tabpanel" aria-labelledby="actions-EG-tab"> - -
Wird geladen...
- -
-
" id="actions-UG" role="tabpanel" aria-labelledby="actions-UG-tab"> - -
Wird geladen...
- + onclick="openAutoActionModal('?action=editor&floor=')">
+ @@ -224,7 +212,7 @@ // die der Browser dafuer beim Broker anmelden muss. const homeRooms = ; const homeTopics = ; - + document.addEventListener('readystatechange', function () { if (event.target.readyState === 'complete') { switchFloor(''); diff --git a/restricted/rooms.php b/restricted/rooms.php index 1fc3bc1..c6a890f 100644 --- a/restricted/rooms.php +++ b/restricted/rooms.php @@ -32,7 +32,23 @@ */ // Reihenfolge im Menü und in der Anzeige; OG ist die Standardetage. -$floors = ["OG", "EG", "UG"]; +// +// AG ist das Außengelände - eine Etage ohne Grundriss. Was draußen hängt, +// stand vorher als Pseudo-Raum "aussen" in automations.php und war damit ein +// einziger Topf: Carport, Pergola, Garage und die Wechselrichter alle +// nebeneinander. Als Etage bekommt jeder davon einen Raum, und die +// Geräteauswahl im Automatik-Editor gruppiert danach wie überall sonst. +$floors = ["OG", "EG", "UG", "AG"]; + +// Ausgeschriebene Namen für Beschriftungen, wo Platz ist. Das Kürzel steht in +// URLs (?floor=AG), in SVG-Element-Ids (AG_Info) und im MQTT-Baum - dort will +// man weder Umlaute noch Länge. +$floorLabels = [ + "OG" => "Obergeschoss", + "EG" => "Erdgeschoss", + "UG" => "Untergeschoss", + "AG" => "Außengelände", +]; $rooms = [ ["floor" => "OG", "mqtt" => "Wohnzimmer", "id" => "wozi", "x" => 180, "y" => 140], @@ -69,6 +85,21 @@ $rooms = [ "einheit" => "W", "stellen" => 0, "gross" => true], ["topic" => "wasser/main/value", "einheit" => "m³", "stellen" => 1], ]], + // Außengelände: noch ohne x/y, es gibt ja keinen Lageplan, auf den eine + // Kachel zeigen könnte. Zuordnen und Automatiken anlegen geht trotzdem - + // dafür zählt allein, dass der Raum hier steht. Kommt der Lageplan, sind + // es zwei Zahlen je Zeile, gemessen mit tools/kachelpositionen.php. + // + // Die Namen stammen von den Geräten selbst: die Hoymiles heißen seit jeher + // "Carport 1-6" und "Veranda OG2-4", das Sonnensegel sitzt an der Pergola. + ["floor" => "AG", "mqtt" => "Carport", "id" => "carport", "werte" => []], + ["floor" => "AG", "mqtt" => "Veranda", "id" => "veranda", "werte" => []], + ["floor" => "AG", "mqtt" => "Pergola", "id" => "pergola", "werte" => []], + ["floor" => "AG", "mqtt" => "Terrasse", "id" => "terrasse", "werte" => []], + ["floor" => "AG", "mqtt" => "Garage", "id" => "garage", "werte" => []], + // Der Garten ist kein Bauteil wie die anderen, aber ein Ort: hier haengen + // der Regensensor und die beiden Ventilsteuerungen. + ["floor" => "AG", "mqtt" => "Garten", "id" => "garten", "werte" => []], ]; /** @@ -91,6 +122,30 @@ function allRooms() return $GLOBALS["rooms"]; } +/** Ausgeschriebener Name einer Etage; unbekannte bleiben ihr Kürzel. */ +function floorLabel($floor) +{ + return $GLOBALS["floorLabels"][$floor] ?? $floor; +} + +/** + * Etagen mit Grundriss - die, die im Menü und im SVG auftauchen. + * + * Das Außengelände ist eine Etage ohne Bild: seine Räume haben keine Kachel, + * und ein Menüpunkt dorthin führte auf eine leere Zeichenfläche. Sobald der + * erste AG-Raum ein x/y bekommt, erscheint die Etage von selbst auch dort - + * es gibt keine zweite Liste, die man nachziehen müsste. + * + * Für die Zuordnung und die Automatiken zählt dagegen $floors: dort ist das + * Außengelände von Anfang an dabei. + */ +function floorsWithPlan() +{ + return array_values(array_filter($GLOBALS["floors"], function ($floor) { + return count(roomsOnFloor($floor)) > 0; + })); +} + /** Nur die Räume mit Kachel, für die Aktualisierungsschleife im Browser. */ function roomsWithTile() {