diff --git a/restricted/deviceDiscovery/device_discovery.py b/restricted/deviceDiscovery/device_discovery.py index eb1798e..3993744 100644 --- a/restricted/deviceDiscovery/device_discovery.py +++ b/restricted/deviceDiscovery/device_discovery.py @@ -21,6 +21,7 @@ from modules.mqtt_module import MQTTModule from modules.shelly_module import ShellyModule from modules.gartenwasser_module import GartenwasserModule from modules.solarmanager_module import SolarManagerModule +from modules.wetterstation_module import WetterstationModule from modules.logic_module import LogicModule #nicht Vergessen neue Module auch unten in die Liste "modules" einzufügen!! # Logging @@ -165,6 +166,13 @@ class Config: if not self.config.has_section('solarmanager'): return False return self._get_bool('solarmanager', 'enable', False) + + # WETTERSTATION + @property + def wetterstation_enable(self) -> bool: + if not self.config.has_section('wetterstation'): + return False + return self._get_bool('wetterstation', 'enable', False) # MQTT @@ -431,7 +439,14 @@ class DatabaseManager: -- Wert zufaellig einmal aenderte. current_value = COALESCE(VALUES(current_value), current_value), possible_values = VALUES(possible_values), - unit = VALUES(unit), + -- Dieselbe Ueberlegung wie bei current_value: ein Modul, + -- das keine Einheit mitliefert, meint "weiss ich nicht" + -- und nicht "loesch die vorhandene". Ohne COALESCE hat + -- ein Suchlauf am 10.09.2026 den Sonnensensoren ihr + -- "Lux" genommen, weil das Tahoma-Modul keine Einheiten + -- kennt - im Automatik-Editor stand danach "877" ohne + -- Angabe, wovon. + unit = COALESCE(VALUES(unit), unit), url = VALUES(url), value_path = VALUES(value_path) """ @@ -504,7 +519,14 @@ class DatabaseManager: -- Siehe insert_actor: NULL heisst "kein Startwert", -- nicht "Messwert loeschen". current_value = COALESCE(VALUES(current_value), current_value), - unit = VALUES(unit), + -- Dieselbe Ueberlegung wie bei current_value: ein Modul, + -- das keine Einheit mitliefert, meint "weiss ich nicht" + -- und nicht "loesch die vorhandene". Ohne COALESCE hat + -- ein Suchlauf am 10.09.2026 den Sonnensensoren ihr + -- "Lux" genommen, weil das Tahoma-Modul keine Einheiten + -- kennt - im Automatik-Editor stand danach "877" ohne + -- Angabe, wovon. + unit = COALESCE(VALUES(unit), unit), url = VALUES(url), value_path = VALUES(value_path), possible_values = VALUES(possible_values) @@ -589,7 +611,8 @@ def main(): ShellyModule(config), LogicModule(config), GartenwasserModule(config), - SolarManagerModule(config) + SolarManagerModule(config), + WetterstationModule(config) ] # Jedes Modul durchlaufen diff --git a/restricted/deviceDiscovery/modules/wetterstation_module.py b/restricted/deviceDiscovery/modules/wetterstation_module.py new file mode 100644 index 0000000..c3743b6 --- /dev/null +++ b/restricted/deviceDiscovery/modules/wetterstation_module.py @@ -0,0 +1,122 @@ +#!/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]