Runner liest nach einem Tahoma-Kommando gleich nach
Bewegt eine Automatik eine Jalousie, stand ihr alter Stand bis zu fuenf Minuten in actor_states - so lange dauert es bis zur naechsten Runde von poll_tahoma. Im Raum-Modal sah man dann eine Jalousie, die es so nicht mehr gab. Der Versandfaden wartet nach dem Kommando jetzt das Ende der Fahrt ab und meldet den Stand des Geraets zurueck; geschrieben wird er wie alles andere im Hauptfaden. Gewartet wird nur auf core:MovingState, ohne Zielwerte - welche das waeren, weiss an dieser Stelle niemand, und nicht jedes Kommando loest eine Fahrt aus. Dass der Faden dabei steht, ist gewollt: das naechste Kommando an dieselbe Jalousie darf ohnehin erst nach der Fahrt kommen. Transport.nachlesen() gibt es fuer alle, liefert aber nur bei Tahoma etwas. MQTT-Geraete melden sich von selbst, HTTP und WLED werden jede Minute gefragt. Nebenbei: das Lesen aller Zustaende eines Geraets steckt jetzt in _zustaende(), die Zuordnung zu den Messwert-Nummern in _zuordnen(). zustaende_lesen() und die Warteschleife der Kugelschreiber-Mechanik benutzen beide dasselbe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -497,6 +497,7 @@ class Versand:
|
||||
def __init__(self):
|
||||
self.warteschlangen = {} # actor_url -> Queue
|
||||
self.ergebnisse = queue.Queue()
|
||||
self.messwerte = queue.Queue()
|
||||
self.laeuft = True
|
||||
|
||||
def einreihen(self, automation_id, beschreibung, transport, auftrag):
|
||||
@@ -518,6 +519,15 @@ class Versand:
|
||||
try:
|
||||
transport.senden(auftrag)
|
||||
self.ergebnisse.put((automation_id, beschreibung, None))
|
||||
# Was das Geraet danach meldet, gleich mitnehmen. Bei Tahoma
|
||||
# laegen sonst bis zu fuenf Minuten zwischen der Fahrt und
|
||||
# dem neuen Stand in der Tabelle; die anderen Transporte
|
||||
# geben hier nichts zurueck. Dass das den Faden aufhaelt, ist
|
||||
# gewollt: das naechste Kommando an dieselbe Jalousie darf
|
||||
# ohnehin erst nach der Fahrt kommen.
|
||||
werte = transport.nachlesen(auftrag["actor_url"])
|
||||
if werte:
|
||||
self.messwerte.put(werte)
|
||||
except Exception as fehler:
|
||||
self.ergebnisse.put((automation_id, beschreibung, str(fehler)))
|
||||
finally:
|
||||
@@ -532,6 +542,15 @@ class Versand:
|
||||
except queue.Empty:
|
||||
return fertig
|
||||
|
||||
def messwerte_abholen(self):
|
||||
"""Was die Versandfaeden nach ihren Kommandos abgelesen haben."""
|
||||
neu = {}
|
||||
while True:
|
||||
try:
|
||||
neu.update(self.messwerte.get_nowait())
|
||||
except queue.Empty:
|
||||
return neu
|
||||
|
||||
def offen(self):
|
||||
return sum(s.unfinished_tasks for s in self.warteschlangen.values())
|
||||
|
||||
@@ -767,6 +786,7 @@ class Runner:
|
||||
if billig or auch_geraete:
|
||||
neu.update(transport.zustaende_lesen())
|
||||
neu.update(self.sammler.abholen())
|
||||
neu.update(self.versand.messwerte_abholen())
|
||||
if neu:
|
||||
self.werte.update(neu)
|
||||
self.werte_zurueckschreiben(neu)
|
||||
|
||||
+71
-21
@@ -108,6 +108,18 @@ class Transport:
|
||||
def senden(self, aktion):
|
||||
raise NotImplementedError
|
||||
|
||||
def nachlesen(self, actor_url):
|
||||
"""
|
||||
Was das Geraet direkt nach einem Kommando meldet.
|
||||
|
||||
Fuer die meisten Transporte nichts: MQTT-Geraete melden sich von
|
||||
selbst, HTTP und WLED werden ohnehin jede Minute gefragt. Nur bei
|
||||
Tahoma lohnt es sich - dort liegen fuenf Minuten zwischen zwei
|
||||
Abfragen, und eine Jalousie steht so lange mit ihrem alten Stand in
|
||||
der Tabelle.
|
||||
"""
|
||||
return {}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# MQTT
|
||||
@@ -446,27 +458,70 @@ class TahomaTransport(Transport):
|
||||
"""
|
||||
self.kombigeraete = set(urls)
|
||||
|
||||
def _zustaende(self, actor_url):
|
||||
"""
|
||||
Alle Zustaende eines Geraets, oder None wenn die Box nicht antwortet.
|
||||
|
||||
Ein leeres Dict waere hier falsch: es hiesse "kennt keine Zustaende"
|
||||
und liesse eine Warteschleife durchlaufen, statt sie warten zu lassen.
|
||||
"""
|
||||
try:
|
||||
antwort = self.requests.get(
|
||||
self.basis + "/setup/devices/" + quote(actor_url, safe="") + "/states",
|
||||
headers=self._kopf(), timeout=self.timeout, verify=False)
|
||||
return {z["name"]: z.get("value") for z in antwort.json()}
|
||||
except Exception as fehler:
|
||||
logger.debug("Tahoma %s nicht erreichbar: %s", actor_url, fehler)
|
||||
return None
|
||||
|
||||
def _zuordnen(self, actor_url, zustaende):
|
||||
"""Die gelesenen Felder den Messwert-Nummern dieses Geraets zuordnen."""
|
||||
werte = {}
|
||||
for s in self.states:
|
||||
if s["actor_url"] == actor_url and s["state_url"] in zustaende:
|
||||
werte[s["id"]] = str(zustaende[s["state_url"]])
|
||||
return werte
|
||||
|
||||
def zustaende_lesen(self):
|
||||
if not self.token:
|
||||
return {}
|
||||
werte = {}
|
||||
nach_geraet = {}
|
||||
for s in self.states:
|
||||
nach_geraet.setdefault(s["actor_url"], []).append(s)
|
||||
for geraet, states in nach_geraet.items():
|
||||
try:
|
||||
antwort = self.requests.get(
|
||||
self.basis + "/setup/devices/" + quote(geraet, safe="") + "/states",
|
||||
headers=self._kopf(), timeout=self.timeout, verify=False)
|
||||
zustaende = {z["name"]: z.get("value") for z in antwort.json()}
|
||||
except Exception as fehler:
|
||||
logger.debug("Tahoma %s nicht erreichbar: %s", geraet, fehler)
|
||||
for geraet in {s["actor_url"] for s in self.states}:
|
||||
zustaende = self._zustaende(geraet)
|
||||
if zustaende is None:
|
||||
continue
|
||||
for s in states:
|
||||
if s["state_url"] in zustaende:
|
||||
werte[s["id"]] = str(zustaende[s["state_url"]])
|
||||
werte.update(self._zuordnen(geraet, zustaende))
|
||||
return werte
|
||||
|
||||
def nachlesen(self, actor_url):
|
||||
"""
|
||||
Nach einem Kommando abwarten, bis das Geraet steht, und dann seinen
|
||||
Stand melden - statt ihn bis zur naechsten Runde (poll_tahoma, fuenf
|
||||
Minuten) alt in der Tabelle stehen zu lassen.
|
||||
|
||||
Gewartet wird nur auf core:MovingState, ohne Zielwerte: welche das
|
||||
waeren, weiss hier niemand, und ein Kommando muss auch keine Fahrt
|
||||
ausloesen. Der Vorlauf faengt die Traegheit der Box ab, danach gilt
|
||||
ein "faehrt nicht" als Stillstand.
|
||||
"""
|
||||
if not self.token:
|
||||
return {}
|
||||
start = time.time()
|
||||
gestartet = False
|
||||
zustaende = None
|
||||
while time.time() - start < self.JALOUSIE_WARTE_SEKUNDEN:
|
||||
time.sleep(2)
|
||||
gelesen = self._zustaende(actor_url)
|
||||
if gelesen is None:
|
||||
continue
|
||||
zustaende = gelesen
|
||||
if zustaende.get("core:MovingState") is True:
|
||||
gestartet = True
|
||||
continue
|
||||
if gestartet or time.time() - start >= self.JALOUSIE_VORLAUF_SEKUNDEN:
|
||||
break
|
||||
return self._zuordnen(actor_url, zustaende) if zustaende else {}
|
||||
|
||||
def senden(self, aktion):
|
||||
if not self.token:
|
||||
raise RuntimeError("Kein Tahoma-Token in der config.ini")
|
||||
@@ -547,13 +602,8 @@ class TahomaTransport(Transport):
|
||||
gestartet = False
|
||||
while time.time() - start < self.JALOUSIE_WARTE_SEKUNDEN:
|
||||
time.sleep(2)
|
||||
try:
|
||||
antwort = self.requests.get(
|
||||
self.basis + "/setup/devices/" + quote(actor_url, safe="") + "/states",
|
||||
headers=self._kopf(), timeout=self.timeout, verify=False)
|
||||
z = {x.get("name"): x.get("value") for x in antwort.json()}
|
||||
except Exception as fehler:
|
||||
logger.debug("Zustand nicht lesbar: %s", fehler)
|
||||
z = self._zustaende(actor_url)
|
||||
if z is None:
|
||||
continue
|
||||
if z.get("core:MovingState") is True:
|
||||
gestartet = True
|
||||
|
||||
Reference in New Issue
Block a user