Discovery-Modul fuer die Messwerte des SolarManagers

Der SolarManager legt gut 130 Topics auf den Broker, aber er meldet sich
nicht per Home-Assistant-Discovery. Das MQTT-Modul findet ihn deshalb
nicht, und eine Automatik konnte weder auf den Netzbezug noch auf den
Ladestand der Batterie oder die Temperatur im Puffer reagieren.

Vier feste Geraete mit neun Messwerten, ausgewaehlt nach dem, was das
Realtime-SVG zeigt - von der PV nur die Summe. Alles anzulegen haette die
Auswahl im Editor unbrauchbar gemacht: jeder Wechselrichter einzeln, jeder
Phasenstrom, jede Vorlauftemperatur.

  Netzanschluss              Bezug, Einspeisung
  Stromzaehler Obergeschoss  Leistung
  Solaranlage                PV-Leistung, Batterie-Ladestand
  Pufferspeicher             drei Temperaturen, Heizstab-Leistung

Die beiden Modbus-Zaehler am Gen24 (meter0 am Hausanschluss, meter1 fuer
das Obergeschoss) gibt es nur ueber diesen Weg - sie sprechen sonst mit
niemandem. Die beiden Shelly EM3 bleiben dagegen aussen vor: die stehen
ueber die Home-Assistant-Discovery laengst als Power_EG_EM_0 und
Power_UG_EM_0 in der Tabelle. solarManager/eg waere ohnehin nicht
dasselbe, dort ist die Wallbox im Carport eingerechnet.

Beim Netzanschluss stehen Bezug und Einspeisung statt P_Grid: eine Zahl
mit Vorzeichen liest sich in einer Bedingung falsch herum. Beim
Obergeschoss geht das nicht, der Zaehler meldet Verbrauch negativ - das
steht als Warnung im Modul.

