["Beschattung", "bi-beschattung", "#60afbf"], "heizung" => ["Heizung", "bi-thermometer-half", "#e5624f"], "licht" => ["Licht", "bi-lightbulb", "#f7c000"], "schalter" => ["Schalten", "bi-toggle-on", "#6cbe58"], "sonstiges" => ["Geräte", "bi-sliders", "#9aa6ad"], "messwerte" => ["Messwerte", "bi-speedometer2", "#4b9fd6"], ]; } /** * Die Geraetegruppen als Reiter-Kacheln. * * Jede Kachel traegt unter ihrem Namen den Zustand der Gruppe ("2 von 3 zu", * "25,7 °C", "1 an") - den setzt js/solar/raumfenster.js, weil er sich * waehrend des Bedienens aendert. Bei nur einer Gruppe bleibt die Leiste * trotzdem stehen: sie ist hier auch die Zusammenfassung. * * $fertig sind Gruppen, deren Inhalt der Aufrufer schon gebaut hat und die * nicht aus einer Geraeteliste entstehen - die Heizung ist so eine: ein * Regler fuer den Raum, nicht eine Karte je Geraet. */ function zeichneGruppen($gruppen, $fertig = []) { $vorhanden = []; foreach (raumGruppenTitel() as $form => $titel) { if (!empty($gruppen[$form]) || isset($fertig[$form])) { $vorhanden[$form] = $titel; } } if (!$vorhanden) { return ""; } $leiste = "
"; $flaechen = ""; $erste = true; foreach ($vorhanden as $form => $titel) { $leiste .= ""; $inhalt = ""; if (isset($fertig[$form])) { $inhalt = $fertig[$form]; } else { if ($form === "beschattung" && count($gruppen[$form]) > 1) { $inhalt .= alleImRaum($gruppen[$form]); } if ($form === "schalter") { $inhalt .= "
"; } foreach ($gruppen[$form] as $d) { $inhalt .= zeichneGeraet($d, $form); } if ($form === "schalter") { $inhalt .= "
"; } } $flaechen .= "
" . $inhalt . "
"; $erste = false; } return $leiste . "
" . $flaechen; } // =========================================================================== // Bausteine // =========================================================================== /** Rahmen fuer Geraete ohne eigene Bedienform (Messwerte, Sonstiges). */ function karteAuf($d, $status = "") { $html = "
" . "
" . htmlspecialchars($d["label"]) . ""; if ($status !== "") { $html .= "" . $status . ""; } return $html . "
"; } function karteZu() { return "
"; } /** 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 seine Bedienung. */ function mehrMenue($cmds) { $cmds = array_values(array_filter($cmds, function ($c) { return !count($c["params"]); })); if (!$cmds) { return ""; } $html = ""; } /** Kommandos, die nicht unter $benutzt stehen. */ function restKommandos($d, $benutzt) { $ids = array_map(function ($c) { return $c["id"]; }, array_filter($benutzt)); return array_values(array_filter($d["commands"], function ($c) use ($ids) { return !in_array($c["id"], $ids, true); })); } /** Stufenwahl als Segment: $stufen = [[wert, beschriftung], ...]. */ function stufenReihe($titel, $rolle, $stufen) { $html = "
" . htmlspecialchars($titel) . "" . "
"; foreach ($stufen as $s) { $html .= ""; } return $html . "
"; } // =========================================================================== // Die Bedienformen // =========================================================================== /** Zahl aus einem Zustand, oder "" wenn es keinen gibt. */ function zustandsZahl($d, $namen) { $wert = zustandsWert($d, $namen); return is_numeric($wert) ? strval(intval($wert)) : ""; } /** * "Alle im Raum": ein Knopf, viele Geraete. Das JavaScript geht die Karten * durch und schickt jedem das passende eigene Kommando - "Sichtschutz" nur * denen, die Hoehe und Neigung zusammen setzen koennen. */ function alleImRaum($geraete) { $mitMy = false; $mitNeigung = false; foreach ($geraete as $d) { $mitMy = $mitMy || kommandoMitUrl($d, ["my"]); $mitNeigung = $mitNeigung || kombiKommando($d); } return "

Alle im Raum

" . "" . "" . ($mitMy ? "" : "") . ($mitNeigung ? "" : "") . "
"; } /** * Rollladen, Jalousie, Markise, Garagentor. * * Links das Fenster, wie der Behang davor gerade steht - ziehen stellt die * Hoehe fein ein. Rechts gross Auf / Stop / my / Zu, darunter Hoehe und * Lamellen als Stufen und die my-Position, die sich dort auch speichern * laesst. Alles wird von js/solar/raumfenster.js ueber die data-Attribute * verdrahtet; welche es gibt, haengt an den Faehigkeiten des Geraets. */ function zeichneBeschattung($d) { $auf = kommandoMitUrl($d, ["up"]); $stop = kommandoMitUrl($d, ["stop"]); $zu = kommandoMitUrl($d, ["down"]); $my = kommandoMitUrl($d, ["my"]); $position = kommandoMitParameter($d, POSITION_PARAMETER); $neigung = kommandoMitParameter($d, NEIGUNG_PARAMETER); $kombi = kombiKommando($d); // Die Hoehe geht, wenn es das gibt, ueber das Kombi-Kommando und nimmt die // Neigung mit - sonst raste die Mechanik bei der Hoehenfahrt um und // niemand faehrt sie wieder an (siehe sendeTahoma() in commands.php). $hoehe = $kombi ?: $position; $attr = [ "auf" => $auf ? $auf["id"] : "", "stop" => $stop ? $stop["id"] : "", "zu" => $zu ? $zu["id"] : "", "my" => $my ? $my["id"] : "", "hoehe-cmd" => $hoehe ? $hoehe["id"] : "", "hoehe-param" => $hoehe ? ($kombi ? parameterNamens($kombi, POSITION_PARAMETER)["id"] : $position["params"][0]["id"]) : "", "hoehe-mit" => $kombi && $neigung ? parameterNamens($kombi, NEIGUNG_PARAMETER)["id"] : "", "neig-cmd" => $neigung ? $neigung["id"] : "", "neig-param" => $neigung ? $neigung["params"][0]["id"] : "", // Was das Geraet zuletzt gemeldet hat. Bei Somfy heisst die Lamelle // "Lamellenausrichtung", die Hoehe schlicht "Position". "pos" => zustandsZahl($d, ["Position"]), "neig" => $neigung ? zustandsZahl($d, ["Lamellenausrichtung"]) : "", "my-pos" => zustandsZahl($d, ["Gespeicherte Position"]), "my-neig" => $neigung ? zustandsZahl($d, ["Gespeicherte Neigung"]) : "", ]; $html = "
$wert) { $html .= " data-" . $name . "='" . htmlspecialchars(strval($wert), ENT_QUOTES) . "'"; } $html .= ">"; // Das Bild: dunkles Glas, davor Lamellen (mit Neigung) oder ein Panzer. // Mehr Lamellen als je sichtbar - die Hoehe schneidet ab. $html .= ""; $html .= "
" . "
" . htmlspecialchars($d["label"]) . "" . "" . mehrMenue(restKommandos($d, [$auf, $stop, $zu, $my, $position, $neigung, $kombi])) . "
"; if ($auf || $stop || $zu || $my) { $html .= "
" . ($auf ? "" : "") . ($stop ? "" : "") . ($my ? "" : "") . ($zu ? "" : "") . "
"; } $prozent = function ($v) { return $v . " %"; }; if ($hoehe) { $html .= stufenReihe("Höhe", "rf-pos", [[0, "offen"], [25, $prozent(25)], [50, "halb"], [75, $prozent(75)], [100, "zu"]]); } if ($neigung) { $html .= stufenReihe("Lamellen", "rf-neig", [[0, "offen"], [25, $prozent(25)], [50, "halb"], [75, $prozent(75)], [100, "zu"]]); } // Die my-Position ist im Motor gespeichert und nur bei IO-Geraeten lesbar. // Speichern geht ueber ajax/room.php?action=my, nicht ueber ein Kommando // aus der Geraetedatenbank. if ($attr["my-pos"] !== "" && $my) { $html .= "
"; } return $html . "
"; } /** * Lampe: die Birne ist der Schalter und leuchtet in ihrer Farbe. Darunter * Helligkeit, Farbfelder und - eingeklappt - Effekt und Preset. */ function zeichneLicht($d) { $schalter = schaltKommandos($d); $an = istAn(zustandsWert($d, ["An", "ison", "on", "state"])); $helligkeit = kommandoMitParameter($d, "brightness"); $farbe = farbKommando($d); // Der zuletzt gemeldete Farbwert steht als [r, g, b, w] in der Datenbank. // Steht dort schwarz, leuchtet die Lampe ueber ihren Weisskanal - ein // schwarzes Feld waere dann eine falsche Auskunft. $roh = json_decode(strval(zustandsWert($d, ["Farbe", "color"])), true); $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])) : "#fff1d6"; $benutzt = array_values($schalter); $benutzt[] = $helligkeit; $benutzt[] = $farbe; $html = "
"; $html .= "
" . "" . "
" . htmlspecialchars($d["label"]) . "
"; // Effekt und Preset sammeln, bevor das Mehr-Menue die Reste bekommt. $listen = []; foreach ($d["commands"] as $c) { if (in_array($c, $benutzt, true) || count($c["params"]) !== 1 || !count($c["params"][0]["options"])) { continue; } $listen[] = $c; $benutzt[] = $c; } $html .= mehrMenue(restKommandos($d, $benutzt)) . "
"; if ($helligkeit) { $p = $helligkeit["params"][0]; $min = $p["min"] === null ? 0 : intval($p["min"]); $max = $p["max"] === null ? 100 : intval($p["max"]); $wert = zustandsWert($d, ["Helligkeit", "brightness", "bri"]); $wert = max($min, min($max, $wert === null || $wert === "" ? $max : intval($wert))); $html .= ""; } if ($farbe) { $html .= "
"Warmweiß", "#fff1d6" => "Weiß", "#dfeeff" => "Kaltweiß", "#ff6b5b" => "Rot", "#ffb347" => "Orange", "#7ddc6f" => "Grün", "#5bb8ff" => "Blau", "#b48cff" => "Lila"] as $f => $name) { $html .= ""; } $html .= "
"; } // Presets, Effekte und andere Listen. Wenige Eintraege stehen als Chips // da; lange Listen (WLED hat ueber 180 Effekte) bekommen einen Knopf mit // dem aktuellen Eintrag, der eine durchsuchbare Liste aufklappt. Die // Welcher Eintrag gerade gilt, liest // js/solar/raumfenster.js bei WLED live aus (room.php?action=wled); // data-feld ist der Parametername, "effect" oder "preset". foreach ($listen as $c) { $p = $c["params"][0]; $kopf = "
" . "" . htmlspecialchars($c["name"]) . ""; $knoepfe = ""; foreach ($p["options"] as $o) { $knoepfe .= ""; } if (count($p["options"]) <= 16) { $html .= $kopf . "
" . $knoepfe . "
"; } else { // Ueber 180 WLED-Effekte waeren als Chips eine Wand. Aus dem // "; foreach ($p["options"] as $o) { $html .= ""; } $html .= "
"; } } return $html . "
"; } /** Alles, was nur an und aus kennt: eine Kachel zum Antippen. */ function zeichneSchalter($d) { $schalter = schaltKommandos($d); $an = istAn(zustandsWert($d, ["An", "ison", "on", "state", "Status"])); return ""; } /** * 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); } }