"Konnte die Anfrage nicht lesen."], 400); } try { switch ($action) { case "save": $id = saveAutomation($body); jsonAntwort(["id" => $id]); case "delete": // Der Modus kommt aus der Rueckfrage im Browser. Unbekanntes // faellt auf das schonendere "abhaengen" zurueck - lieber eine // pausierte Automatik zu viel als eine geloeschte zu viel. $modus = ($body["mode"] ?? "") === "mitloeschen" ? "mitloeschen" : "abhaengen"; deleteAutomation(intval($body["id"] ?? 0), $modus); jsonAntwort(["ok" => true]); case "toggle": jsonAntwort(["enabled" => toggleAutomation(intval($body["id"] ?? 0))]); default: jsonAntwort(["error" => "Unbekannte Aktion."], 400); } } catch (InvalidArgumentException $e) { jsonAntwort(["error" => $e->getMessage()], 400); } catch (Throwable $e) { jsonAntwort(["error" => "Speichern fehlgeschlagen: " . $e->getMessage()], 500); } } // --- Wer haengt an dieser Automatik? ------------------------------------- // Wird vor dem Loeschen gefragt, damit die Rueckfrage im Browser die Namen // nennen kann und nicht nur eine Zahl. if ($action === "followers") { jsonAntwort(["followers" => automationFollowers(intval($_GET["id"] ?? 0))]); } // --- Was sagen diese Messwerte gerade? ----------------------------------- // Der offene Editor fragt das im Takt nach, damit neben jeder Bedingung der // laufende Wert steht. Absichtlich ein eigener, winziger Endpunkt: den // ganzen Geraetekatalog neu zu holen waeren je Runde ueber hundert // Kilobyte fuer ein paar Zahlen. if ($action === "werte") { $ids = array_slice(explode(",", $_GET["ids"] ?? ""), 0, 200); jsonAntwort(["werte" => stateValues($ids)]); } // --- Zeitleiste ---------------------------------------------------------- // Alle Automatiken eines Tages mit ihrer Lage, siehe restricted/zeitleiste.php. if ($action === "zeitleiste") { require_once("../restricted/zeitleiste.php"); try { jsonAntwort(zeitleiste(strval($_GET["datum"] ?? date("Y-m-d")))); } catch (Throwable $e) { jsonAntwort(["error" => "Zeitleiste nicht abrufbar: " . $e->getMessage()], 500); } } // --- Uebersichtstabelle -------------------------------------------------- if ($action === "list") { $tage = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]; $list = listAutomations($floor); if (!$list) { echo "

Für diese Etage ist noch nichts eingerichtet.

"; } else { echo "
"; // Je schmaler der Schirm, desto weniger Spalten: auf dem Handy bleiben // Name, Ausloeser und die Knoepfe. Sonst muss man seitwaerts scrollen, // um eine Automatik ueberhaupt pausieren zu koennen. echo "" . "" . "" . ""; /* * Eine Automatik, die von einer anderen ausgeloest wird, steht unter * ihr statt alphabetisch irgendwo dazwischen - sonst liest man * "Rollladen Magdalena morgens" und sieht nicht, woran es haengt. * * Eingeklappt, weil die Uebersicht sonst mit jeder Verkettung laenger * wird, ohne mehr zu sagen: der Ausloeser ist das, was man sucht. Der * Zaehler am Pfeil verraet, dass da noch etwas ist. * * Gruppiert wird nur die Darstellung. Jeder Nachfolger bleibt eine * vollwertige Automatik mit eigener Pause, eigenen Rahmenbedingungen * und eigener Zeile im Protokoll - sonst waere zu klaeren, wem die * Pause gehoert und was das Protokoll zeigt. */ $nachFolgern = []; foreach ($list as $a) { foreach ($a["triggers"] as $davor) { $nachFolgern[intval($davor)][] = intval($a["id"]); } } $vorhanden = []; foreach ($list as $a) { $vorhanden[intval($a["id"])] = $a; } // Wessen Ausloeser nicht in dieser Etage steht, faengt selbst oben an. $wurzeln = []; foreach ($list as $a) { $hatVater = false; foreach ($a["triggers"] as $davor) { if (isset($vorhanden[intval($davor)])) { $hatVater = true; } } if (!$hatVater) { $wurzeln[] = intval($a["id"]); } } $reihe = []; $einordnen = function ($id, $tiefe, $vater) use (&$einordnen, &$reihe, $nachFolgern, $vorhanden) { if (!isset($vorhanden[$id]) || isset($reihe[$id])) { return; } $reihe[$id] = ["auto" => $vorhanden[$id], "tiefe" => $tiefe, "vater" => $vater, "kinder" => 0]; foreach ($nachFolgern[$id] ?? [] as $kind) { $einordnen($kind, $tiefe + 1, $id); } }; foreach ($wurzeln as $id) { $einordnen($id, 0, 0); } // Was durch einen Kreis nicht erreicht wurde, kommt trotzdem mit. foreach ($list as $a) { $einordnen(intval($a["id"]), 0, 0); } // Gezaehlt wird erst jetzt, und nur was wirklich eingeordnet wurde: // wer zwei Ausloeser hat, steht nur unter dem ersten. Aus der rohen // Nachfolgerliste gezaehlt verspraeche der zweite Pfeil eine Zeile, // die er nicht aufklappen kann. foreach ($reihe as $eintrag) { if ($eintrag["vater"]) { $reihe[$eintrag["vater"]]["kinder"]++; } } foreach ($reihe as $eintrag) { $a = $eintrag["auto"]; $tiefe = $eintrag["tiefe"]; $kinder = $eintrag["kinder"]; $vater = $eintrag["vater"]; // Nur die aktiven Tage nennen. Sieben Haekchen untereinander // haben die Zeile frueher unnoetig hoch gemacht. $aktiveTage = []; for ($i = 0; $i < 7; $i++) { if ($a["weekdays"] & (1 << $i)) { $aktiveTage[] = $tage[$i]; } } // Kalendertage auf "immer" gehoeren zur Tagesangabe, nicht in // die Nachbemerkung: sie fuegen Tage hinzu. Ohne angehakten // Wochentag tragen sie die Automatik sogar allein - "nur // Feiertage" ist damit eine gueltige Einstellung. $dazu = []; if ($a["on_vacation"] == 2) { $dazu[] = "Ferientage"; } if ($a["on_holiday"] == 2) { $dazu[] = "Feiertage"; } if (count($aktiveTage) === 7) { $wann = "täglich"; } elseif ($aktiveTage) { $wann = implode(" ", $aktiveTage); if ($dazu) { $wann .= " + " . implode(" + ", $dazu); } } elseif ($dazu) { $wann = "nur " . implode(" + ", $dazu); } else { $wann = "nie – kein Tag ausgewählt"; } // Vorabend: alle Tagesangaben meinen morgen. Vorne, damit auch // "nicht an Feiertagen" weiter hinten als Folgetag gelesen wird. if ($a["next_day"]) { $wann = "Vorabend von " . $wann; } if ($a["window_from"] !== "00:00" || $a["window_to"] !== "23:59") { $wann .= ", " . $a["window_from"] . "–" . $a["window_to"]; } if ($a["on_holiday"] == 0) { $wann .= ", nicht an Feiertagen"; } if ($a["on_vacation"] == 0) { $wann .= ", nicht in den Ferien"; } if ($a["once_per_day"]) { $wann .= ", nur einmal am Tag"; } // Vor der Sperre, weil es auch zeitlich davor liegt: erst muss // die Bedingung stehen bleiben, dann wird ausgeloest. if (!empty($a["hold_secs"])) { $wann .= ", erst nach " . haltezeitKurz($a["hold_secs"]) . " ununterbrochen"; } if ($a["lockout_secs"] > 0) { $wann .= ", frühestens alle " . ($a["lockout_secs"] >= 60 ? intdiv($a["lockout_secs"], 60) . " Min." : $a["lockout_secs"] . " Sek."); } $zuletzt = $a["last_run"] ? date("d.m. H:i", strtotime($a["last_run"])) : "–"; $pause = $a["enabled"] ? "bi-pause-fill" : "bi-play-fill"; $titel = $a["enabled"] ? "Pausieren" : "Fortsetzen"; $id = intval($a["id"]); $klassen = []; if (!$a["enabled"]) { $klassen[] = "opacity-50"; } if ($tiefe > 0) { // Eingeklappt starten: die Zeile gehoert dem Ausloeser, und // der steht direkt darueber. $klassen[] = "auto-folge"; $klassen[] = "auto-von-" . $vater; $klassen[] = "d-none"; } $klasse = $klassen ? " class='" . implode(" ", $klassen) . "'" : ""; // Der Pfeil klappt die Nachfolger auf. Er steht nur da, wo es // welche gibt - ein Pfeil ohne Inhalt waere eine Einladung ins // Leere. $griff = ""; if ($kinder > 0) { $griff = ""; } $einzug = $tiefe > 0 ? "" . str_repeat("   ", $tiefe) . " " : ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } echo "
NameAuslöserAktionWannZuletzt
" . $einzug . $griff . htmlspecialchars($a["name"]) . "" . htmlspecialchars($a["conditionText"]) . "" . htmlspecialchars($a["actionText"]) . "" . htmlspecialchars($wann) . "" . $zuletzt . "" . " " . " " . "" . "
"; } exit; } // --- Editor -------------------------------------------------------------- $id = intval($_GET["id"] ?? 0); $auto = $id > 0 ? loadAutomation($id) : null; if (!$auto) { $auto = emptyAutomation($floor); } // Geraetekatalog und Datensatz reisen im selben Dokument mit. Frueher holte // der Editor die Geraeteliste und je Zeile noch einmal deren Details nach - // zwei zusaetzliche Anfragen pro Bedingung, und die Dropdowns fuellten sich // asynchron, weshalb ein gespeicherter Wert nicht zuverlaessig ankam. $nutzdaten = json_encode([ "automation" => $auto, "devices" => deviceCatalog(), "lockouts" => lockoutChoices(), "haltezeiten" => haltezeitChoices(), "ferien" => kalendertagChoices("Ferien", "in den Ferien"), "feiertage" => kalendertagChoices("Feiertage", "an Feiertagen"), "tagvorlagen" => tagVorlagen(), // Nur fuer die Reihenfolge der Raum-Gruppen: rooms.php gibt sie vor, und // Etage fuer Etage liest sich besser als alphabetisch. "rooms" => raumListe(), ], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>
Wenn … das alles zusammenkommt
Ferien
Feiertage
von bis
Sperre
Auslösen
Dann … passiert das hier