diff --git a/autoActions/autoaction_runner.py b/autoActions/autoaction_runner.py index 9fec062..d7d87ea 100644 --- a/autoActions/autoaction_runner.py +++ b/autoActions/autoaction_runner.py @@ -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) diff --git a/autoActions/transports.py b/autoActions/transports.py index e1e75f1..474b5cd 100644 --- a/autoActions/transports.py +++ b/autoActions/transports.py @@ -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