Jalousien: Neigung ueber 30 % nur mit Umweg ueber 0 %

Die Aussenjalousien haben eine Kugelschreiber-Mechanik: beim Herunterfahren
stehen die Lamellen bei etwa 30 %. In Richtung 0 % laesst sich von jeder
Stellung aus direkt neigen; darueber hinaus rastet die Mechanik nur um, wenn
sie vorher einmal auf 0 % war. Ein "Neigung 80 %" ohne diesen Umweg bleibt
wirkungslos.

Beide Versender wissen das jetzt: restricted/commands.php fuer die
Bedienung im Raum-Modal, transports.py fuer den Runner. Bei setOrientation
mit einem Ziel ueber 30 % geht erst eine 0 raus, dann wird gewartet, bis die
Box diese 0 auch meldet, und erst danach der eigentliche Wert. Der Umweg
gehoert dorthin und nicht in die Bedienung - sonst muesste ihn jeder kennen,
der eine Jalousie anspricht, auch beim Anlegen einer Automatik.

Gewartet wird auf core:SlateOrientationState und nicht auf
core:MovingState: bei Neigungsfahrten meldet die Box waehrend der ganzen
Bewegung "faehrt nicht". Genau darauf war ich vorher hereingefallen.

Zwei Messungen aus der Erprobung stecken in den Konstanten: von 100 % auf
0 % braucht eine Jalousie gut fuenfzehn Sekunden, deshalb die Grenze von
25 s. Im Versuch ging ein "Neigung 100 %" von 79 % aus nach 14 s durch -
echtes Warten, nicht die Notbremse.

Dabei ein Fehler, den ausgerechnet das Ziel 0 verdeckt haette: die
PHP-Leseroutine liefert null, wenn die Abfrage fehlschlaegt, und
intval(null) ist 0. Die Warteschleife hielt einen fehlgeschlagenen
Lesevorgang damit fuer "Ziel erreicht" und brach sofort ab - der Umweg fand
also gar nicht statt, es sah nur so aus. In der Python-Fassung ist
dieselbe Stelle jetzt ebenfalls ausdruecklich gegen ein fehlendes Feld
abgesichert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-31 19:18:55 +02:00
co-authored by Claude Opus 5
parent 98a1da6cd5
commit 1b9bbc9857
2 changed files with 171 additions and 24 deletions
+65 -5
View File
@@ -32,6 +32,7 @@ id, `aktion` ein Dict mit actor_url, command_url und params (Liste aus
import json
import logging
import re
import time
import urllib3
from datetime import datetime
from urllib.parse import quote
@@ -382,6 +383,24 @@ class TahomaTransport(Transport):
schema = "io://"
# Bis zu dieser Neigung fahren die Aussenjalousien direkt.
#
# Sie haben eine Kugelschreiber-Mechanik: beim Herunterfahren stehen die
# Lamellen bei etwa 30 %. In Richtung 0 % laesst sich von jeder Stellung
# aus direkt neigen; darueber hinaus muss die Mechanik erst einmal auf
# 0 % zurueck, sonst rastet sie nicht um - ein "Neigung 80 %" ohne
# diesen Umweg bleibt wirkungslos.
#
# Dasselbe steht in restricted/commands.php: beide Versender brauchen es.
NEIGUNG_DIREKT_MAX = 30
# So lange wird hoechstens auf das Ende einer Neigungsfahrt gewartet.
# Auf core:MovingState ist dabei kein Verlass - gemessen hat die Box
# waehrend einer Neigungsfahrt kein einziges Mal "faehrt" gemeldet, der
# Zustandswert selbst ist die verlaessliche Auskunft.
# Gemessen braucht eine Jalousie von 100 % auf 0 % gut fuenfzehn Sekunden.
NEIGUNG_WARTE_SEKUNDEN = 25
def __init__(self, requests_modul, pin, token, timeout=10, dry_run=False):
self.requests = requests_modul
self.pin = pin
@@ -432,19 +451,60 @@ class TahomaTransport(Transport):
raise RuntimeError("Kein Tahoma-Token in der config.ini")
# Die Reihenfolge der Parameter ist die aus command_parameters - bei
# setClosureAndOrientation also erst Position, dann Winkel.
befehl = {"name": aktion["command_url"],
"parameters": [self._zahl(p["wert"]) for p in aktion["params"]]}
rumpf = {"label": "AutoAction",
"actions": [{"deviceURL": aktion["actor_url"], "commands": [befehl]}]}
parameter = [self._zahl(p["wert"]) for p in aktion["params"]]
# Kugelschreiber-Mechanik, siehe NEIGUNG_DIREKT_MAX. Der Umweg gehoert
# hierher und nicht in die Automatik - sonst muesste ihn jeder kennen,
# der eine Jalousie anspricht.
umweg = (aktion["command_url"] == "setOrientation"
and len(parameter) == 1
and isinstance(parameter[0], (int, float))
and parameter[0] > self.NEIGUNG_DIREKT_MAX)
if self.dry_run:
logger.info("[dry-run] Tahoma %s", json.dumps(rumpf, ensure_ascii=False))
logger.info("[dry-run] Tahoma %s %s%s", aktion["command_url"], parameter,
" (mit Umweg ueber Neigung 0)" if umweg else "")
return
if umweg:
self._apply(aktion["actor_url"], "setOrientation", [0])
self._warteAufNeigung(aktion["actor_url"], 0)
self._apply(aktion["actor_url"], aktion["command_url"], parameter)
def _apply(self, actor_url, name, parameter):
"""Ein Kommando an die Box schicken."""
rumpf = {"label": "AutoAction",
"actions": [{"deviceURL": actor_url,
"commands": [{"name": name, "parameters": parameter}]}]}
antwort = self.requests.post(self.basis + "/exec/apply", headers=self._kopf(),
data=json.dumps(rumpf), timeout=self.timeout, verify=False)
if antwort.status_code >= 400:
raise RuntimeError("Tahoma antwortete mit %d: %s"
% (antwort.status_code, antwort.text[:120]))
def _warteAufNeigung(self, actor_url, ziel):
"""Wartet, bis die Neigung den Zielwert zeigt."""
for _ in range(self.NEIGUNG_WARTE_SEKUNDEN):
time.sleep(1)
try:
antwort = self.requests.get(
self.basis + "/setup/devices/" + quote(actor_url, safe="") + "/states",
headers=self._kopf(), timeout=self.timeout, verify=False)
for zustand in antwort.json():
if zustand.get("name") != "core:SlateOrientationState":
continue
wert = zustand.get("value")
# Ein fehlendes Feld darf nicht als 0 durchgehen - das
# waere ausgerechnet beim Ziel 0 ein falsches Erfolgs-
# signal.
if wert is not None and int(wert) == ziel:
return True
except Exception as fehler:
logger.debug("Neigung nicht lesbar: %s", fehler)
logger.warning("Neigung von %s hat %s%% nicht innerhalb von %d s erreicht",
actor_url, ziel, self.NEIGUNG_WARTE_SEKUNDEN)
return False
@staticmethod
def _zahl(wert):
"""Tahoma erwartet Zahlen als Zahlen, Text als Text."""