diff --git a/js/solar/homeMQTT.js b/js/solar/homeMQTT.js index bdac51c..31e3d89 100644 --- a/js/solar/homeMQTT.js +++ b/js/solar/homeMQTT.js @@ -367,6 +367,16 @@ function switchFloor(floor){ schieber.addEventListener("change", () => { const params = {}; params[regler.dataset.param] = schieber.value; + // Der Hoehenregler nimmt die Neigung mit: eine Hoehenfahrt rastet die + // Lamellen um, und der Server faehrt sie nur dann wieder an, wenn der + // Befehl ein Neigungsziel dabei hat (Kugelschreiber-Mechanik, siehe + // sendeTahoma() in restricted/commands.php). Welcher Parameter das + // ist, sagt der Server ueber data-mit-param. + if (regler.dataset.mitParam) { + const daneben = regler.closest(".raum-jalousie") + .querySelector("[data-rolle='" + regler.dataset.mitRolle + "'] input[type=range]"); + if (daneben) params[regler.dataset.mitParam] = daneben.value; + } raumKommando(Number(regler.dataset.cmd), params, regler); }); }); @@ -584,6 +594,11 @@ function switchFloor(floor){ const beginn = Date.now(); let gefahren = false; + // Der zuletzt im Stillstand gelesene Stand. "core:MovingState ist false" + // heisst noch nicht "steht": die Box meldet das Ende der Fahrt, bevor die + // Werte darauf nachgezogen haben. Fertig ist es erst, wenn zwei Abfragen + // hintereinander dasselbe sagen. + let letzterStand = null; // Solange der Auftrag laeuft, ist nichts entschieden: die // Kugelschreiber-Mechanik schickt zwei Befehle mit einer Wartezeit // dazwischen, und zwischen ihnen steht die Jalousie still. Wer da @@ -603,7 +618,7 @@ function switchFloor(floor){ delete karte.dataset.jalousieBis; } - function runde(letzte) { + function runde() { raumRequest("jalousie", { actor: actor }).then(stand => { // Ein Fehler soll auffallen. Ohne die Meldung endet die Schleife // still, und die Anzeige bleibt einfach stehen - nicht zu @@ -615,6 +630,12 @@ function switchFloor(floor){ aufhoeren(); return; } + const abgelesen = stand.faehrt + ? null + : String(stand.position) + "/" + String(stand.neigung); + const steht = abgelesen !== null && abgelesen === letzterStand; + letzterStand = abgelesen; + if (stand.faehrt) { gefahren = true; } else if (Date.now() >= ruheAb) { @@ -623,31 +644,34 @@ function switchFloor(floor){ // "Neigung 0" - anzeigen hiesse, den Umweg vorzufuehren. jalousieAnzeigen(karte, stand); } - if (letzte || Date.now() > Number(karte.dataset.jalousieBis)) { + if (Date.now() > Number(karte.dataset.jalousieBis)) { aufhoeren(); return; } // Steht sie still, ist die Fahrt vorbei - aber erst, wenn wir sie // haben fahren sehen oder der Vorlauf um ist. Eine letzte Runde // danach holt den Wert, den die Box beim Anhalten setzt. - const fertig = !stand.faehrt - && Date.now() >= ruheAb - && (gefahren || !vorlaufNoetig - || Date.now() - beginn >= JALOUSIE_VORLAUF_MS); - // Die Bestaetigungsrunde faengt Werte ab, die die Box erst nach dem - // Anhalten nachreicht. Beim blossen Aufmachen eines Raums, in dem - // sich nichts geruehrt hat, gibt es nichts nachzureichen - dann - // bleibt es bei einer Abfrage je Jalousie. - if (fertig && !vorlaufNoetig && !gefahren) { + // Beim blossen Aufmachen eines Raums, in dem sich nichts geruehrt + // hat, gibt es nichts abzuwarten - dann bleibt es bei einer Abfrage + // je Jalousie. + if (!vorlaufNoetig && !gefahren && !stand.faehrt) { aufhoeren(); return; } - setTimeout(() => runde(fertig), JALOUSIE_TAKT_MS); + const fertig = steht + && Date.now() >= ruheAb + && (gefahren || !vorlaufNoetig + || Date.now() - beginn >= JALOUSIE_VORLAUF_MS); + if (fertig) { + aufhoeren(); + return; + } + setTimeout(() => runde(false), JALOUSIE_TAKT_MS); }); } // Beim Oeffnen sofort nachsehen, nach einem Kommando erst im Takt: da // stuende die Box ohnehin noch auf dem alten Wert. - setTimeout(() => runde(false), auftrag ? JALOUSIE_TAKT_MS : 0); + setTimeout(runde, auftrag ? JALOUSIE_TAKT_MS : 0); } /** diff --git a/restricted/commands.php b/restricted/commands.php index 71cf54c..e455ca3 100644 --- a/restricted/commands.php +++ b/restricted/commands.php @@ -27,24 +27,13 @@ const MQTT_BROKER = "localhost"; const MQTT_PORT = 1883; /** - * Bis zu dieser Neigung fahren die Aussenjalousien direkt. + * So heissen die beiden Parameter einer Jalousie im Geraetemodell. * - * Sie haben eine Kugelschreiber-Mechanik: beim Herunterfahren stehen die - * Lamellen bei etwa 30 %. In Richtung 0 % laesst sich von jeder Stellung aus - * direkt neigen; ueber 30 % hinaus muss die Mechanik erst einmal auf 0 % - * zurueck, sonst rastet sie nicht um. Ein "Neigung 80 %" ohne diesen Umweg - * bleibt wirkungslos. - * - * Das gilt fuer jedes Kommando, das die Neigung setzt - "Neigung" ebenso wie - * "Position+Neigung". Erkannt wird es deshalb am Parameter und nicht am - * Kommandonamen: beide heissen ihren Neigungsparameter "Neigung". - * - * Dasselbe steht in SolarManager/autoActions/transports.py - beide Versender - * brauchen es, und beide muessen es wissen. + * An ihnen wird die Kugelschreiber-Mechanik erkannt, nicht am Kommandonamen: + * setzt ein Befehl beide, war eine Hoehenfahrt dabei und die Lamellen sind + * umgerastet - dann muss die Neigung ueber 0 % angefahren werden (siehe + * sendeTahoma()). Setzt er nur die Neigung, steht die Mechanik schon richtig. */ -const NEIGUNG_DIREKT_MAX = 30; - -/** So heissen die beiden Parameter einer Jalousie im Geraetemodell. */ const NEIGUNG_PARAMETER = "Neigung"; const POSITION_PARAMETER = "Position"; @@ -485,6 +474,7 @@ function tahomaNachlesen($deviceUrl) $start = time(); $gestartet = false; $zustaende = null; + $letzterStand = null; while (time() - $start < JALOUSIE_WARTE_SEKUNDEN) { sleep(2); $gelesen = tahomaZustaende($deviceUrl); @@ -492,11 +482,21 @@ function tahomaNachlesen($deviceUrl) continue; } $zustaende = $gelesen; - if (($zustaende["core:MovingState"] ?? null) === true) { + $faehrt = ($zustaende["core:MovingState"] ?? null) === true; + // "faehrt nicht mehr" ist nicht "steht": die Box meldet das Ende der + // Fahrt, bevor Hoehe und Neigung darauf nachgezogen haben. Erst zwei + // gleiche Ablesungen hintereinander sind der Endstand. + $stand = $faehrt ? null + : strval($zustaende["core:ClosureState"] ?? "") + . "/" . strval($zustaende["core:SlateOrientationState"] ?? ""); + $steht = $stand !== null && $stand === $letzterStand; + $letzterStand = $stand; + + if ($faehrt) { $gestartet = true; continue; } - if ($gestartet || time() - $start >= JALOUSIE_VORLAUF_SEKUNDEN) { + if ($steht && ($gestartet || time() - $start >= JALOUSIE_VORLAUF_SEKUNDEN)) { break; } } @@ -580,13 +580,23 @@ function sendeTahoma($cmd, $werte, $probe) $neigungIndex = 1; } - // Kugelschreiber-Mechanik: ueber NEIGUNG_DIREKT_MAX hinaus rastet sie nur - // um, wenn sie vorher einmal auf 0 % war. Geschickt wird deshalb derselbe - // Befehl zweimal - erst mit Neigung 0, dann mit dem gewuenschten Wert. - // Die Position bleibt dabei stehen, die Jalousie faehrt also nur einmal. - $umweg = $neigungIndex !== null - && is_numeric($parameter[$neigungIndex]) - && $parameter[$neigungIndex] > NEIGUNG_DIREKT_MAX; + // Kugelschreiber-Mechanik: eine Hoehenfahrt setzt die Lamellen um, und aus + // der neuen Raststellung heraus laesst sich die Neigung nicht direkt + // anfahren - die Mechanik muss dafuer erst einmal auf 0 % zurueck. + // Deshalb wird derselbe Befehl zweimal geschickt, erst mit Neigung 0, + // dann mit dem gewuenschten Wert; die Position bleibt dabei stehen, die + // Jalousie faehrt also nur einmal. + // + // Der Umweg gehoert an die Hoehe, nicht an den Neigungswert: setzt ein + // Befehl beides, wird er immer gefahren, auch bei kleinen Winkeln. Wird + // nur die Neigung verstellt, steht die Mechanik schon richtig und es geht + // direkt. Ein Befehl, der nur die Hoehe setzt, hat kein Neigungsziel - + // fuer den gibt es hier nichts zu tun; das Raum-Modal schickt deshalb + // beides zusammen (siehe zeichneBeschattung() in roomControls.php). + // + // Dasselbe steht in transports.py: beide Versender brauchen es. + $umweg = $neigungIndex !== null && $positionIndex !== null + && is_numeric($parameter[$neigungIndex]); $vorstufe = $parameter; if ($umweg) { diff --git a/restricted/roomControls.php b/restricted/roomControls.php index b92aebd..ca3c03c 100644 --- a/restricted/roomControls.php +++ b/restricted/roomControls.php @@ -69,6 +69,37 @@ function kommandoMitParameter($d, $name) return null; } +/** + * Kommando, das Hoehe und Neigung zusammen setzt - null, wenn es keins gibt. + * + * Der Hoehenregler benutzt es statt des reinen "Position"-Kommandos: eine + * Hoehenfahrt rastet die Lamellen um, danach muss die Neigung ueber 0 % + * wieder angefahren werden. Den Umweg faehrt sendeTahoma(), aber nur, wenn + * der Befehl ueberhaupt ein Neigungsziel mitbringt. + */ +function kombiKommando($d) +{ + foreach ($d["commands"] as $c) { + $namen = array_map(function ($p) { return $p["name"]; }, $c["params"]); + sort($namen); + if ($namen === [NEIGUNG_PARAMETER, POSITION_PARAMETER]) { + return $c; + } + } + return null; +} + +/** Der Parameter eines Kommandos mit diesem Namen, null wenn es ihn nicht gibt. */ +function parameterNamens($cmd, $name) +{ + foreach ($cmd["params"] as $p) { + if ($p["name"] === $name) { + return $p; + } + } + return null; +} + /** Kommando mit Parametern rot/gruen/blau - eine Farbe zum Aussuchen. */ function farbKommando($d) { @@ -292,15 +323,22 @@ function reglerFeld($min, $max, $wert, $einheit, $beschriftung = "") * 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) +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 "