Raum-Modal im neuen Stil: Reiter-Kacheln, Beschattung mit my, Heizring, Licht
- Beschattung: Auf/Stop/my/Zu, Hoehe und Lamellen in 25-%-Stufen, Ziehen im Fensterbild, neue Behang-Grafik ueber die ganze Kartenhoehe, Alle im Raum - my-Position: aktuellen Stand per setMemorized1Position/-Orientation in den Motor schreiben (room.php?action=my), gespeicherte Werte werden angezeigt - Heizung: Ring mit Ist-Temperatur und ziehbarem Soll-Punkt, -/+ und Schnellwahl - Licht: Birne als Schalter, Helligkeit, Farbfelder, Effekt/Preset eingeklappt - Logik aus homeMQTT.js nach js/solar/raumfenster.js; bindModalSlider entfaellt Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+241
-312
@@ -14,15 +14,10 @@
|
||||
* auch ein neu gefundenes Geraet sein passendes Bedienelement, ohne dass hier
|
||||
* jemand einen Typnamen nachtraegt.
|
||||
*
|
||||
* Das Gegenstueck im Browser ist raumBedienungAktivieren() in
|
||||
* js/solar/homeMQTT.js - dort haengen die Zuhoerer an den data-Attributen,
|
||||
* die hier gesetzt werden.
|
||||
* Das Gegenstueck im Browser ist js/solar/raumfenster.js - dort haengen die
|
||||
* Zuhoerer an den data-Attributen, die hier gesetzt werden.
|
||||
*/
|
||||
|
||||
// Die Geraetegruppen werden als Reiter gezeichnet - eine Bauart fuer die
|
||||
// ganze Seite, siehe restricted/reiter.php.
|
||||
require_once(__DIR__ . "/reiter.php");
|
||||
|
||||
/**
|
||||
* Was das Kommando schaltet: "ein", "aus", "um" - oder null, wenn es kein
|
||||
* Schaltkommando ist.
|
||||
@@ -173,7 +168,9 @@ function bedienform($d)
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Gruppen in der Reihenfolge, in der ihre Tabs erscheinen.
|
||||
* Die Gruppen in der Reihenfolge, in der ihre Reiter erscheinen, mit Symbol
|
||||
* und Akzentfarbe - dieselben Farben wie in Lade-, Heiz- und
|
||||
* Bewaesserungsfenster.
|
||||
*
|
||||
* Beschattung zuerst: das ist es, was man in einem Raum am haeufigsten
|
||||
* anfasst. Messwerte zuletzt - die schaut man an, man bedient sie nicht.
|
||||
@@ -181,21 +178,24 @@ function bedienform($d)
|
||||
function raumGruppenTitel()
|
||||
{
|
||||
return [
|
||||
"beschattung" => ["Beschattung", "bi-blinds"],
|
||||
"heizung" => ["Heizung", "bi-thermometer-half"],
|
||||
"licht" => ["Licht", "bi-lightbulb"],
|
||||
"schalter" => ["Schalten", "bi-toggle-on"],
|
||||
"sonstiges" => ["Geräte", "bi-sliders"],
|
||||
"messwerte" => ["Messwerte", "bi-speedometer2"],
|
||||
// "bi-blinds" gibt es in der eingebundenen Icon-Version nicht - die
|
||||
// Kachel blieb leer. Ein eigenes Zeichen: Fensterrahmen mit Lamellen.
|
||||
"beschattung" => ["Beschattung", "<svg viewBox='24 24' width='16' height='16' fill='none' stroke='currentColor' stroke-width='1.8' stroke-linecap='round'><rect x='4' y='3' width='16' height='18' rx='1.5'/><path d='M4 7.5h16M4 12h16M4 16.5h16'/></svg>", "#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 Tabs.
|
||||
* Die Geraetegruppen als Reiter-Kacheln.
|
||||
*
|
||||
* 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.
|
||||
* 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
|
||||
@@ -213,180 +213,58 @@ function zeichneGruppen($gruppen, $fertig = [])
|
||||
return "";
|
||||
}
|
||||
|
||||
// Aus den Gruppen werden Reiter. Wie sie aussehen und wie umgeschaltet
|
||||
// wird, steht in restricted/reiter.php - hier nur, was drinsteht.
|
||||
$reiter = [];
|
||||
$leiste = "<div class='rf-reiter' role='tablist'>";
|
||||
$flaechen = "";
|
||||
$erste = true;
|
||||
foreach ($vorhanden as $form => $titel) {
|
||||
$leiste .= "<button type='button' role='tab' data-rf-reiter='" . $form . "'"
|
||||
. " aria-selected='" . ($erste ? "true" : "false") . "' style='--c:" . $titel[2] . "'>"
|
||||
. "<span class='rf-sym'>" . (strpos($titel[1], "<svg") === 0 ? $titel[1] : "<i class='bi " . $titel[1] . "'></i>") . "</span>"
|
||||
. "<b>" . htmlspecialchars($titel[0]) . "</b>"
|
||||
. "<span class='rf-st' data-rf-status='" . $form . "'></span></button>";
|
||||
|
||||
$inhalt = "";
|
||||
if (isset($fertig[$form])) {
|
||||
$inhalt = $fertig[$form];
|
||||
} else {
|
||||
if ($form === "beschattung" && count($gruppen[$form]) > 1) {
|
||||
$inhalt .= alleImRaum($gruppen[$form]);
|
||||
}
|
||||
if ($form === "schalter") {
|
||||
$inhalt .= "<div class='rf-schalter-liste'>";
|
||||
}
|
||||
foreach ($gruppen[$form] as $d) {
|
||||
$inhalt .= zeichneGeraet($d, $form);
|
||||
}
|
||||
if ($form === "schalter") {
|
||||
$inhalt .= "</div>";
|
||||
}
|
||||
}
|
||||
$reiter[] = [
|
||||
"id" => $form,
|
||||
"titel" => $titel[0],
|
||||
"symbol" => $titel[1],
|
||||
"inhalt" => $inhalt,
|
||||
];
|
||||
$flaechen .= "<div class='rf-flaeche' role='tabpanel' data-rf-flaeche='" . $form . "'"
|
||||
. " style='--a:" . $titel[2] . "'" . ($erste ? "" : " hidden") . ">" . $inhalt . "</div>";
|
||||
$erste = false;
|
||||
}
|
||||
// "schmal", weil das Modal eng ist: kleinere Reiter, die waagerecht
|
||||
// scrollen statt umzubrechen.
|
||||
return reiterBlock($reiter, ["name" => "raumtab", "schmal" => true,
|
||||
"klasse" => "mb-2"]);
|
||||
return $leiste . "</div>" . $flaechen;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Bausteine
|
||||
// ===========================================================================
|
||||
|
||||
/** Rahmen fuer Geraete ohne eigene Bedienform (Messwerte, Sonstiges). */
|
||||
function karteAuf($d, $status = "")
|
||||
{
|
||||
$html = "<div class='card mb-2 raum-geraet' data-actor='" . intval($d["id"]) . "'>"
|
||||
. "<div class='card-body py-2'>"
|
||||
. "<div class='d-flex justify-content-between align-items-center gap-2 mb-2'>"
|
||||
. "<span class='fw-semibold small text-truncate'>" . htmlspecialchars($d["label"]) . "</span>";
|
||||
$html = "<div class='rf-karte raum-geraet' data-actor='" . intval($d["id"]) . "'>"
|
||||
. "<div class='rf-kopf-zeile'><b>" . htmlspecialchars($d["label"]) . "</b>";
|
||||
if ($status !== "") {
|
||||
$html .= "<span class='small text-body-secondary text-nowrap raum-status'>" . $status . "</span>";
|
||||
$html .= "<span class='rf-pille'>" . $status . "</span>";
|
||||
}
|
||||
return $html . "</div>";
|
||||
}
|
||||
|
||||
function karteZu()
|
||||
{
|
||||
return "</div></div>";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 "<div class='raum-regler' data-cmd='" . intval($cmd["id"]) . "'"
|
||||
. " data-param='" . intval($param["id"]) . "'>"
|
||||
. "<div class='d-flex justify-content-between small'>"
|
||||
. "<span>" . htmlspecialchars($titel) . "</span>"
|
||||
. "<span class='raum-regler-wert fw-semibold'>" . $wert . $einheit . "</span></div>"
|
||||
. reglerFeld($min, $max, $wert, $einheit)
|
||||
. "</div>";
|
||||
}
|
||||
|
||||
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 "<input type='range' class='form-range range-color-track' min='" . $min . "' max='" . $max . "'"
|
||||
. " step='1' value='" . $wert . "' data-einheit='" . htmlspecialchars($einheit, ENT_QUOTES) . "'"
|
||||
. ($beschriftung === "" ? ""
|
||||
: " aria-label='" . htmlspecialchars($beschriftung, ENT_QUOTES) . "'")
|
||||
. ">";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, $rolle,
|
||||
$mitParam = null, $mitRolle = "")
|
||||
{
|
||||
$min = reglerMin($param);
|
||||
$max = reglerMax($param);
|
||||
$wert = reglerWert($wert, $min, $max);
|
||||
// $mitParam: ein zweiter Parameter desselben Kommandos, dessen Wert der
|
||||
// Regler mit der Rolle $mitRolle beisteuert. Damit schickt der
|
||||
// Hoehenregler die Neigung mit, ohne dass das JavaScript wissen muss,
|
||||
// welche Nummern das sind.
|
||||
$mit = $mitParam === null ? ""
|
||||
: " data-mit-param='" . intval($mitParam["id"]) . "' data-mit-rolle='" . $mitRolle . "'";
|
||||
// Keine Prozentzahl darunter: was der Regler bewirkt, zeigt das
|
||||
// Lamellenbild daneben, und die gesparte Zeile macht die Bahn laenger.
|
||||
return "<div class='raum-regler raum-spalte' data-cmd='" . intval($cmd["id"]) . "'"
|
||||
. " data-param='" . intval($param["id"]) . "' data-rolle='" . $rolle . "'" . $mit . ">"
|
||||
. "<span class='raum-name'>" . htmlspecialchars($titel) . "</span>"
|
||||
. "<span class='raum-ende'>" . htmlspecialchars($oben) . "</span>"
|
||||
. "<div class='raum-senkrecht'>" . reglerFeld($min, $max, $wert, "%", $titel) . "</div>"
|
||||
. "<span class='raum-ende'>" . htmlspecialchars($unten) . "</span>"
|
||||
. "</div>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Das Fenster, wie die Jalousie davor gerade steht.
|
||||
*
|
||||
* Zwei Zahlen in einem Bild: wie weit sie heruntergefahren ist und wie die
|
||||
* Lamellen stehen. Weder "60 %" noch "30 %" sagt, ob man noch hinaussieht -
|
||||
* das Bild sagt es.
|
||||
*
|
||||
* Die Hoehe schneidet ab: gezeichnet wird nur der Teil des Fensters, den die
|
||||
* Jalousie bedeckt, mit der Endschiene an seiner Unterkante. Die Neigung
|
||||
* bestimmt, wie dick eine Lamelle von vorn erscheint - w * sin(Winkel),
|
||||
* genau das, was das Licht abhaelt. Beides setzt lamellenZeichnen() in
|
||||
* js/solar/homeMQTT.js.
|
||||
*
|
||||
* Die Kennung des Beschnitts traegt die Geraetenummer: in einem Raum haengen
|
||||
* mehrere dieser Bilder, und url(#...) findet sonst immer dasselbe.
|
||||
*/
|
||||
function lamellenBild($actorId)
|
||||
{
|
||||
$schnitt = "lamellen" . intval($actorId);
|
||||
$html = "<svg class='raum-lamellen' viewBox='0 0 100 60' preserveAspectRatio='none' aria-hidden='true'>"
|
||||
. "<defs><clipPath id='" . $schnitt . "'>"
|
||||
. "<rect class='raum-abdeckung' x='0' y='0' width='100' height='60'></rect>"
|
||||
. "</clipPath></defs>"
|
||||
. "<g clip-path='url(#" . $schnitt . ")'>";
|
||||
foreach ([5, 15, 25, 35, 45, 55] as $mitte) {
|
||||
$html .= "<rect class='raum-lamelle' x='0' width='100' data-mitte='" . $mitte . "'"
|
||||
. " y='" . ($mitte - 1) . "' height='2'></rect>";
|
||||
}
|
||||
return $html . "</g>"
|
||||
. "<rect class='raum-endschiene' x='0' y='57' width='100' height='3'></rect>"
|
||||
. "</svg>";
|
||||
}
|
||||
|
||||
/** Kippschalter mit dem Stand, den das Geraet zuletzt gemeldet hat. */
|
||||
function kippSchalter($d, $schalter, $an)
|
||||
{
|
||||
$id = "sw" . intval($d["id"]);
|
||||
$html = "<div class='form-check form-switch raum-schalter'"
|
||||
. " data-ein='" . (isset($schalter["ein"]) ? intval($schalter["ein"]["id"]) : "") . "'"
|
||||
. " data-aus='" . (isset($schalter["aus"]) ? intval($schalter["aus"]["id"]) : "") . "'"
|
||||
. " data-um='" . (isset($schalter["um"]) ? intval($schalter["um"]["id"]) : "") . "'>"
|
||||
. "<input class='form-check-input' type='checkbox' role='switch' id='" . $id . "'"
|
||||
. ($an ? " checked" : "") . ">"
|
||||
. "<label class='form-check-label small' for='" . $id . "'>"
|
||||
. ($an ? "An" : "Aus") . "</label></div>";
|
||||
return $html;
|
||||
return "</div>";
|
||||
}
|
||||
|
||||
/** Ein "wahr"-artiger Wert - die Geraete melden mal True, mal 1, mal "on". */
|
||||
@@ -403,7 +281,7 @@ function istAn($wert)
|
||||
*
|
||||
* 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.
|
||||
* Parameter braucht, hat oben seine Bedienung.
|
||||
*/
|
||||
function mehrMenue($cmds)
|
||||
{
|
||||
@@ -411,9 +289,8 @@ function mehrMenue($cmds)
|
||||
if (!$cmds) {
|
||||
return "";
|
||||
}
|
||||
$html = "<div class='dropdown'>"
|
||||
. "<button class='btn btn-sm btn-link text-body-secondary p-0' type='button'"
|
||||
. " data-bs-toggle='dropdown' aria-label='Weitere Befehle'>"
|
||||
$html = "<div class='dropdown rf-mehr'>"
|
||||
. "<button class='rf-mehr-knopf' type='button' data-bs-toggle='dropdown' aria-label='Weitere Befehle'>"
|
||||
. "<i class='bi bi-three-dots'></i></button><ul class='dropdown-menu dropdown-menu-end'>";
|
||||
foreach ($cmds as $c) {
|
||||
$html .= "<li><button class='dropdown-item small' type='button' data-cmd='" . intval($c["id"]) . "'>"
|
||||
@@ -422,109 +299,145 @@ function mehrMenue($cmds)
|
||||
return $html . "</ul></div>";
|
||||
}
|
||||
|
||||
/** 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 = "<div class='rf-reihe'><span class='rf-reihe-titel'>" . htmlspecialchars($titel) . "</span>"
|
||||
. "<div class='rf-segment' role='radiogroup' aria-label='" . htmlspecialchars($titel, ENT_QUOTES) . "'>";
|
||||
foreach ($stufen as $s) {
|
||||
$html .= "<button type='button' role='radio' aria-checked='false' data-" . $rolle . "='" . $s[0] . "'>"
|
||||
. $s[1] . "</button>";
|
||||
}
|
||||
return $html . "</div></div>";
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// 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)
|
||||
/** Zahl aus einem Zustand, oder "" wenn es keinen gibt. */
|
||||
function zustandsZahl($d, $namen)
|
||||
{
|
||||
if ($position === null || $position === "") {
|
||||
return "";
|
||||
$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);
|
||||
}
|
||||
$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);
|
||||
return "<div class='rf-alle'><h3>Alle im Raum</h3><div class='rf-chips'>"
|
||||
. "<button type='button' class='rf-chip' data-alle='auf'><i class='bi bi-chevron-up'></i>Alle auf</button>"
|
||||
. "<button type='button' class='rf-chip' data-alle='zu'><i class='bi bi-chevron-down'></i>Alle zu</button>"
|
||||
. ($mitMy ? "<button type='button' class='rf-chip' data-alle='my'><i class='bi bi-star'></i>Alle auf my</button>" : "")
|
||||
. ($mitNeigung ? "<button type='button' class='rf-chip' data-alle='sicht'><i class='bi bi-distribute-vertical'></i>Sichtschutz</button>" : "")
|
||||
. "</div></div>";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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);
|
||||
|
||||
// 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);
|
||||
// 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"]) : "",
|
||||
];
|
||||
|
||||
// $kombi gehoert dazu: es steckt jetzt hinter dem Hoehenregler und hat im
|
||||
// Mehr-Menue nichts mehr verloren.
|
||||
$benutzt = array_filter([$auf, $stop, $zu, $position, $neigung, $kombi]);
|
||||
$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 .= "<div class='raum-jalousie'>";
|
||||
|
||||
// Auf, Stop, Zu untereinander: so zeigen die Knoepfe in die Richtung, in
|
||||
// die sie fahren.
|
||||
if ($auf || $stop || $zu) {
|
||||
$html .= "<div class='btn-group-vertical btn-group-sm raum-fahrt' role='group'>";
|
||||
foreach ([[$auf, "bi-chevron-up", "Auf"], [$stop, "bi-stop-fill", "Stop"],
|
||||
[$zu, "bi-chevron-down", "Zu"]] as $k) {
|
||||
if (!$k[0]) {
|
||||
continue;
|
||||
}
|
||||
$html .= "<button type='button' class='btn btn-outline-primary' data-cmd='" . intval($k[0]["id"]) . "'"
|
||||
. " title='" . htmlspecialchars($k[2], ENT_QUOTES) . "'"
|
||||
. " aria-label='" . htmlspecialchars($k[2], ENT_QUOTES) . "'>"
|
||||
. "<i class='bi " . $k[1] . "'></i></button>";
|
||||
}
|
||||
$html .= "</div>";
|
||||
$html = "<div class='rf-behang raum-geraet' data-actor='" . intval($d["id"]) . "'";
|
||||
foreach ($attr as $name => $wert) {
|
||||
$html .= " data-" . $name . "='" . htmlspecialchars(strval($wert), ENT_QUOTES) . "'";
|
||||
}
|
||||
// Erst das Bild, dann die Regler: neben den Fahrknoepfen faende der
|
||||
// Daumen sonst beim Antippen gleich einen Schieber.
|
||||
if ($position || $neigung) {
|
||||
$html .= lamellenBild($d["id"]);
|
||||
$html .= ">";
|
||||
|
||||
// Das Bild: dunkles Glas, davor Lamellen (mit Neigung) oder ein Panzer.
|
||||
// Mehr Lamellen als je sichtbar - die Hoehe schneidet ab.
|
||||
$html .= "<div class='rf-fenster'" . ($hoehe ? " data-rf-zieh title='Ziehen zum Einstellen'" : "") . " aria-hidden='true'>"
|
||||
. "<div class='rf-glas'><div class='rf-behang-teil'>"
|
||||
. ($neigung ? "<div class='rf-lamellen'>" . str_repeat("<i></i>", 40) . "</div>" : "<div class='rf-panzer'></div>")
|
||||
. "<div class='rf-schiene'></div></div></div></div>";
|
||||
|
||||
$html .= "<div class='rf-behang-mitte'>"
|
||||
. "<div class='rf-kopf-zeile'><b>" . htmlspecialchars($d["label"]) . "</b>"
|
||||
. "<span class='rf-pille' data-rf-stand></span>"
|
||||
. mehrMenue(restKommandos($d, [$auf, $stop, $zu, $my, $position, $neigung, $kombi])) . "</div>";
|
||||
|
||||
if ($auf || $stop || $zu || $my) {
|
||||
$html .= "<div class='rf-fahrt'>"
|
||||
. ($auf ? "<button type='button' data-rf-fahrt='auf'><i class='bi bi-chevron-up'></i>Auf</button>" : "<span></span>")
|
||||
. ($stop ? "<button type='button' class='rf-stop' data-rf-fahrt='stop' aria-label='Stop'><i class='bi bi-stop-fill'></i></button>" : "")
|
||||
. ($my ? "<button type='button' class='rf-my' data-rf-fahrt='my'><i class='bi bi-star-fill'></i>my</button>" : "")
|
||||
. ($zu ? "<button type='button' data-rf-fahrt='zu'><i class='bi bi-chevron-down'></i>Zu</button>" : "<span></span>")
|
||||
. "</div>";
|
||||
}
|
||||
if ($position) {
|
||||
// Kann das Geraet beides zusammen, geht die Hoehe ueber dieses
|
||||
// Kommando und nimmt die Neigung mit - sonst raste die Mechanik bei
|
||||
// der Hoehenfahrt um und niemand faehrt sie wieder an.
|
||||
$hoehenCmd = $kombi ?: $position;
|
||||
$hoehenParam = $kombi ? parameterNamens($kombi, POSITION_PARAMETER)
|
||||
: $position["params"][0];
|
||||
$mitParam = $kombi ? parameterNamens($kombi, NEIGUNG_PARAMETER) : null;
|
||||
$html .= senkrechterRegler($hoehenCmd, $hoehenParam, $standPos,
|
||||
"Position", "oben", "unten", "position",
|
||||
$neigung ? $mitParam : null, "neigung");
|
||||
$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 .= senkrechterRegler($neigung, $neigung["params"][0], $standNeigung,
|
||||
"Neigung", "auf", "zu", "neigung");
|
||||
$html .= stufenReihe("Lamellen", "rf-neig", [[0, "offen"], [25, $prozent(25)], [50, "halb"], [75, $prozent(75)], [100, "zu"]]);
|
||||
}
|
||||
$html .= mehrMenue($rest);
|
||||
return $html . "</div>" . karteZu();
|
||||
// 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 .= "<div class='rf-my-zeile' data-rf-my></div>";
|
||||
}
|
||||
return $html . "</div></div>";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Lampe: die Birne ist der Schalter und leuchtet in ihrer Farbe. Darunter
|
||||
* Helligkeit, Farbfelder und - eingeklappt - Effekt und Preset.
|
||||
*/
|
||||
function zeichneLicht($d)
|
||||
{
|
||||
@@ -533,88 +446,105 @@ function zeichneLicht($d)
|
||||
$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"];
|
||||
// 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 = "<div class='rf-lampe raum-geraet' data-actor='" . intval($d["id"]) . "'"
|
||||
. " data-ein='" . (isset($schalter["ein"]) ? intval($schalter["ein"]["id"]) : "") . "'"
|
||||
. " data-aus='" . (isset($schalter["aus"]) ? intval($schalter["aus"]["id"]) : "") . "'"
|
||||
. " data-um='" . (isset($schalter["um"]) ? intval($schalter["um"]["id"]) : "") . "'"
|
||||
. " data-an='" . ($an ? "1" : "") . "' style='--farbe:" . $hex . "'>";
|
||||
|
||||
$html .= "<div class='rf-lampe-kopf'>"
|
||||
. "<button type='button' class='rf-birne' data-rf-birne aria-pressed='" . ($an ? "true" : "false") . "'"
|
||||
. " aria-label='" . htmlspecialchars($d["label"], ENT_QUOTES) . " schalten'><i class='bi bi-lightbulb'></i></button>"
|
||||
. "<div class='rf-lampe-name'><b>" . htmlspecialchars($d["label"]) . "</b><span data-rf-lampe-stand></span></div>";
|
||||
|
||||
// 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)) . "</div><div class='rf-lampe-regler'>";
|
||||
|
||||
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 .= "<input type='range' class='rf-hell' min='" . $min . "' max='" . $max . "' value='" . $wert . "'"
|
||||
. " data-cmd='" . intval($helligkeit["id"]) . "' data-param='" . intval($p["id"]) . "'"
|
||||
. " aria-label='Helligkeit'>";
|
||||
}
|
||||
|
||||
$html = karteAuf($d, "");
|
||||
$html .= "<div class='d-flex justify-content-between align-items-center mb-2'>";
|
||||
$html .= $schalter ? kippSchalter($d, $schalter, $an) : "<span></span>";
|
||||
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 .= "<span class='raum-farbe d-inline-flex align-items-center gap-1'"
|
||||
. " data-cmd='" . intval($farbe["id"]) . "'";
|
||||
$html .= "<div class='rf-farben' data-cmd='" . intval($farbe["id"]) . "'";
|
||||
foreach ($farbe["params"] as $p) {
|
||||
$html .= " data-" . strtolower($p["name"]) . "='" . intval($p["id"]) . "'";
|
||||
}
|
||||
$html .= "><label class='small text-body-secondary' for='col" . intval($d["id"]) . "'>Farbe</label>"
|
||||
. "<input type='color' class='form-control form-control-color form-control-sm'"
|
||||
. " id='col" . intval($d["id"]) . "' value='" . $hex . "'></span>";
|
||||
}
|
||||
$html .= "</div>";
|
||||
|
||||
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;
|
||||
$html .= ">";
|
||||
foreach (["#ffd9a0" => "Warmweiß", "#fff1d6" => "Weiß", "#dfeeff" => "Kaltweiß", "#ff6b5b" => "Rot",
|
||||
"#ffb347" => "Orange", "#7ddc6f" => "Grün", "#5bb8ff" => "Blau", "#b48cff" => "Lila"] as $f => $name) {
|
||||
$html .= "<button type='button' class='rf-farbe' style='--f:" . $f . "' data-farbe='" . $f . "'"
|
||||
. " aria-pressed='" . ($f === $hex ? "true" : "false") . "' aria-label='" . $name . "' title='" . $name . "'></button>";
|
||||
}
|
||||
$benutzteIds[] = $c["id"];
|
||||
$p = $c["params"][0];
|
||||
$html .= "<div class='input-group input-group-sm mb-1 raum-cmd' data-cmdrow='" . intval($c["id"]) . "'>"
|
||||
. "<span class='input-group-text'>" . htmlspecialchars($c["name"]) . "</span>"
|
||||
. "<select class='form-select raum-sofort' data-param='" . intval($p["id"]) . "'"
|
||||
. " data-cmd='" . intval($c["id"]) . "'>";
|
||||
foreach ($p["options"] as $o) {
|
||||
$html .= "<option value='" . htmlspecialchars($o["value"], ENT_QUOTES) . "'>"
|
||||
. htmlspecialchars($o["label"]) . "</option>";
|
||||
}
|
||||
$html .= "</select></div>";
|
||||
$html .= "<label class='rf-farbe rf-farbe-eigen' title='Eigene Farbe'>"
|
||||
. "<input type='color' value='" . $hex . "' aria-label='Eigene Farbe'></label></div>";
|
||||
}
|
||||
|
||||
$rest = array_values(array_filter($d["commands"], function ($c) use ($benutzteIds) {
|
||||
return !in_array($c["id"], $benutzteIds, true);
|
||||
}));
|
||||
if ($rest) {
|
||||
$html .= "<div class='d-flex justify-content-end'>" . mehrMenue($rest) . "</div>";
|
||||
if ($listen) {
|
||||
$html .= "<details class='rf-effekte'><summary>" . htmlspecialchars(implode(" & ", array_map(function ($c) { return $c["name"]; }, $listen))) . "</summary>";
|
||||
foreach ($listen as $c) {
|
||||
$p = $c["params"][0];
|
||||
// Wenige Eintraege als Chips, lange Listen (WLED hat ueber 180
|
||||
// Effekte) als Auswahl - Chips waeren dort eine Wand.
|
||||
if (count($p["options"]) <= 16) {
|
||||
$html .= "<div class='rf-chips' data-cmd='" . intval($c["id"]) . "' data-param='" . intval($p["id"]) . "'>";
|
||||
foreach ($p["options"] as $o) {
|
||||
$html .= "<button type='button' class='rf-chip' data-wert='" . htmlspecialchars($o["value"], ENT_QUOTES) . "'"
|
||||
. " aria-pressed='false'>" . htmlspecialchars($o["label"]) . "</button>";
|
||||
}
|
||||
$html .= "</div>";
|
||||
} else {
|
||||
$html .= "<label class='rf-auswahl'><span>" . htmlspecialchars($c["name"]) . "</span>"
|
||||
. "<select class='form-select form-select-sm' data-cmd='" . intval($c["id"]) . "' data-param='" . intval($p["id"]) . "'>";
|
||||
foreach ($p["options"] as $o) {
|
||||
$html .= "<option value='" . htmlspecialchars($o["value"], ENT_QUOTES) . "'>" . htmlspecialchars($o["label"]) . "</option>";
|
||||
}
|
||||
$html .= "</select></label>";
|
||||
}
|
||||
}
|
||||
$html .= "</details>";
|
||||
}
|
||||
return $html . karteZu();
|
||||
return $html . "</div></div>";
|
||||
}
|
||||
|
||||
/** Alles, was nur an und aus kennt. */
|
||||
/** 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"]));
|
||||
$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 .= "<div class='d-flex justify-content-between align-items-center'>"
|
||||
. kippSchalter($d, $schalter, $an) . mehrMenue($rest) . "</div>";
|
||||
return $html . karteZu();
|
||||
return "<button type='button' class='rf-schalter raum-geraet' data-actor='" . intval($d["id"]) . "'"
|
||||
. " data-ein='" . (isset($schalter["ein"]) ? intval($schalter["ein"]["id"]) : "") . "'"
|
||||
. " data-aus='" . (isset($schalter["aus"]) ? intval($schalter["aus"]["id"]) : "") . "'"
|
||||
. " data-um='" . (isset($schalter["um"]) ? intval($schalter["um"]["id"]) : "") . "'"
|
||||
. " aria-pressed='" . ($an ? "true" : "false") . "'>"
|
||||
. "<span class='rf-sym'><i class='bi bi-power'></i></span>"
|
||||
. "<b>" . htmlspecialchars($d["label"]) . "</b><span class='rf-st'>" . ($an ? "an" : "aus") . "</span></button>";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -636,9 +566,8 @@ function zeichneMesswerte($d)
|
||||
}
|
||||
$html .= "</div>";
|
||||
if ($weitere) {
|
||||
$html .= "<details class='mt-2'><summary class='small text-body-secondary'>"
|
||||
. count($weitere) . " weitere Werte</summary>"
|
||||
. "<div class='raum-werte mt-2'>";
|
||||
$html .= "<details class='rf-effekte'><summary>" . count($weitere) . " weitere Werte</summary>"
|
||||
. "<div class='raum-werte'>";
|
||||
foreach ($weitere as $s) {
|
||||
$html .= wertKachel($s);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user