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 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 12:55:45 +02:00
co-authored by Claude Opus 5
parent 60c4ad989b
commit 67c2a774ac
2 changed files with 102 additions and 2 deletions
+43 -1
View File
@@ -46,6 +46,38 @@ function raumAntwort($data, $code = 200)
exit; 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 --------------------------------------------------- // --- Schaltende Aufrufe ---------------------------------------------------
if ($_SERVER["REQUEST_METHOD"] === "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
$body = json_decode(file_get_contents("php://input"), true); $body = json_decode(file_get_contents("php://input"), true);
@@ -54,11 +86,21 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
} }
try { try {
if (($_GET["action"] ?? "") === "command") { 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"] : [], is_array($body["params"] ?? null) ? $body["params"] : [],
!empty($body["probe"])); !empty($body["probe"]));
// 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]); raumAntwort(["ok" => $text]);
} }
raumAntwortUndWeiter(["ok" => $text]);
tahomaNachlesen($url);
exit;
}
if (($_GET["action"] ?? "") === "temp") { if (($_GET["action"] ?? "") === "temp") {
// Bewusst derselbe Weg wie im alten Thermostat-Modal: das Topic // Bewusst derselbe Weg wie im alten Thermostat-Modal: das Topic
// Raumtemp/<Etage>/<Raum>/changeSetTemp. // Raumtemp/<Etage>/<Raum>/changeSetTemp.
+58
View File
@@ -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."); 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. */ /** Werte in der Reihenfolge der Parameter, fehlende als leer. */
function parameterWerte($cmd, $werte) function parameterWerte($cmd, $werte)
{ {
@@ -445,6 +460,49 @@ function warteAufJalousie($deviceUrl, $neigungZiel, $schliessungZiel = null)
return false; 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 * Die Geraete-URL eines Aktors, null wenn es ihn nicht gibt oder er nicht an
* der Tahoma-Box haengt. * der Tahoma-Box haengt.