Alles nur zum Lesen. Geschaltet wird am SolarManager nichts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 15:06:31 +02:00
co-authored by Claude Opus 5
parent fe8602a79f
commit 22ecc1b50f
2 changed files with 161 additions and 2 deletions
+11 -2
View File
@@ -20,7 +20,8 @@ from modules.wled_module import WLEDModule
from modules.mqtt_module import MQTTModule from modules.mqtt_module import MQTTModule
from modules.shelly_module import ShellyModule from modules.shelly_module import ShellyModule
from modules.gartenwasser_module import GartenwasserModule from modules.gartenwasser_module import GartenwasserModule
from modules.logic_module import LogicModule #nicht Vergessen neue Module auch in Zeile 546 einzufügen!! from modules.solarmanager_module import SolarManagerModule
from modules.logic_module import LogicModule #nicht Vergessen neue Module auch unten in die Liste "modules" einzufügen!!
# Logging # Logging
logging.basicConfig( logging.basicConfig(
@@ -158,6 +159,13 @@ class Config:
return False return False
return self._get_bool('gartenwasser', 'enable', False) return self._get_bool('gartenwasser', 'enable', False)
# SOLARMANAGER
@property
def solarmanager_enable(self) -> bool:
if not self.config.has_section('solarmanager'):
return False
return self._get_bool('solarmanager', 'enable', False)
# MQTT # MQTT
@property @property
@@ -580,7 +588,8 @@ def main():
MQTTModule(config), MQTTModule(config),
ShellyModule(config), ShellyModule(config),
LogicModule(config), LogicModule(config),
GartenwasserModule(config) GartenwasserModule(config),
SolarManagerModule(config)
] ]
# Jedes Modul durchlaufen # Jedes Modul durchlaufen
@@ -0,0 +1,150 @@
#!/usr/bin/env python3
"""
SolarManager Module
Die wenigen Messwerte aus solarManager/#, die eine Automatik braucht
KEINE Datenbank-Operationen!
"""
import logging
from typing import List, Dict, Tuple
from modules.base_module import BaseModule
logger = logging.getLogger(__name__)
# Wurzel aller Topics, die solarManager.py über mqttClient.publish() abgibt.
# Dasselbe Wort steht dort als Vorgabe im Quelltext; bewusst kein
# Konfigurationseintrag, er müsste in beiden Repositorien gleich lauten.
WURZEL = "solarManager"
# Aus dieser Nachricht wird hier bewusst nur ein kleiner Teil zu Messwerten.
# Sie trägt gut 130 Topics: jeden Wechselrichter einzeln, jede Strangleistung,
# jeden Phasenstrom, jede Vorlauf- und Rücklauftemperatur. Alles anzulegen
# hieße, die Auswahl im Automatik-Editor unbrauchbar zu machen - man sucht
# dann in einer Liste, statt eine Regel zu schreiben. Aufgenommen ist, was
# das Realtime-SVG auf der Solarseite anzeigt, und von der PV nur die Summe.
#
# Nicht dabei sind die beiden Shelly EM3 (Erd- und Untergeschoss). Die melden
# sich per Home-Assistant-Discovery und stehen über das MQTT-Modul längst als
# eigene Geräte in der Datenbank; ein zweiter Satz derselben Zahlen machte die
# Auswahl nur unübersichtlich. Zu beachten: solarManager/eg wäre ohnehin nicht
# dasselbe - dort ist die Wallbox im Carport mit eingerechnet.
#
# Die beiden Modbus-Zähler am Gen24 gibt es dagegen *nur* hier. Sie hängen
# unter 192.168.179.155 an derselben Adresse wie der Wechselrichter und
# sprechen sonst mit niemandem (siehe gatherModbusData.py im SolarManager,
# meter0 und meter1).
#
# Jedes dieser Topics trägt eine blanke Zahl, kein JSON - deshalb hat keiner
# der Messwerte einen value_path. Wer später einen vermisst, trägt hier eine
# Zeile ein; die vollständige Liste steht im Browser unter mqttData auf der
# Solarseite.
#
# Die URL eines Geräts ist das Topic seines ersten Messwerts. Ein eigener
# Namensraum wäre erfunden: der SolarManager kennt keine Geräte, nur Zahlen.
# Sie muss nur eindeutig sein und mit mqtt:// beginnen, damit der Runner den
# MQTT-Transport wählt; abonniert wird über die Topics der Messwerte.
GERAETE = [
{
# meter0 am Gen24, der Zähler am Hausanschluss.
#
# Bezug und Einspeisung statt P_Grid: P_Grid ist eine Zahl mit
# Vorzeichen (negativ heißt Einspeisung), und eine Bedingung darauf
# liest sich falsch herum. Diese beiden sind immer positiv, jeweils
# eine von beiden ist null.
"type": "Stromzähler",
"name": "Netzanschluss",
"messwerte": [
("Bezug", "P_GridCons", "W"),
("Einspeisung", "P_GridFeed", "W"),
],
},
{
# meter1 am Gen24, der Zähler für das Obergeschoss.
#
# ACHTUNG, Vorzeichen: der Zähler meldet Verbrauch negativ, und
# solarManager.py rechnet die Wallbox im Obergeschoss heraus, ohne das
# Vorzeichen zu drehen. Das Realtime-SVG zeigt deshalb -og an. Eine
# Bedingung "das Obergeschoss verbraucht mehr als 500 W" heißt hier
# also "Leistung < -500".
"type": "Stromzähler",
"name": "Stromzähler Obergeschoss",
"messwerte": [("Leistung", "og", "W")],
},
{
"type": "Solaranlage",
"name": "Solaranlage",
"messwerte": [
# Summe aller Stränge. Die einzelnen stehen in P_PVn, einem
# JSON-Feld - für eine Bedingung taugt die Summe besser, und
# genau darum ging es bei der Auswahl.
("PV-Leistung", "P_PV", "W"),
("Batterie-Ladestand", "SOC", "%"),
],
},
{
"type": "Heizung",
"name": "Pufferspeicher",
"messwerte": [
("Temperatur oben", "t_buffT", "°C"),
("Temperatur Mitte", "t_buffM", "°C"),
("Temperatur unten", "t_buffB", "°C"),
# Was der Heizstab gerade in den Puffer schiebt. Nur zum Lesen:
# gestellt wird er über ajax/heater.php, nicht über MQTT.
("Heizstab-Leistung", "pHeat", "W"),
],
},
]
class SolarManagerModule(BaseModule):
"""
SolarManager Modul - Implementiert BaseModule Interface
Wie das Gartenwasser- und das Logic-Modul sucht dieses nichts, sondern
schreibt feste Geräte hin. Der SolarManager meldet sich nicht per
Home-Assistant-Discovery; das MQTT-Modul findet ihn also nicht, und ohne
dieses Modul kann eine Automatik weder auf den Netzbezug noch auf den
Ladestand der Batterie oder die Temperatur im Puffer reagieren.
Alles hier ist zum Lesen. Geschaltet wird am SolarManager nichts: die
Wallbox und die Heizung regelt er selbst, und wo das Web-UI eingreift,
tut es das über eigene Endpunkte (ajax/heater.php, ajax/carSteuerung.php)
und nicht über diese Topics.
"""
def is_enabled(self) -> bool:
"""Prüft ob das SolarManager-Modul aktiviert ist"""
return self.config.solarmanager_enable
def discover(self) -> Tuple[List[Dict], List[Dict]]:
"""
Erzeugt die Geräte aus GERAETE - alle als Sensoren, ohne Kommandos.
Returns:
Tuple (actors, sensors)
"""
logger.info("\n" + "=" * 60)
logger.info("SOLARMANAGER-MESSWERTE WERDEN ERZEUGT")
logger.info("=" * 60)
sensoren = [self._geraet(g) for g in GERAETE]
anzahl = sum(len(g["messwerte"]) for g in GERAETE)
logger.info("%d Geräte mit %d Messwerten", len(sensoren), anzahl)
return [], sensoren
@staticmethod
def _geraet(geraet: Dict) -> Dict:
"""Ein Gerät mit seinen Messwerten."""
states = [{
"name": name,
"url": WURZEL + "/" + topic,
"type": "float",
"unit": einheit,
} for name, topic, einheit in geraet["messwerte"]]
return {
"type": geraet["type"],
"name": geraet["name"],
"url": "mqtt://" + states[0]["url"],
"states": states,
}