From 67c2a774ac6b90fc3abd7c734cdea0f5970aaf09 Mon Sep 17 00:00:00 2001 From: Moirtz Wagner Date: Sat, 5 Sep 2026 12:55:45 +0200 Subject: [PATCH] Nach dem Fahrbefehl liest der Server nach, nicht der Browser Wer das Raum-Modal gleich nach einem Fahrbefehl zumachte, nahm den einzigen Zuschauer mit: die Verfolgung im Browser endet mit dem Modal, und niemand hielt fest, wo die Jalousie stehen blieb. In actor_states stand bis zum naechsten Poll des Runners der alte Wert - genau die fuenf Minuten, die eigentlich weg sein sollten. ajax/room.php schickt die Antwort auf ein Tahoma-Kommando jetzt ab, schliesst die Verbindung und wartet danach selbst auf das Ende der Fahrt (fastcgi_finish_request, dieselbe Bauart wie in ajax/tahoma.php). Ob der Browser noch da ist, spielt keine Rolle mehr; ignore_user_abort deckt auch den Fall ab, dass er mitten im Aufruf verschwindet. Die Verfolgung im Modal bleibt, sie ist jetzt aber nur noch fuer die Anzeige zustaendig, solange jemand hinsieht. Das Aufschreiben haengt nicht mehr daran. Geprueft an der Wozi-Schiebetuer: Kommando auf Position 70, 0,7 Sekunden spaeter die Seite neu geladen. 48 Sekunden danach zeigte das frisch geoeffnete Modal 70 - und eine Neigung von 59 statt 62, die sich beim Fahren von selbst verstellt hatte und die nur die Box kannte. Co-Authored-By: Claude Opus 5 --- ajax/room.php | 46 ++++++++++++++++++++++++++++++-- restricted/commands.php | 58 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/ajax/room.php b/ajax/room.php index 3ce2064..20b9b75 100644 --- a/ajax/room.php +++ b/ajax/room.php @@ -46,6 +46,38 @@ function raumAntwort($data, $code = 200) exit; } +/** + * Die Antwort abschicken, aber weiterarbeiten. + * + * Nach einem Fahrbefehl muss noch jemand abwarten, wo die Jalousie stehen + * bleibt, und das aufschreiben - sonst steht in actor_states bis zum + * naechsten Poll des Runners der alte Wert. Das dem Browser zu ueberlassen + * ging schief: wer das Modal gleich nach dem Fahrbefehl zumachte, nahm den + * einzigen Zuschauer mit. + * + * Dieselbe Bauart wie in ajax/tahoma.php: Content-Length setzen, ausliefern, + * die Verbindung schliessen, weiterlaufen. Ohne FPM bleibt nur, die Puffer zu + * leeren - dann hat der Browser seine Daten zwar, wartet aber trotzdem. + */ +function raumAntwortUndWeiter($data) +{ + http_response_code(200); + header("Content-Type: application/json; charset=utf-8"); + $text = json_encode($data, JSON_UNESCAPED_UNICODE); + header("Content-Length: " . strlen($text)); + echo $text; + // Der Aufrufer ist weg, das Skript soll trotzdem zu Ende kommen. + ignore_user_abort(true); + if (function_exists("fastcgi_finish_request")) { + fastcgi_finish_request(); + return; + } + while (ob_get_level() > 0) { + ob_end_flush(); + } + flush(); +} + // --- Schaltende Aufrufe --------------------------------------------------- if ($_SERVER["REQUEST_METHOD"] === "POST") { $body = json_decode(file_get_contents("php://input"), true); @@ -54,10 +86,20 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") { } try { if (($_GET["action"] ?? "") === "command") { - $text = executeCommand(intval($body["command_id"] ?? 0), + $id = intval($body["command_id"] ?? 0); + $text = executeCommand($id, is_array($body["params"] ?? null) ? $body["params"] : [], !empty($body["probe"])); - raumAntwort(["ok" => $text]); + // Bei einem Tahoma-Geraet ist mit dem Absenden noch nichts + // festgehalten: die Box meldet die neue Stellung erst, wenn die + // Fahrt zu Ende ist. Also Antwort raus und in Ruhe nachsehen. + $url = empty($body["probe"]) ? tahomaUrlZuKommando($id) : null; + if ($url === null) { + raumAntwort(["ok" => $text]); + } + raumAntwortUndWeiter(["ok" => $text]); + tahomaNachlesen($url); + exit; } if (($_GET["action"] ?? "") === "temp") { // Bewusst derselbe Weg wie im alten Thermostat-Modal: das Topic diff --git a/restricted/commands.php b/restricted/commands.php index e578a73..71cf54c 100644 --- a/restricted/commands.php +++ b/restricted/commands.php @@ -139,6 +139,21 @@ function executeCommand($commandId, $werte, $probe = false) throw new RuntimeException("Für " . $cmd["actor_name"] . " gibt es keinen Weg zum Gerät."); } +/** + * Die Tahoma-Geraete-URL hinter einem Kommando, null wenn es keine ist. + * Damit der Aufrufer nach dem Schalten weiss, ob sich ein Nachlesen lohnt. + */ +function tahomaUrlZuKommando($commandId) +{ + $cmd = loadCommand($commandId); + $pin = strval($GLOBALS["tahoma_PIN"] ?? ""); + if (!$cmd || $pin === "" + || strpos(strval($cmd["actor_url"]), "://" . $pin . "/") === false) { + return null; + } + return $cmd["actor_url"]; +} + /** Werte in der Reihenfolge der Parameter, fehlende als leer. */ function parameterWerte($cmd, $werte) { @@ -445,6 +460,49 @@ function warteAufJalousie($deviceUrl, $neigungZiel, $schliessungZiel = null) return false; } +/** + * Nach einem Kommando abwarten, bis das Geraet steht, und seinen Stand in + * actor_states festhalten. + * + * Gedacht fuer den Aufruf *nach* fastcgi_finish_request(): der Browser hat + * seine Antwort dann laengst, und ob jemand noch zusieht, spielt keine + * Rolle mehr. Genau daran scheiterte die Verfolgung im Modal - wer es + * direkt nach dem Fahrbefehl zumachte, liess niemanden zurueck, der den + * Endstand haette aufschreiben koennen. Bis zum naechsten Poll des Runners + * stand dann der alte Wert in der Tabelle, fuenf Minuten lang. + * + * Gewartet wird nur auf core:MovingState, ohne Zielwerte - welche das + * waeren, weiss hier niemand, und nicht jedes Kommando loest eine Fahrt + * aus. Der Vorlauf faengt die Traegheit der Box ab: kurz nach dem Kommando + * steht dort noch "faehrt nicht". + * + * Anders als die Warteschleife der Kugelschreiber-Mechanik laeuft das hier + * erst, wenn das ganze Manoever durch ist - beide Schwenks sind geschickt, + * die Fahrt, auf die gewartet wird, ist die letzte. + */ +function tahomaNachlesen($deviceUrl) +{ + $start = time(); + $gestartet = false; + $zustaende = null; + while (time() - $start < JALOUSIE_WARTE_SEKUNDEN) { + sleep(2); + $gelesen = tahomaZustaende($deviceUrl); + if ($gelesen === null) { + continue; + } + $zustaende = $gelesen; + if (($zustaende["core:MovingState"] ?? null) === true) { + $gestartet = true; + continue; + } + if ($gestartet || time() - $start >= JALOUSIE_VORLAUF_SEKUNDEN) { + break; + } + } + return $zustaende === null ? 0 : tahomaZustaendeSchreiben($deviceUrl, $zustaende); +} + /** * Die Geraete-URL eines Aktors, null wenn es ihn nicht gibt oder er nicht an * der Tahoma-Box haengt.