#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Wetterstation Module Die eigene Wetterstation am Haus - fest beschrieben, nicht gesucht KEINE Datenbank-Operationen! """ import logging from typing import List, Dict, Tuple from modules.base_module import BaseModule logger = logging.getLogger(__name__) # Wurzel aller Topics. wsMQTTbridge.py im SolarManager hängt am Websocket der # Station (ws://192.168.179.42/ws) und legt jedes Feld der Nachricht einzeln # unter diese Wurzel - retained, deshalb hat ein frisch verbundener Leser # sofort alle Werte. # # Zwei Eigenheiten der Brücke, die man kennen muss: # # * Die trägen Werte gehen nicht bei jeder Nachricht raus. Die Brücke wirft # tempAmb, hum, qff, dewpt, iaq, avgWindspeed und avgWindDeg aus jeder # Nachricht heraus außer jeder einundzwanzigsten. Wind und Böe kommen # dagegen immer. Für eine Automatik macht das keinen Unterschied - der # Runner hält den zuletzt gesehenen Wert -, für die Fehlersuche schon: # eine Außentemperatur, die minutenlang steht, ist kein Ausfall. # # * Der Zeitstempel kommt von der Station selbst, nicht vom Broker. WURZEL = "weatherStation" # Was von der Station als Messwert im Editor auftaucht. # # Nicht dabei sind zwei Topics. "IP" ist keine Messung. Und "QNH [hPa]" ist # der auf Meereshöhe gerechnete Druck nach Standardatmosphäre - er steht bei # uns gut hundert Hektopascal neben qff (1133 gegen 1023) und ist offenbar # nicht auf die Stationshöhe eingestellt. qff rechnet mit der tatsächlichen # Temperatur und ist der Wert, den auch die Wetterkachel auf der Startseite # zeigt. Zwei Drücke nebeneinander, von denen einer falsch ist, wären im # Editor eine Falle. # # Die Windgeschwindigkeiten stehen in km/h - so beschriftet sie # restricted/weather_cards.html, und so passen die Zahlen zur Historie in # solarLog.weatherStation. # # Windrichtung und Böenrichtung sind Grad im Uhrzeigersinn ab Nord, wie beim # Kompass: 0 = aus Norden, 90 = aus Osten, 180 = aus Süden, 270 = aus Westen. # Eine Bedingung darauf ist unhandlich, weil Nord bei 0 und bei 360 liegt - # für "Wind aus Westen" schreibt man zwei Bedingungen (> 225 und < 315). MESSWERTE = [ # (Name im Editor, Topic, Typ, Einheit) ("Außentemperatur", "tempAmb", "float", "°C"), ("Luftfeuchte", "hum", "float", "%"), ("Taupunkt", "dewpt", "float", "°C"), ("Luftdruck", "qff", "float", "hPa"), ("Wind", "windspeed", "float", "km/h"), ("Wind im Mittel", "avgWindspeed", "float", "km/h"), ("Böe", "maxgust", "float", "km/h"), ("Windrichtung", "windDeg", "float", "°"), ("Böenrichtung", "gustDeg", "float", "°"), ("Luftgüte", "iaq", "float", None), # Kein Wert zum Vergleichen, sondern zum Nachsehen: im Editor steht er # hinter dem Namen und verrät, ob die Brücke noch läuft. Genauso hält es # der Regen mit seinem "Stand". ("Stand", "Timestamp", "string", None), ] class WetterstationModule(BaseModule): """ Wetterstation - Implementiert BaseModule Interface Wie das Gartenwasser-, das Logic- und das SolarManager-Modul sucht dieses nichts, sondern schreibt ein festes Gerät hin. Die Station meldet sich nicht per Home-Assistant-Discovery; das MQTT-Modul findet sie also nicht. Warum sie überhaupt hineingehört: für den Hitzeschutz ist die Außentemperatur die bessere Bedingung als die Raumtemperatur. Wenn es drinnen schon warm ist, ist es zum Verschatten zu spät - die Wärme steht dann bereits im Raum und muss wieder heraus. Draußen sieht man sie kommen. Und Wind gab es hier bisher gar nicht: ohne ihn lässt sich für Markise und Sonnensegel kein Sturmschutz bauen. Alles hier ist zum Lesen. Zu schalten gibt es an einer Wetterstation nichts. """ def is_enabled(self) -> bool: """Prüft ob das Wetterstation-Modul aktiviert ist""" return self.config.wetterstation_enable def discover(self) -> Tuple[List[Dict], List[Dict]]: """ Erzeugt das eine Gerät aus MESSWERTE - als Sensor, ohne Kommandos. Returns: Tuple (actors, sensors) """ logger.info("\n" + "=" * 60) logger.info("WETTERSTATION WIRD ERZEUGT") logger.info("=" * 60) states = [{ "name": name, "url": WURZEL + "/" + topic, "type": typ, "unit": einheit, } for name, topic, typ, einheit in MESSWERTE] geraet = { "type": "Wetter", "name": "Wetterstation", # Die URL eines Geräts ist das Topic seines ersten Messwerts, wie # beim SolarManager. Sie muss nur eindeutig sein und mit mqtt:// # beginnen, damit der Runner den MQTT-Transport wählt; abonniert # wird über die Topics der Messwerte. "url": "mqtt://" + states[0]["url"], "states": states, } logger.info("1 Gerät mit %d Messwerten", len(states)) return [], [geraet]