["Beschattung", "bi-blinds"], "licht" => ["Licht", "bi-lightbulb"], "schalter" => ["Schalten", "bi-toggle-on"], "sonstiges" => ["Geräte", "bi-sliders"], "messwerte" => ["Messwerte", "bi-speedometer2"], ]; } /** * Die Geraetegruppen als Tabs. * * Vorher standen sie untereinander, und man scrollte an drei Rollladen * vorbei, um zum Licht zu kommen. Bei nur einer Gruppe bleibt die Leiste * weg - ein einzelner Tab beschriftet nur sich selbst. */ function zeichneGruppen($gruppen) { $vorhanden = []; foreach (raumGruppenTitel() as $form => $titel) { if (!empty($gruppen[$form])) { $vorhanden[$form] = $titel; } } if (!$vorhanden) { return ""; } $html = ""; if (count($vorhanden) > 1) { $html .= ""; } $html .= "
"; $erster = true; foreach ($vorhanden as $form => $titel) { $html .= "
"; foreach ($gruppen[$form] as $d) { $html .= zeichneGeraet($d, $form); } $html .= "
"; $erster = false; } return $html . "
"; } // =========================================================================== // Bausteine // =========================================================================== function karteAuf($d, $status = "") { $html = "
" . "
" . "
" . "" . htmlspecialchars($d["label"]) . ""; if ($status !== "") { $html .= "" . $status . ""; } return $html . "
"; } function karteZu() { return "
"; } /** * Ein Schieberegler, der beim Loslassen schaltet. * * Waehrend des Ziehens zu schicken hiesse, bei jedem Pixel ein Kommando an * die Box zu geben - eine Jalousie kaeme aus dem Anfahren nicht mehr heraus. */ function reglerZeile($titel, $cmd, $param, $wert, $einheit = "%") { $min = reglerMin($param); $max = reglerMax($param); $wert = reglerWert($wert, $min, $max); return "
" . "
" . "" . htmlspecialchars($titel) . "" . "" . $wert . $einheit . "
" . reglerFeld($min, $max, $wert, $einheit) . "
"; } function reglerMin($param) { return $param["min"] === null ? 0 : intval($param["min"]); } function reglerMax($param) { return $param["max"] === null ? 100 : intval($param["max"]); } function reglerWert($wert, $min, $max) { $wert = $wert === null || $wert === "" ? $min : intval($wert); return max($min, min($max, $wert)); } function reglerFeld($min, $max, $wert, $einheit, $beschriftung = "") { return ""; } /** * Ein senkrechter Regler mit Wort an jedem Ende. * * Beide Werte einer Jalousie bewegen etwas, das oben und unten hat; ein * waagerechter Schieber dafuer muss erst uebersetzt werden. Senkrecht steht * der Griff dort, wo der Rollladen steht. * * Gedreht wird mit transform, nicht mit writing-mode. writing-mode ist zwar * der neuere Weg, aber am Handy laesst sich ein so gebauter Regler nur * schlecht anfassen - das Ziehen wird als Wischen gelesen und blaettert die * Seite, statt den Griff mitzunehmen. Ein gedrehter gewoehnlicher Regler * bewegt sich in seinen eigenen Koordinaten waagerecht, wird von jedem * Browser unterstuetzt und traegt dieselbe Gestaltung wie die liegenden. */ function senkrechterRegler($cmd, $param, $wert, $titel, $oben, $unten) { $min = reglerMin($param); $max = reglerMax($param); $wert = reglerWert($wert, $min, $max); return "
" . "" . htmlspecialchars($titel) . "" . "" . htmlspecialchars($oben) . "" . "
" . reglerFeld($min, $max, $wert, "%", $titel) . "
" . "" . htmlspecialchars($unten) . "" . "" . $wert . "%" . "
"; } /** * Die sechs Lamellen neben dem Neigungsregler. * * Bei 30 % oder 70 % sagt die Zahl nicht, ob man noch hinaussieht. Die * Streifen zeigen es: schmal mit Luft dazwischen heisst offen, geschlossen * liegen sie aneinander. Gezeichnet wird die Hoehe, die eine gekippte * Lamelle von vorn zeigt - genau das, was das Licht abhaelt. Die Hoehe setzt * lamellenZeichnen() in js/solar/homeMQTT.js. */ function lamellenBild() { $html = ""; } /** Kippschalter mit dem Stand, den das Geraet zuletzt gemeldet hat. */ function kippSchalter($d, $schalter, $an) { $id = "sw" . intval($d["id"]); $html = "
" . "" . "
"; return $html; } /** Ein "wahr"-artiger Wert - die Geraete melden mal True, mal 1, mal "on". */ function istAn($wert) { if ($wert === null) { return false; } return in_array(strtolower(trim(strval($wert))), ["1", "true", "on", "an", "ein", "yes"], true); } /** * Nebensaechliche Kommandos in ein Mehr-Menue, damit sie nicht stoeren. * * Nur Kommandos ohne Parameter: ein Menuepunkt bietet nichts zum Einstellen, * ein "Position+Neigung" von hier aus wuerde ohne Werte losgeschickt. Was * Parameter braucht, hat oben seinen Schieberegler. */ function mehrMenue($cmds) { $cmds = array_values(array_filter($cmds, function ($c) { return !count($c["params"]); })); if (!$cmds) { return ""; } $html = ""; } // =========================================================================== // Die Bedienformen // =========================================================================== /** * Der zuletzt gemeldete Stand in Worten. * * "0 % zu" muss man erst uebersetzen, "offen" nicht. Die Prozentzahl steht * ohnehin am Schieberegler darunter. */ function beschattungsStand($position, $neigung) { if ($position === null || $position === "") { return ""; } $p = intval($position); $text = $p === 0 ? "offen" : ($p === 100 ? "geschlossen" : $p . " % zu"); if ($neigung !== null && $neigung !== "" && $p > 0) { $text .= ", Neigung " . intval($neigung) . " %"; } return htmlspecialchars($text); } /** * Rollladen, Jalousie, Markise, Garagentor. * * Auf/Stop/Zu gross zum Antippen, darunter die Feinheiten. Die Schieber * starten auf dem zuletzt gemeldeten Stand - so sieht man, wo das Geraet * gerade steht, bevor man es bewegt. */ function zeichneBeschattung($d) { $auf = kommandoMitUrl($d, ["up"]); $stop = kommandoMitUrl($d, ["stop"]); $zu = kommandoMitUrl($d, ["down"]); $position = kommandoMitParameter($d, "Position"); $neigung = kommandoMitParameter($d, "Neigung"); // Was das Geraet zuletzt gemeldet hat. Bei Somfy heisst die Lamelle // "Lamellenausrichtung", die Hoehe schlicht "Position". $standPos = zustandsWert($d, ["Position"]); $standNeigung = zustandsWert($d, ["Lamellenausrichtung"]); $status = beschattungsStand($standPos, $standNeigung); $benutzt = array_filter([$auf, $stop, $zu, $position, $neigung]); $benutzteIds = array_map(function ($c) { return $c["id"]; }, $benutzt); $rest = array_values(array_filter($d["commands"], function ($c) use ($benutzteIds) { return !in_array($c["id"], $benutzteIds, true); })); $html = karteAuf($d, $status); $html .= "
"; // Auf, Stop, Zu untereinander: so zeigen die Knoepfe in die Richtung, in // die sie fahren. if ($auf || $stop || $zu) { $html .= "
"; foreach ([[$auf, "bi-chevron-up", "Auf"], [$stop, "bi-stop-fill", "Stop"], [$zu, "bi-chevron-down", "Zu"]] as $k) { if (!$k[0]) { continue; } $html .= ""; } $html .= "
"; } if ($position) { $html .= senkrechterRegler($position, $position["params"][0], $standPos, "Position", "oben", "unten"); } if ($neigung) { // Regler und Lamellenbild bilden ein Paar: das Bild sagt, was der // Regler daneben bedeutet, und laeuft beim Ziehen mit. $html .= "
" . senkrechterRegler($neigung, $neigung["params"][0], $standNeigung, "Neigung", "auf", "zu") . lamellenBild() . "
"; } $html .= mehrMenue($rest); return $html . "
" . karteZu(); } /** * Lampe: Kippschalter, Helligkeit, Farbe, Effekt. * * Die Farbe kommt in der Datenbank als drei Parameter 0-255 an. Drei * Zahlenfelder sind keine Farbwahl - deshalb ein Farbfeld, das der Browser * aufmacht, und die Zerlegung passiert erst beim Absenden. */ function zeichneLicht($d) { $schalter = schaltKommandos($d); $an = istAn(zustandsWert($d, ["An", "ison", "on", "state"])); $helligkeit = kommandoMitParameter($d, "brightness"); $farbe = farbKommando($d); $benutzteIds = []; foreach ($schalter as $c) { $benutzteIds[] = $c["id"]; } foreach ([$helligkeit, $farbe] as $c) { if ($c) { $benutzteIds[] = $c["id"]; } } $html = karteAuf($d, ""); $html .= "
"; $html .= $schalter ? kippSchalter($d, $schalter, $an) : ""; if ($farbe) { // Der zuletzt gemeldete Farbwert steht als [r, g, b, w] in der // Datenbank; alles andere faellt auf Weiss zurueck. $roh = json_decode(strval(zustandsWert($d, ["Farbe", "color"])), true); // Steht dort schwarz, leuchtet die Lampe ueber ihren Weisskanal - ein // schwarzes Feld waere dann eine falsche Auskunft. $hex = is_array($roh) && count($roh) >= 3 && (intval($roh[0]) || intval($roh[1]) || intval($roh[2])) ? sprintf("#%02x%02x%02x", intval($roh[0]), intval($roh[1]), intval($roh[2])) : "#ffffff"; $html .= "Farbe" . ""; } $html .= "
"; if ($helligkeit) { $html .= reglerZeile("Helligkeit", $helligkeit, $helligkeit["params"][0], zustandsWert($d, ["Helligkeit", "brightness", "bri"]), ""); } // Effekt und Preset sind Listen - die gehoeren in eine Auswahl, nicht in // ein Zahlenfeld. Sie schalten sofort, ohne zusaetzlichen Knopf. foreach ($d["commands"] as $c) { if (in_array($c["id"], $benutzteIds, true) || count($c["params"]) !== 1 || !count($c["params"][0]["options"])) { continue; } $benutzteIds[] = $c["id"]; $p = $c["params"][0]; $html .= "
" . "" . htmlspecialchars($c["name"]) . "" . "
"; } $rest = array_values(array_filter($d["commands"], function ($c) use ($benutzteIds) { return !in_array($c["id"], $benutzteIds, true); })); if ($rest) { $html .= "
" . mehrMenue($rest) . "
"; } return $html . karteZu(); } /** Alles, was nur an und aus kennt. */ function zeichneSchalter($d) { $schalter = schaltKommandos($d); $an = istAn(zustandsWert($d, ["An", "ison", "on", "state", "Status"])); $benutzteIds = array_map(function ($c) { return $c["id"]; }, array_values($schalter)); $rest = array_values(array_filter($d["commands"], function ($c) use ($benutzteIds) { return !in_array($c["id"], $benutzteIds, true); })); $html = karteAuf($d, ""); $html .= "
" . kippSchalter($d, $schalter, $an) . mehrMenue($rest) . "
"; return $html . karteZu(); } /** * Geraete, die nur melden: ein Wertegitter statt einer Textwurst. * * Ein Stromzaehler hat sechzehn Werte. Alle sechzehn hinzuschreiben ist * genauso unbrauchbar wie acht davon abzuschneiden - die ersten sechs * stehen da, der Rest ist einen Klick entfernt. */ function zeichneMesswerte($d) { $html = karteAuf($d, htmlspecialchars($d["type"])); $states = nachWichtigkeit($d["states"]); $sichtbar = array_slice($states, 0, 6); $weitere = array_slice($states, 6); $html .= "
"; foreach ($sichtbar as $s) { $html .= wertKachel($s); } $html .= "
"; if ($weitere) { $html .= "
" . count($weitere) . " weitere Werte" . "
"; foreach ($weitere as $s) { $html .= wertKachel($s); } $html .= "
"; } return $html . karteZu(); } /** * Messwerte so ordnen, dass die sechs sichtbaren die richtigen sind. * * Die Reihenfolge in der Datenbank ist die des Geraetesuchlaufs: beim * Wasserzaehler standen MAC-Adresse, Firmware und freier Speicher vorn, der * Verbrauch hinter "12 weitere Werte". Vorne gehoert hin, was gemessen wird - * ein Wert mit Einheit -, hinten, was ueber das Geraet selbst Auskunft gibt. */ function nachWichtigkeit($states) { $diagnose = ["mac", "hostname", "fwversion", "uptime", "freemem", "rssi", "error", "status", "ip", "ssid", "version"]; $rang = function ($s) use ($diagnose) { $leer = $s["value"] === null || $s["value"] === ""; $technisch = in_array(strtolower($s["name"]), $diagnose, true); return ($technisch ? 4 : 0) + ($leer ? 2 : 0) + ($s["unit"] ? 0 : 1); }; usort($states, function ($a, $b) use ($rang) { return $rang($a) - $rang($b); }); return $states; } function wertKachel($s) { $wert = strval($s["value"]); if ($wert === "") { $wert = "–"; } // Messwerte kommen mit voller Rechengenauigkeit aus dem Geraet; auf einer // Kachel liest niemand 231.10000000000002. if (is_numeric($wert) && strpos($wert, ".") !== false) { $wert = rtrim(rtrim(number_format(floatval($wert), 2, ",", ""), "0"), ","); } return "
" . "
" . htmlspecialchars($wert) . ($s["unit"] ? " " . htmlspecialchars($s["unit"]) . "" : "") . "
" . "
" . htmlspecialchars($s["name"]) . "
"; } /** Was in keine Form passt: Kommandos so, wie sie in der Datenbank stehen. */ function zeichneSonstiges($d) { $html = karteAuf($d, htmlspecialchars($d["type"])); $knoepfe = array_values(array_filter($d["commands"], function ($c) { return !count($c["params"]); })); if ($knoepfe) { $html .= "
"; foreach ($knoepfe as $c) { $html .= ""; } $html .= "
"; } foreach ($d["commands"] as $c) { if (!count($c["params"])) { continue; } $html .= "
" . "" . htmlspecialchars($c["name"]) . ""; foreach ($c["params"] as $p) { if (count($c["params"]) > 1) { $html .= "" . htmlspecialchars($p["name"]) . ""; } if ($p["options"]) { $html .= ""; } else { $html .= ""; } } $html .= "
"; } return $html . karteZu(); } /** Ein Geraet in seiner Bedienform zeichnen. */ function zeichneGeraet($d, $form) { switch ($form) { case "beschattung": return zeichneBeschattung($d); case "licht": return zeichneLicht($d); case "schalter": return zeichneSchalter($d); case "messwerte": return zeichneMesswerte($d); default: return zeichneSonstiges($d); } }