Die Gruppenueberschriften sind Tabs geworden, Beschattung zuerst - das ist es, was man in einem Raum am haeufigsten anfasst. Vorher scrollte man an drei Rollladen vorbei, um zum Licht zu kommen. Bei nur einer Gruppe bleibt die Leiste weg; auf schmalen Schirmen scrollt sie seitwaerts, statt die Namen umzubrechen. Bei den Jalousien stehen Auf, Stop und Zu jetzt untereinander - so zeigen die Knoepfe in die Richtung, in die sie fahren. Die Hoehe daneben ist ein senkrechter Regler mit "oben" und "unten" an den Enden: der Griff steht dort, wo der Rollladen steht, und niemand muss "100 %" uebersetzen. Die Neigung hat ein Bild dazubekommen. "30 %" oder "70 %" sagt nicht, ob man noch hinaussieht; vier Streifen sagen es. Gezeichnet wird die Hoehe, die eine gekippte Lamelle von vorn zeigt (w * sin(Winkel)) - bei "auf" sieht man nur ihre Kante und viel Luft dazwischen, bei "zu" deckt sie den Abstand zur naechsten vollstaendig ab. Genau das, was das Licht abhaelt. Die Regler faerben ihre Bahn jetzt mit, wie es der Temperaturregler seit jeher tut - dieselbe Eigenschaft --background-size, nur beim senkrechten von oben nach unten. css/solar.css hatte gemischte Zeilenenden und ist dabei durchgehend auf LF gebracht worden; das erklaert den grossen Diff bei kleiner Aenderung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
140 lines
5.6 KiB
PHP
140 lines
5.6 KiB
PHP
<?php
|
||
/**
|
||
* Raum-Modal: alles, was in einem Raum haengt, auf einer Seite.
|
||
*
|
||
* Loest das frühere Thermostat-Modal ab. Der Grundriss zeigt Raeume, also
|
||
* sollte ein Klick darauf den Raum oeffnen und nicht nur seine Heizung - die
|
||
* Rollladen im selben Zimmer waren bisher gar nicht erreichbar.
|
||
*
|
||
* Welche Geraete zu einem Raum gehoeren, sagt die Spalte actors.room (siehe
|
||
* den Zuordnungsdialog in der Karte "Automatismen").
|
||
*
|
||
* GET ?room=EG_Bad Inhalt des Modals
|
||
* POST ?action=command {"command_id": N, "params": {...}} - ein Kommando
|
||
* POST ?action=temp {"room": "EG_Bad", "temp": 21} - Solltemperatur
|
||
*/
|
||
|
||
// Ein "Zu" an einer Jalousie faehrt herunter, wartet und neigt danach - das
|
||
// dauert in der Summe ueber eine Minute. Die Vorgabe von PHP reicht dafuer
|
||
// nicht; ajax/tahoma.php macht es an derselben Stelle genauso.
|
||
set_time_limit(180);
|
||
|
||
require_once("../helper.php");
|
||
require_once("../restricted/automations.php");
|
||
require_once("../restricted/commands.php");
|
||
require_once("../restricted/roomControls.php");
|
||
require_once("./phpMQTT.php");
|
||
|
||
if (!checkLogin()) {
|
||
http_response_code(403);
|
||
exit;
|
||
}
|
||
|
||
function raumAntwort($data, $code = 200)
|
||
{
|
||
http_response_code($code);
|
||
header("Content-Type: application/json; charset=utf-8");
|
||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||
exit;
|
||
}
|
||
|
||
// --- Schaltende Aufrufe ---------------------------------------------------
|
||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||
$body = json_decode(file_get_contents("php://input"), true);
|
||
if (!is_array($body)) {
|
||
raumAntwort(["error" => "Konnte die Anfrage nicht lesen."], 400);
|
||
}
|
||
try {
|
||
if (($_GET["action"] ?? "") === "command") {
|
||
$text = executeCommand(intval($body["command_id"] ?? 0),
|
||
is_array($body["params"] ?? null) ? $body["params"] : [],
|
||
!empty($body["probe"]));
|
||
raumAntwort(["ok" => $text]);
|
||
}
|
||
if (($_GET["action"] ?? "") === "temp") {
|
||
// Bewusst derselbe Weg wie im alten Thermostat-Modal: das Topic
|
||
// Raumtemp/<Etage>/<Raum>/changeSetTemp.
|
||
$raum = preg_replace('/[^A-Za-z0-9_]/', '', strval($body["room"] ?? ""));
|
||
$temp = intval($body["temp"] ?? 0);
|
||
if ($temp < 10 || $temp > 30) {
|
||
raumAntwort(["error" => "Temperatur außerhalb 10–30 °C."], 400);
|
||
}
|
||
$mqtt = new Bluerhinos\phpMQTT(MQTT_BROKER, MQTT_PORT, "RoomTempSetter_" . getmypid());
|
||
if (!$mqtt->connect(true, null, "", "")) {
|
||
raumAntwort(["error" => "Keine Verbindung zum MQTT-Broker."], 500);
|
||
}
|
||
$mqtt->publish("Raumtemp/" . str_replace("_", "/", $raum) . "/changeSetTemp",
|
||
strval($temp), 0, false);
|
||
$mqtt->close();
|
||
raumAntwort(["ok" => "Solltemperatur " . $temp . " °C"]);
|
||
}
|
||
raumAntwort(["error" => "Unbekannte Aktion."], 400);
|
||
} catch (InvalidArgumentException $e) {
|
||
raumAntwort(["error" => $e->getMessage()], 400);
|
||
} catch (Throwable $e) {
|
||
raumAntwort(["error" => $e->getMessage()], 500);
|
||
}
|
||
}
|
||
|
||
|
||
// --- Inhalt des Modals ----------------------------------------------------
|
||
$schluessel = preg_replace('/[^A-Za-z0-9_]/', '', strval($_GET["room"] ?? ""));
|
||
$teile = explode("_", $schluessel, 2);
|
||
$etage = $teile[0] ?? "";
|
||
$name = $teile[1] ?? "";
|
||
$raumKey = $etage . "/" . $name;
|
||
|
||
// Hat der Raum ein Thermostat? Nur dann gibt es etwas zu heizen.
|
||
$hatThermostat = false;
|
||
foreach (allRooms() as $raum) {
|
||
if ($raum["floor"] === $etage && $raum["mqtt"] === $name) {
|
||
$hatThermostat = isset($raum["x"]); // Kachel = Thermostat vorhanden
|
||
}
|
||
}
|
||
|
||
// Alles im Raum, sortiert nach Bedienform. Das Thermostat selbst faellt
|
||
// heraus: es wird oben zum Schieberegler und braucht daneben keine zweite
|
||
// Bedienung - "Solltemperatur" stand vorher zweimal im Modal.
|
||
$katalog = deviceCatalog();
|
||
$gruppen = [];
|
||
foreach ($katalog as $d) {
|
||
if ($d["room"] !== $raumKey) {
|
||
continue;
|
||
}
|
||
$form = bedienform($d);
|
||
if ($form === "heizung") {
|
||
continue;
|
||
}
|
||
$gruppen[$form][] = $d;
|
||
}
|
||
|
||
echo "<div class='p-1' id='roomView' data-room='" . htmlspecialchars($schluessel, ENT_QUOTES) . "'>";
|
||
|
||
if ($hatThermostat) {
|
||
echo "<div class='card mb-2'><div class='card-body py-2'>";
|
||
echo "<div class='d-flex justify-content-between align-items-baseline'>";
|
||
echo "<label class='form-label mb-0 small' for='modal-slider'>Solltemperatur</label>";
|
||
echo "<span id='modal-slider-label' class='fw-semibold text-nowrap ms-2' style='color:#297195'>–</span>";
|
||
echo "</div>";
|
||
echo "<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.
|
||
echo "<div class='d-flex flex-wrap gap-3 small text-body-secondary'>";
|
||
echo "<span><i class='bi bi-thermometer-half me-1'></i><span data-mqtt='Temp[degC]'>–</span> °C</span>";
|
||
echo "<span><i class='bi bi-droplet me-1'></i><span data-mqtt='rHum[%]'>–</span> %rF</span>";
|
||
echo "<span data-mqtt-heizung class='d-none'><i class='bi bi-fire text-danger me-1'></i>heizt</span>";
|
||
echo "</div>";
|
||
echo "</div></div>";
|
||
}
|
||
|
||
if (!$gruppen && !$hatThermostat) {
|
||
echo "<p class='text-body-secondary mb-0'>Diesem Raum ist noch nichts zugeordnet. "
|
||
. "Das geht über das Haus-Symbol in der Karte „Automatismen“.</p>";
|
||
}
|
||
|
||
echo zeichneGruppen($gruppen);
|
||
|
||
echo "<div id='roomFeedback' class='small mt-2'></div>";
|
||
echo "</div>";
|