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:
2026-09-15 22:21:19 +02:00
co-authored by Claude Opus 5
parent fed0d35e03
commit d5d2d8f336
8 changed files with 1201 additions and 1016 deletions
+82 -21
View File
@@ -12,6 +12,9 @@
* GET ?room=12 Inhalt des Modals, 12 = rooms.id
* POST ?action=command {"command_id": N, "params": {...}} - ein Kommando
* POST ?action=temp {"room": 12, "temp": 21} - Solltemperatur
* POST ?action=jalousie {"actor": 5} - Stand einer Jalousie
* POST ?action=my {"actor": 5} - aktuellen Stand als
* my-Position speichern
*
* Frueher hiess der Raum "EG_Bad" und wurde am Unterstrich zerlegt. Das hielt
* weder ein Leerzeichen im Namen aus (erst nachgeruestet) noch eine
@@ -158,9 +161,53 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
? intval($zustaende[$feld]) : null;
};
raumAntwort([
"faehrt" => ($zustaende["core:MovingState"] ?? null) === true,
"position" => $zahl("core:ClosureState"),
"neigung" => $zahl("core:SlateOrientationState"),
"faehrt" => ($zustaende["core:MovingState"] ?? null) === true,
"position" => $zahl("core:ClosureState"),
"neigung" => $zahl("core:SlateOrientationState"),
"myPosition" => $zahl("core:Memorized1PositionState"),
"myNeigung" => $zahl("core:Memorized1OrientationState"),
]);
}
// Den Stand, auf dem die Jalousie gerade steht, als my-Position in den
// Motor schreiben. Die Box kennt dafuer setMemorized1Position und bei
// Raffstores zusaetzlich setMemorized1Orientation; die Geraetesuche
// uebernimmt beide nicht als Kommando, deshalb geht es hier direkt.
// Gespeichert wird, was die Box meldet - nicht, was im Browser steht:
// der koennte einer Fahrt hinterherhinken.
if (($_GET["action"] ?? "") === "my") {
$url = tahomaAktorUrl(intval($body["actor"] ?? 0));
if ($url === null) {
raumAntwort(["error" => "Kein Tahoma-Gerät mit dieser Nummer."], 404);
}
$vorher = tahomaZustaende($url);
if ($vorher === null) {
raumAntwort(["error" => "Tahoma-Box antwortet nicht."], 503);
}
if (($vorher["core:MovingState"] ?? null) === true) {
raumAntwort(["error" => "Die Jalousie fährt noch bitte warten, bis sie steht."], 409);
}
if (!isset($vorher["core:Memorized1PositionState"]) || !is_numeric($vorher["core:ClosureState"] ?? null)) {
raumAntwort(["error" => "Dieses Gerät kann keine my-Position speichern."], 400);
}
$position = intval($vorher["core:ClosureState"]);
$neigung = null;
tahomaKommando($url, "setMemorized1Position", [$position]);
$text = "my-Position " . $position . "%";
if (isset($vorher["core:Memorized1OrientationState"]) && is_numeric($vorher["core:SlateOrientationState"] ?? null)) {
$neigung = intval($vorher["core:SlateOrientationState"]);
tahomaKommando($url, "setMemorized1Orientation", [$neigung]);
$text .= ", Lamellen " . $neigung . "%";
}
// Die Box braucht einen Moment, bis sie den neuen Wert meldet.
sleep(2);
$nachher = tahomaZustaende($url);
if ($nachher !== null) {
tahomaZustaendeSchreiben($url, $nachher);
}
raumAntwort([
"ok" => $text . " gespeichert",
"myPosition" => $position,
"myNeigung" => $neigung,
]);
}
raumAntwort(["error" => "Unbekannte Aktion."], 400);
@@ -210,34 +257,48 @@ foreach ($katalog as $d) {
// einer Geraeteliste: ein Regler fuer den Raum statt einer Karte je Geraet.
$fertig = [];
if ($hatThermostat) {
$karte = "<div class='card mb-2 raum-geraet'><div class='card-body py-2'>"
. "<div class='d-flex justify-content-between align-items-baseline'>"
. "<label class='form-label mb-0 small' for='modal-slider'>Solltemperatur</label>"
. "<span id='modal-slider-label' class='fw-semibold text-nowrap ms-2' style='color:#297195'></span>"
. "</div>"
. "<input type='range' id='modal-slider' class='form-range range-color-track' min='10' max='30'>"
// Der Ist-Zustand kommt live ueber MQTT, nicht aus der Datenbank -
// der Browser hoert ohnehin schon auf Raumtemp/#, und ein Wert von
// vorhin waere neben dem Regler nur irrefuehrend.
. "<div class='d-flex flex-wrap gap-3 small text-body-secondary'>"
. "<span><i class='bi bi-thermometer-half me-1'></i><span data-mqtt='Temp[degC]'></span> °C</span>"
. "<span><i class='bi bi-droplet me-1'></i><span data-mqtt='rHum[%]'></span> %rF</span>"
. "<span data-mqtt-heizung class='d-none'><i class='bi bi-fire text-danger me-1'></i>heizt</span>"
. "</div></div></div>";
$fertig["heizung"] = $karte;
// Ring mit Ist-Temperatur und ziehbarem Soll-Punkt, daneben - und + und
// die Schnellwahl. Alle Zahlen kommen live ueber MQTT (der Browser hoert
// ohnehin auf den Thermostat-Zweig) und werden von js/solar/raumfenster.js
// eingetragen. Das Thermostat nimmt ganze Grad von 10 bis 30 °C, siehe
// action=temp.
$fertig["heizung"] = "<div class='rf-heiz' data-rf-heiz-teil data-min='10' data-max='30'>"
. "<div class='rf-ring'><svg viewBox='0 0 170 170'>"
. "<defs><linearGradient id='rfHeizVerlauf' gradientUnits='userSpaceOnUse' gradientTransform='rotate(-135 85 85)' x1='15' y1='0' x2='155' y2='0'>"
. "<stop offset='0' stop-color='#4b9fd6'/><stop offset='.55' stop-color='#e8a33a'/><stop offset='1' stop-color='#e5624f'/>"
. "</linearGradient></defs>"
. "<circle class='rf-ring-spur' cx='85' cy='85' r='70' transform='rotate(135 85 85)'/>"
. "<circle class='rf-ring-ist' cx='85' cy='85' r='70' transform='rotate(135 85 85)'/>"
. "<circle class='rf-ring-griff' cx='85' cy='85' r='70' transform='rotate(135 85 85)' data-rf-ringzug/>"
. "<circle class='rf-ring-soll' cx='155' cy='85' r='10' data-rf-ringzug tabindex='0' role='slider'"
. " aria-label='Solltemperatur' aria-valuemin='10' aria-valuemax='30'/></svg>"
. "<div class='rf-ring-mitte'><small>im Raum</small><b data-rf-heiz='ist'></b><span data-rf-heiz='soll'>Soll </span></div></div>"
. "<div class='rf-heiz-rechts'><h3>Solltemperatur</h3>"
. "<div class='rf-stepper'><button type='button' data-rf-schritt='-1' aria-label='1 Grad kälter'></button>"
. "<output data-rf-heiz='wert'></output>"
. "<button type='button' data-rf-schritt='1' aria-label='1 Grad wärmer'>+</button></div>"
. "<div class='rf-chips'>"
. "<button type='button' class='rf-chip' data-rf-soll='10' aria-pressed='false'>Minimum <span>10°</span></button>"
. "<button type='button' class='rf-chip' data-rf-soll='17' aria-pressed='false'>Eco <span>17°</span></button>"
. "<button type='button' class='rf-chip' data-rf-soll='21' aria-pressed='false'>Komfort <span>21°</span></button>"
. "<button type='button' class='rf-chip' data-rf-soll='23' aria-pressed='false'>Warm <span>23°</span></button></div>"
. "<div class='rf-hinweis' data-rf-heiz='hinweis' hidden><i class='bi bi-info-circle'></i><span></span></div>"
. "<div class='rf-pillen'><span class='rf-pille'><i class='bi bi-droplet'></i><span data-rf-heiz='feuchte'></span></span>"
. "<span class='rf-pille' data-rf-heiz='puffer' hidden><i class='bi bi-fire'></i>Pufferbetrieb</span></div>"
. "</div></div>";
}
// Der Thermostat-Zweig reist mit: das Modal fuellt Ist-Werte und Heizsymbol
// aus dem laufenden MQTT-Strom und muss wissen, wo es nachsehen soll.
echo "<div class='p-1' id='roomView' data-room='" . $raum["nr"] . "'"
echo "<div class='rf' id='roomView' data-room='" . $raum["nr"] . "'"
. " data-thermostat='" . htmlspecialchars((string)$raum["thermostat"], ENT_QUOTES) . "'>";
if (!$gruppen && !$fertig) {
echo "<p class='text-body-secondary mb-0'>Diesem Raum ist noch nichts zugeordnet. "
echo "<p class='rf-leer'>Diesem Raum ist noch nichts zugeordnet. "
. "Das geht über das Haus-Symbol in der Karte „Automatismen“.</p>";
}
echo zeichneGruppen($gruppen, $fertig);
echo "<div id='roomFeedback' class='small mt-2'></div>";
echo "<div id='roomFeedback' class='rf-meldung' aria-live='polite'>Änderungen gelten sofort</div>";
echo "</div>";