Ende der Fahrt abwarten, Umweg an die Hoehe binden

Zwei Sachen, beide an der Anlage aufgefallen.

Abgebrochen wurde auf "core:MovingState ist false". Das ist nicht
dasselbe wie "steht": die Box meldet das Ende der Fahrt, bevor Hoehe und
Neigung darauf nachgezogen haben, und der festgehaltene Wert war dann der
von kurz davor. Statt einer festen Wartezeit zaehlt jetzt Stabilitaet -
fertig ist es, wenn zwei Abfragen hintereinander dasselbe sagen. Damit
faellt auch die Bestaetigungsrunde weg, sie war genau dieser Gedanke in
schlechter.

Der Umweg der Kugelschreiber-Mechanik hing am Neigungswert (ueber 30 %).
Er gehoert an die Hoehe: eine Hoehenfahrt rastet die Lamellen um, danach
muss die Neigung ueber 0 % wieder angefahren werden - bei jedem Winkel,
nicht erst bei grossen. Wird dagegen nur die Neigung verstellt, steht die
Mechanik schon richtig und es geht direkt. NEIGUNG_DIREKT_MAX faellt
damit weg.

Der Hoehenregler im Raum-Modal schickt deshalb jetzt das kombinierte
Kommando mit der Stellung des Neigungsreglers daneben - ohne
Neigungsziel gaebe es nichts, was der Umweg anfahren koennte.

Geprueft an der Wozi-Schiebetuer: Hoehe von 48 auf 60 bei Neigung 76, und
die 76 standen danach wieder da. Vorher htte die Mechanik sie auf einen
beliebigen Wert umgerastet. Dazu elf Faelle im Browser und sieben gegen
transports.py, darunter "Position+Neigung 10" (Umweg) gegen "nur Neigung
80" (direkt).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 13:35:38 +02:00
co-authored by Claude Opus 5
parent fe73517c17
commit fe8602a79f
3 changed files with 128 additions and 45 deletions
+37 -13
View File
@@ -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);
}
/**
+35 -25
View File
@@ -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) {
+56 -7
View File
@@ -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 "<div class='raum-regler raum-spalte' data-cmd='" . intval($cmd["id"]) . "'"
. " data-param='" . intval($param["id"]) . "' data-rolle='" . $rolle . "'>"
. " data-param='" . intval($param["id"]) . "' data-rolle='" . $rolle . "'" . $mit . ">"
. "<span class='raum-name'>" . htmlspecialchars($titel) . "</span>"
. "<span class='raum-ende'>" . htmlspecialchars($oben) . "</span>"
. "<div class='raum-senkrecht'>" . reglerFeld($min, $max, $wert, "%", $titel) . "</div>"
@@ -424,8 +462,9 @@ function zeichneBeschattung($d)
$auf = kommandoMitUrl($d, ["up"]);
$stop = kommandoMitUrl($d, ["stop"]);
$zu = kommandoMitUrl($d, ["down"]);
$position = kommandoMitParameter($d, "Position");
$neigung = kommandoMitParameter($d, "Neigung");
$position = kommandoMitParameter($d, POSITION_PARAMETER);
$neigung = kommandoMitParameter($d, NEIGUNG_PARAMETER);
$kombi = kombiKommando($d);
// Was das Geraet zuletzt gemeldet hat. Bei Somfy heisst die Lamelle
// "Lamellenausrichtung", die Hoehe schlicht "Position".
@@ -433,7 +472,9 @@ function zeichneBeschattung($d)
$standNeigung = zustandsWert($d, ["Lamellenausrichtung"]);
$status = beschattungsStand($standPos, $standNeigung);
$benutzt = array_filter([$auf, $stop, $zu, $position, $neigung]);
// $kombi gehoert dazu: es steckt jetzt hinter dem Hoehenregler und hat im
// Mehr-Menue nichts mehr verloren.
$benutzt = array_filter([$auf, $stop, $zu, $position, $neigung, $kombi]);
$benutzteIds = array_map(function ($c) { return $c["id"]; }, $benutzt);
$rest = array_values(array_filter($d["commands"], function ($c) use ($benutzteIds) {
return !in_array($c["id"], $benutzteIds, true);
@@ -464,8 +505,16 @@ function zeichneBeschattung($d)
$html .= lamellenBild($d["id"]);
}
if ($position) {
$html .= senkrechterRegler($position, $position["params"][0], $standPos,
"Position", "oben", "unten", "position");
// Kann das Geraet beides zusammen, geht die Hoehe ueber dieses
// Kommando und nimmt die Neigung mit - sonst raste die Mechanik bei
// der Hoehenfahrt um und niemand faehrt sie wieder an.
$hoehenCmd = $kombi ?: $position;
$hoehenParam = $kombi ? parameterNamens($kombi, POSITION_PARAMETER)
: $position["params"][0];
$mitParam = $kombi ? parameterNamens($kombi, NEIGUNG_PARAMETER) : null;
$html .= senkrechterRegler($hoehenCmd, $hoehenParam, $standPos,
"Position", "oben", "unten", "position",
$neigung ? $mitParam : null, "neigung");
}
if ($neigung) {
$html .= senkrechterRegler($neigung, $neigung["params"][0], $standNeigung,