Einbindung in Home Assistant - z-master42/solarflow GitHub Wiki

SolarFlow in Home Assistant einbinden

Einleitung

Zendure betreibt für den Abruf von Informationen für die Produkte SuperBase V, Satellite Battery und SolarFlow einen MQTT-Broker. Dies stellt aktuell auch die einzige offizielle Möglichkeit dar, Informationen außerhalb der von Zendure bereitgestellten App abzugreifen. Da Zendure die Daten über einen eigenen Broker auspielt läuft die Verbindung zwangsweise durchs Internet. Eine rein lokale Steuerung ist aktuell und offiziell noch nicht möglich. MQTT ist ein offenes Netzwerkprotokoll für eine Maschine-zu-Maschine-Kommunikation. Dabei stehen in der Regel mehrere Clients in Verbindung zu einem Broker. Die ausgetauschten Nachrichten werden hierachisch abgestuft durch Topics definiert. Um entsprechende Informationen abzugreifen oder Befehle zu senden muss das entprechende Topic abonniert werden.

Vorbereitung

Voraussetzung zur Nutzung ist ein Account bei Zendure (den ja jeder dadurch haben sollte, dass er sich die App zur Steuerung des SolarFlow installiert hat). Für den Abruf der MQTT-Daten des eigenen SolarFlow benötigt man im weiteren einen appKey und ein appSecret. Um diese beiden Werte zu erhalten, benötigt ihr, neben der Emailadresse mit der ihr euch bei Zendure registriert habt, die Seriennummer eures PV-Hubs. Ich habe zum Abruf das Kommandozeilen-Tool curl verwendet.

Vorgehen auf einem Microsoft Betriebssystem

  • Öffnet mit Windows-Taste + R die Eingabeaufforderung.

  • Gebt cmd ein.

  • Gebt folgenden Befehl in der Kommandozeile ein:

    Regionseinstellung in der Zendure-App auf "Global"

    curl -i -v --json "{'snNumber': 'EureHubSeriennummer', 'account': 'EureEmailadresse'}" https://app.zendure.tech/v2/developer/api/apply
    

    Regionseinstellung in der Zendure-App auf einem "Europäischen Land"

    curl -i -v --json "{'snNumber': 'EureHubSeriennummer', 'account': 'EureEmailadresse'}" https://app.zendure.tech/eu/developer/api/apply
    
  • Zuvor habt ihr natürlich eure Seriennummer und eure verwendete Emailadresse anstelle der Platzhalter eingetragen.

Vorgehen auf einem Linux Betriebssystem

  • Öffnet mit Strg+Alt+T ein Terminalfenster.

  • Gebt folgenden Befehl in der Kommandozeile ein:

    Regionseinstellung in der Zendure-App auf "Global"

    curl -i -X POST -H 'Content-Type: application/json' -d '{"snNumber": "EureHubSeriennummer", "account": "EureEmailadresse"}' https://app.zendure.tech/v2/developer/api/apply
    

    Regionseinstellung in der Zendure-App auf einem "Europäischen Land"

    curl -i -X POST -H 'Content-Type: application/json' -d '{"snNumber": "EureHubSeriennummer", "account": "EureEmailadresse"}' https://app.zendure.tech/eu/developer/api/apply
    
  • Zuvor habt ihr natürlich eure Seriennummer und eure verwendete Emailadresse anstelle der Platzhalter eingetragen.

Antwort

Habt ihr keine Fehler gemacht, sollte eine Antwort wie folgt erscheinen:

{"code":200,"success":true,"data":{"appKey":"EuerAppKey","secret":"EuerAppSecret","mqttUrl":"mqtt.zen-iot.com","port":1883},"msg":"Successful operation"}

Anstelle der Platzhalter findet ihr dann euren appKey und euer appSecret. Beides sind Buchstaben-Zahlen-Kombinationen.

Einrichtung von MQTT in Home Assistant

Je nachdem wie weit ihr euch in Home Assistant schon ausgetobt habt, gibt es nun mehrere Wege zum Ziel. Ich empfehle hier aber grundsätzlich die Variante mit einem eigenem MQTT-Broker und manuell angelegten Entitäten (unter 2.).

  1. Option: Noch kein MQTT-Gerät mit Home Assistant verbunden

    Wenn dies eure erste Berührung mit MQTT ist, geht die Sache recht schnell.

    • Fügt über Einstellungen - Geräte & Dienste eine neue Integration hinzu. Sucht dort nach MQTT und wählt die ohne irgendwelche weiteren Bezeichnungen.

    • Der Benutzername ist euer appKey und das Passwort euer appSecret. Die URL des Brokers und der Port wurden euch ebenfalls mit der o.a. Antwort geliefert: mqtt.zen-iot.com oder mqtt-eu.zen-iot.com mit Port 1883.

    • Damit auch Daten reinkommen müsst ihr wie eingangs erwähnt, noch ein Topic abonnieren. Dies geschieht hier in dem ihr auf der Konfigurationsseite Enable Discovery aktiviert und als Discovery prefix euren appKey eintragt.

      grafik

      Hinweis: Es werden euch hierdurch nur Entitäten/Sensoren angelegt. Es wird kein dediziertes Gerät angelegt, welches die Entitäten/Sensoren enhält.

  2. Option: Ihr nutzt bereits einen MQTT-Broker in Home Assistant

    Wenn ihr bereits Erfahrungen mit MQTT gesammelt habt, weil ihr z. B. Sensoren oder Steckdosen darüber mit Home Assistant verbunden habt, ist die Wahrscheinlichkeit sehr groß, dass ihr das Mosquitto broker-Addon installiert und die MQTT-Integration für die Verbindung mit diesem konfiguriert habt.

    Problem: In Home Assistant kann nur eine MQTT-Integration installiert werden.

    Lösung: Ihr müsst eine Brücke zum MQTT-Broker von Zendure bauen. Hierbei gibt es dann zudem zwei Möglichkeiten des weiteren Vorgehens. Entweder ist lasst euch durch Home Assistant alle verfügbaren Sensoren automatisch anlegen oder ihr fügt diese manuell hinzu. Direkt vorweg: Ich habe meine manuell hinzugefügt, so hatte ich die Möglichkeit diese dabei noch anzupassen. Zudem kam es vermehrt vor, dass in Home Assistanten mit der Zeit Entitäten mehrfach angelegt worden sind.

    1. Check vorweg

      • Um zu überprüfen, ob seitens des Zendure-Brokers überhaupt Daten eures SolarFlows ausgespielt werden, bietet sich das Programm MQTT-Explorer an, welches es für die gängisten Betriebssysteme gibt.

      • Erstellt dort eine neue Connection mit euren Zugangsdaten (appKey, appSecret, mqttUrl) wie im Screenshot.

        grafik

      • Unter Advanced (Button) müsst ihr dann noch euren appKey als Topic abonnieren, also als neue Subscription hinzufügen --> appKey/#.

        grafik

      • Es sollten dann ziemlich zeitig Werte reinkommen. Wenn ihr alles aufklappt sieht es ungefähr so aus:

        grafik Unter der Broker-Adresse erscheint ein Eintrag der wie euer appKey lautet (alles in 🔴). Darunter gibt es drei weitere Einträge; switch, sensor, und einen der euren SolarFlow bezeichnet. Wir nennen ihn daher ab jetzt deviceID (alles in 🔵).

        • switch enthält als Einträge die Bauanleitungen für die bisher verfügbaren Schalter.
        • sensor enthält als Einträge die Bauanleitungen für die bisher verfügbaren Sensoren.
        • deviceID enthält als Eintrag die Status der Sensoren, jedoch immer nur diejenigen, deren Wert sich geändert hat.

        Aktuell verfügbar sind folgende Sensoren und Schalter (die Liste erhebt keinen Anspruch auf Vollständigkeit):

        Feld Beschreibung Geräteklasse Automatische Erkennung [Hub 1200] (2. iii.)
        electricLevel Ladestand über alle Batterien in % Sensor Ja
        remainOutTime Verbleibende Zeit bis Batterien entladen in min. Ob die Entladegrenze berücksichtigt wird, ist mir nicht bekannt Sensor Ja
        remainInputTime Verbleibende Zeit bis Batterien geladen in min. Ob die Ladegrenze berücksichtigt wird, ist mir nicht bekannt Sensor Ja
        socSet (Obere) Ladegrenze in % * 10 Sensor Ja
        inputLimit Maximale Leistungsaufnahme aus dem Netz (z. B. beim AC-Laden mit dem Hyper 2000 oder Ace 1500) Sensor Nein
        outputLimit Maximale Leistungsabgabe ans "Haus" (Obergrenze) in W Sensor Ja
        solarInputPower Aktuelle Solarleistung über alle Eingänge in W Sensor Ja
        packInputPower Aktuelle Entladeleistung der Batterien in W Sensor Ja
        outputPackPower Aktuelle Ladeleistung der Batterien in W Sensor Ja
        outputHomePower Aktuelle Leistungsabgabe ans "Haus" in W Sensor Ja
        packNum Anzahl angeschlossener Batterien Sensor Ja
        packState Status über alle Batterien (0: Standby, 1: Mind. eine Batterie wird geladen, 2: Mind. eine Batterie wird entladen) Sensor Ja
        buzzerSwitch Bestätigungston Ein oder Aus Schalter Ja
        masterSwitch Funktion nicht bekannt. Steht bei mir immer auf Ein Schalter Ja
        wifiState WLAN-Verbindungsstatus (1: Verbunden, 0: Getrennt) Sensor Nein
        hubState Verhalten des Hub/Hyper bei Erreichen der Entladegrenze (0: Ausgabe beenden und Standby 1: Ausgabe beenden und Ausschalten) Sensor Nein
        inverseMaxPower Maximal zulässige Abgabeleistung ans "Haus" / Gesetzliche Obergrenze in W Sensor Nein
        packData Sammel-Topic über die Batteriewerte, filterbar über die jeweilige Batterieseriennummer (maxVol: Spannung der Zelle mit der höchsten Spannung in V * 100, minVol: Spannung der Zelle mit der niedrigsten Spannung in V * 100, maxTemp: Temperatur der Zelle mit der höchsten Temperatur in K, socLevel: Ladestand in %, sn: Seriennummer) Sensor Ja
        solarPower1 Aktuelle Solarleistung Eingang PV 1 in W Sensor Ja
        solarPower2 Aktuelle Solarleistung Eingang PV 2 in W Sensor Ja
        passMode Bypass-Modus (0: Automatisch, 1: Immer aus, 2: Immer ein) Sensor Ja
        autoRecover Automatisches Rücksetzen des Bypass-Modus (0: Aus 1: Ein) Schalter Ja
        sn Seriennummer des Hub/Hyper Sensor Nein
        heatState Batterieheizung AB2000 und S-Serie (0: Aus, 1: Ein) Sensor Nein
        acMode AC-Modus (2: Entladen, 1: Laden) Sensor Nein

        Spezielle Hyper 2000 und Ace 1500 Sensoren und Schalter:

        Feld Beschreibung Geräteklasse Automatische Erkennung (2. iii.)
        gridInputPower Leistungsbezug aus dem Netz in W Sensor Ja
        hyperTmp Temperatur des Hyper in K * 10 Sensor Nein
        acOutputPower (Hyper 2000) Funktion nicht bekannt. Vermutlich Leistungsabgabe in Verbindung mit dem Zubehör Off-Grid-Steckdosenleiste in W Sensor Ja
        dcOutputPower (Ace 1500) Vermutlich Leistungsabgabe USB und XT-60 in W Sensor Ja
        acSwitch Vermutlich Leistungsabgabe über vorhandene Steckdosen (Ace 1500) bzw. die Off-Grid-Steckdosenleiste Schalter Ja
        dcSwitch (Ace 1500) Vermutlich Leistungsabgabe USB und XT-60 Schalter Ja

        Hinweis: Die Sensoren, welche als Schalter angeben sind, nutzen dies nur zur Informationsdarstellung. Über den Schalter lässt sich die Funktion nicht ein- oder ausschalten. Zudem sind alle Sensoren aus Sicht des Hubs benannt. Dies kann schon mal zu Verwirrungen führen, denn outputPackPower ist die Leistung, die in die Batterien geht und packInputPower die Leistung, die aus den Batterien entnommen wird.

    2. Gleicher Start

      • Der Anfang ist bei beiden Möglichkeiten gleich.

      • Zunächst muss die Brücke zum Zendure-Broker aufgebaut werden. Hiefür müsst ihr auf das share-Verzeichnis eures Home Assistant zugreifen. Über das File Editor-Addon ist dies z. B. nicht möglich. Über dieses habt ihr nämlich nur Zugriff auf das config-Verzeichnis. Ihr könnt dies über das Studio Code Server-Addon, das Samba share-Addon oder das Terminal & SSH-Addon machen. Die einzelnen Wege erkläre ich nachfolgend. Erstellt zunächst eine Datei mit folgendem Inhalt. Wie die Datei heißt ist völlig egal und welchen Editor oder welches Textverarbeitungsprogramm ihr dafür nutzt ebenfalls.

      • Erstellt eine neue Textdatei.

      • Fügt folgenden Inhalt ein:

        connection zendure-broker
        address <mqttUrl>:1883
        remote_username <appKey>
        remote_password <appSecret>
        remote_clientid <appKey>
        topic <appKey>/# in
        
      • Alles zwischen <> ersetzt ihr inklusive, der <> natürlich wieder durch eure eigenen Daten. Es dürfen keine <> mehr vorhanden sein.

      1. Studio Code Server-Addon

        • Installiert das Studio Code Server-Addon im Addon-Bereich und wechselt danach auf die Konfigurationsseite des Addons. Klickt dort auf Nicht verwendete optionale Konfigurationsoptionen einblenden und tragt unter config_path /root ein. Klickt auf Speichern und startet anschließend das Addon und öffnet die Benutzeroberfläche.
        • In der Benutzeroberfläche geht ihr links oben über das burger-Menu -> File -> Open Folder... oder nutzt alternativ die Tastenkombination Strg + K und dann Strg + O.
        • In dem erscheinenden Auswahlmenü sucht ihr share und klickt anschließend auf OK.
        • Das Verzeichnis wird vermutlich leer sein. Erstellt über das entsprechende Icon einen neuen Ordner folder und nennt in mosquitto und erstellt in diesem neuen Ordner eine neue Datei file mit dem Namen zendure.conf.
        • Kopiert nun den Inhalt aus eurer zuvor angelegten Textdatei in die neue Datei.
        • Wollt ihr, dass Home Assistant euch die Sensorentitäten automatisch anlegt übernehmt noch die weiteren Zeilen aus iii. .
        • Sämtliche Änderungen die ihr in der Benutzeroberfläche macht, werden direkt gespeichert.
      2. Samba share-Addon

        • Installiert das Samba share-Addon im Addon-Bereich und wechselt danach auf die Konfigurationsseite des Addons. Vergebt dort einen Username und ein Password klickt auf Speichern. Startet anschließend das Addon.
        • Fügt im Datei-Explorer eures Betriebssystems eine neue Netzwerkadresse hinzu und gebt als Pfad die IP-Adresse eures Home Assistant gefolgt von \share ein. Zum Beispiel \\192.168.1.42\share.
        • Meldet euch mit den im Addon vergebenen Benutzernamen und Passwort an.
        • Das Verzeichnis ist vermutlich leer. Erstellt eine neuen Ordner namens mosquitto und darin eine neue Datei namens 'zendure.conf'.
        • Kopiert nun den Inhalt aus eurer zuvor angelegten Textdatei in die neue Datei.
        • Wollt ihr, dass Home Assistant euch die Sensorentitäten automatisch anlegt übernehmt noch die weiteren Zeilen aus iii. .
        • Speichert die Datei.
      3. Terminal & SSH-Addon

        • Installiert das Terminal & SSH-Addon im Addon-Bereich, startet es danach und öffnet die Benutzeroberfläche.
        • Wechselt mit dem Befehl cd share ins share-Verzeichnis.
        • Überprüft mit ls ob es schon einen Ordner mosquitto gibt. Erscheint nach dem Befehl einfach eine neue Eingabezeile, existiert der Ordner noch nicht, ansonsten erscheint ein Eintrag mosquitto.
        • Ist der Ordner nicht vorhanden, erstellt ihn mit mkdir mosquitto.
        • Wechselt mit cd mosquitto in das Verzeichnis.
        • Nun erstellen wir eine neue Datei und bearbeiten diese mit dem Editor nano. Gebt dazu nano zendure.conf ein.
        • Kopiert nun den Inhalt aus eurer zuvor angelegten Textdatei und fügt ihn in das Terminalfenster ein. Achtung: Hierfür müsst ihr die Umschalttaste eurer Tastatur gedrückt halten und mit der rechten Maustaste ins Terminalfenster klicken und dort Einfügen auswählen. Nun sollte der Inhalt eurer Textdatei im Terminalfenster stehen.
        • Wollt ihr, dass Home Assistant euch die Sensorentitäten automatisch anlegt überspringt die weiteren Schritte zunächst und macht bei iii. weiter.
        • Schließt mit Strg + X den nano-Editor.
        • Bestätigt die am unteren Terminalrand erscheinende Frage mit y.
        • Bestätigt die nächste Frage mit der Eingabetaste.

      • In der Konfiguration des Mosquitto-Addons überprüft ihr jetzt noch ob unter Customize active auf true gesetzt ist.
      • Abschließend ist das Mosquitto-Addon neu zu starten.
      • Im Log sollte dann ein Eintrag ähnlich Connecting bridge external-bridge (mqtt.zen-iot.com:1883) auftauchen. Ggf. müsst ihr das Log mehrmals aktualisieren (Geduld). Sollte hingehen irgendwas mit Timeout oder so kommen, einfach das Addon noch mal neu starten.
    3. Automatische Einfügung

      Damit Home Assistant die Sensoren und Schalter automatisch erstellt, muss es wissen wie diese aufgebaut sind. Zudem gibt es seitens Home Assistant Vorgaben, wie die Topics aussehen müssen damit dies funktioniert. Ergänzt hierzu in euer zendure.conf einfach noch zwei weitere Zeilen mit:

      topic # in 0 homeassistant/sensor/<appKey>/ <appKey>/sensor/device/
      topic # in 0 homeassistant/switch/<appKey>/ <appKey>/switch/device/
      

      Hinweis: Es werden euch hierdurch nur Entitäten/Sensoren angelegt. Es wird kein dediziertes Gerät angelegt, welches die Entitäten/Sensoren enhält. D. h. ihr findet die einzelnen Entitäten nur wenn ihr diese über ihren jeweiligen Namen sucht. Zudem kam es, wie bereits erwähnt, vermehrt dazu, dass die Entitäten mit der Zeit mehrfach angelegt wurden.

      Macht nun oben da weiter, wo ihr eben hierhin abgesprungen seit.

    4. Manuelle Einfügung

      Das manuelle, also händische, Anlegen von Entitäten erfolgt in Home Assistant über die configuration.yaml. Diese liegt im config-Verzeichnis. Auf dieses könnt ihr ebenso via Samba zugreifen. Genau so gut ist aber der Weg über das File Editor Addon oder das Studio Code Server Addon. In diesen wird der eingefügte Code zur besseren Lesbarkeit farblich markiert und sollten Formatierungs- oder Syntaxfehler vorliegen wird dies direkt angezeigt. An für sich könnt ihr alles in die configuration.yaml schreiben. Mit der Zeit wird diese dann aber etwas unübersichtlich, da alles untereinander in einer Textdatei steht. Schöner ist es hier, entsprechende Konfigurationen in neue Dateien auszulagern.

      • Öffnet eure configuration.yaml.

      • Ergänzt unter dem nachstehenden Block, welcher sich ziemlich am Anfang der Datei befindet eine neue Zeile mit: mqtt: !include mqtt.yaml.

        group: !include groups.yaml
        automation: !include automations.yaml
        script: !include scripts.yaml
        scene: !include scenes.yaml

        Hinweis: Der Block muss bei euch nicht genau so aussehen. Hier ist das ebenfalls davon abhängig, wie weit ihr euch in Home Assistant schon ausgetobt habt. Wenn ich das richtig in Erinnerung habe, sollte aber wenigstens eine !include-Zeile schon vorhanden sein.

      • Erstellt eine neue Datei mqtt.yaml und fügt nachstehenden Inhalt ein.

      • Alles zwischen <> ersetzt ihr inklusive, der <> natürlich wieder durch eure eigenen Daten. Es dürfen keine <> mehr vorhanden sein.

          # Die finalen Entitätsnamen setzen sich aus dem Sensornamen und dem Gerätenamen zusammen
          # Beim ersten Sensor hier also SolarFlow Hub State (sensor.solarflow_hub_state)
          sensor:
            - name: "Hub State"
              unique_id: "<deviceID>hubState"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{% if value_json.hubState == 0 %}Standby{%elif value_json.hubState == 1 %}Ausschalten{%endif%}"
              icon: mdi:vector-link
              device_class: "enum"
              options: ['Standby','Ausschalten']
              # Der gesamte device-Block muss nur einmal angegeben werden, daher ist bei den nachfolgenden Entitäten nur noch identifiers vorhanden
              device: 
                name: "SolarFlow" 
                identifiers: "<EurePVHubSeriennummer>"
                manufacturer: "Zendure"
                # Ggf. an euer Modell anpassen
                model: "SmartPV Hub 1200 Controller"
        
            - name: "Solar Input Power"
              unique_id: "<deviceID>solarInputPower"
              state_topic: "<appKey>/<deviceID>/state"
              unit_of_measurement: "W"
              device_class: "power"
              value_template: >
                {% if states('sensor.solarflow_solar_input_power') not in ['unknown'] %}  {# Muss ggf. an euren Entitätsnamen angepasst werden. #}
                  {{ int(value_json.solarInputPower, 0) }}
                {% else %}
                  {{ int(0) }}
                {% endif %}
              state_class: "measurement"
              icon: mdi:solar-panel
              device: 
                identifiers: "<EurePVHubSeriennummer>"
              
            - name: "Pack Input Power"
              unique_id: "<deviceID>packInputPower"
              state_topic: "<appKey>/<deviceID>/state"
              unit_of_measurement: "W"
              device_class: "power"
              value_template: >
                {% if states('sensor.solarflow_pack_input_power') not in ['unknown'] %} {# Muss ggf. an euren Entitätsnamen angepasst werden. #}
                  {{ int(value_json.packInputPower, 0) }}
                {% else %}
                  {{ int(0) }}
                {% endif %}
              state_class: "measurement"
              icon: mdi:battery-arrow-down-outline
              device: 
                identifiers: "<EurePVHubSeriennummer>"
              
            - name: "Output Pack Power"
              unique_id: "<deviceID>outputPackPower"
              state_topic: "<appKey>/<deviceID>/state"
              unit_of_measurement: "W"
              device_class: "power"
              value_template: >
                {% if states('sensor.solarflow_output_pack_power') not in ['unknown'] %} {# Muss ggf. an euren Entitätsnamen angepasst werden. #}
                  {{ int(value_json.outputPackPower, 0) }}
                {% else %}
                  {{ int(0) }}
                {% endif %}
              state_class: "measurement"
              icon: mdi:battery-arrow-up-outline
              device: 
                identifiers: "<EurePVHubSeriennummer>"
              
            - name: "Output Home Power"
              unique_id: "<deviceID>outputHomePower"
              state_topic: "<appKey>/<deviceID>/state"
              unit_of_measurement: "W"
              device_class: "power"
              value_template: >
                {% if states('sensor.solarflow_output_home_power') not in ['unknown'] %} {# Muss ggf. an euren Entitätsnamen angepasst werden. #}
                  {{ int(value_json.outputHomePower, 0) }}
                {% else %}
                  {{ int(0) }}
                {% endif %}
              state_class: "measurement"
              icon: mdi:home-import-outline
              device: 
                identifiers: "<EurePVHubSeriennummer>"
              
            - name: "Output Limit"
              unique_id: "<deviceID>outputLimit"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.outputLimit | int }}"
              unit_of_measurement: "W"
              icon: mdi:home-lightning-bolt-outline
              device: 
                identifiers: "<EurePVHubSeriennummer>"
                
            - name: "Input Limit"
              unique_id: "<deviceID>inputLimit"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.inputLimit | int }}"
              unit_of_measurement: "W"
              device: 
                identifiers: "<EurePVHubSeriennummer>"
            
            - name: "Remain Out Time"
              unique_id: "<deviceID>remainOutTime"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.remainOutTime | int }}"
              device_class: "duration"
              unit_of_measurement: "min"
              device: 
                identifiers: "<EurePVHubSeriennummer>"
            
            - name: "Remain Input Time"
              unique_id: "<deviceID>remainInputTime"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.remainInputTime | int }}"
              device_class: "duration"
              unit_of_measurement: "min"
              device: 
                identifiers: "<EurePVHubSeriennummer>"
            
            - name: "Pack State"
              unique_id: "<deviceID>packState"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{% if value_json.packState == 0 %}Standby{%elif value_json.packState == 1 %}Laden{%elif value_json.packState == 2 %}Entladen{%endif%}"
              device_class: "enum"
              options: ['Standby','Laden','Entladen']
              icon: mdi:battery-sync-outline
              device: 
                identifiers: "<EurePVHubSeriennummer>"
            
            - name: "Pack Num"
              unique_id: "<deviceID>packNum"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.packNum | int }}"
              icon: mdi:battery-check-outline
              device: 
                identifiers: "<EurePVHubSeriennummer>"
            
            - name: "Electric Level"
              unique_id: "<deviceID>electricLevel"
              state_topic: "<appKey>/<deviceID>/state"
              unit_of_measurement: "%"
              device_class: "battery"
              value_template: "{{ value_json.electricLevel | int }}"
              device: 
                identifiers: "<EurePVHubSeriennummer>"
            
            - name: "SOC Set"
              unique_id: "<deviceID>socSet"
              state_topic: "<appKey>/<deviceID>/state"
              unit_of_measurement: "%"
              value_template: "{{ value_json.socSet | int / 10 }}"
              icon: mdi:battery-plus
              device: 
                identifiers: "<EurePVHubSeriennummer>"
                
            - name: "Inverse Max Power"
              unique_id: "<deviceID>inverseMaxPower"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.inverseMaxPower | int }}"
              unit_of_measurement: "W"
              icon: mdi:flash-alert
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - name: "Solar Power 1"
              unique_id: "<deviceID>solarPower1"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: >
                {% if states('sensor.solarflow_solar_power_1') not in ['unknown'] %} {# Muss ggf. an euren Entitätsnamen angepasst werden. #}
                  {{ int(value_json.solarPower1, 0) }}
                {% else %}
                  {{ int(0) }}
                {% endif %}
              unit_of_measurement: "W"
              device_class: "power"
              state_class: "measurement"
              icon: mdi:solar-panel
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - name: "Solar Power 2"
              unique_id: "<deviceID>solarPower2"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: >
                {% if states('sensor.solarflow_solar_power_2') not in ['unknown'] %} {# Muss ggf. an euren Entitätsnamen angepasst werden. #}
                  {{ int(value_json.solarPower2, 0) }}
                {% else %}
                  {{ int(0) }}
                {% endif %}
              unit_of_measurement: "W"
              device_class: "power"
              state_class: "measurement"
              icon: mdi:solar-panel
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - name: "Pass Mode"
              unique_id: "<deviceID>passMode"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{% if value_json.passMode == 0 %}Automatisch{%elif value_json.passMode == 1 %}Immer aus{%elif value_json.passMode == 2 %}Immer ein{%endif%}"
              device_class: "enum"
              options: ['Automatisch','Immer aus','Immer ein']
              icon: mdi:auto-mode
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - name: "AC Mode"
              unique_id: "<deviceID>acMode"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{% if value_json.acMode == 2 %}Entladen{%elif value_json.acMode == 1 %}Laden{%endif%}"
              options: ['Entladen','Laden']
              device_class: "enum"
              icon: mdi:current-ac
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - name: "Batterie <Nr> maxTemp"
              unique_id: "<deviceID>Batterie<Nr>maxTemp"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: >
                {% if (value_json.packData | is_defined) %}
                  {% for i in value_json.packData %}
                    {% if i.sn == "<EureBatterieSeriennummer>" %}
                      {{ (i.maxTemp | float - 273.15) | round(2) }}
                    {% endif %}
                  {% endfor %}
                {% endif %}
              unit_of_measurement: "°C"
              device_class: "temperature"
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - name: "Batterie <Nr> maxVol"
              unique_id: "<deviceID>Batterie<Nr>maxVol"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: >
                {% if (value_json.packData | is_defined) %}
                  {% for i in value_json.packData %}
                    {% if i.sn == "<EureBatterieSeriennummer>" %}
                      {{ i.maxVol | float / 100 }}
                    {% endif %}
                  {% endfor %}
                {% endif %}
              unit_of_measurement: "V"
              device_class: "voltage"
              icon: mdi:alpha-v-circle-outline
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - name: "Batterie <Nr> minVol"
              unique_id: "<deviceID>Batterie<Nr>minVol"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: >
                {% if (value_json.packData | is_defined) %}
                  {% for i in value_json.packData %}
                    {% if i.sn == "<EureBatterieSeriennummer>" %}
                      {{ i.minVol | float / 100 }}
                    {% endif %}
                  {% endfor %}
                {% endif %}
              unit_of_measurement: "V"
              device_class: "voltage"
              icon: mdi:alpha-v-circle-outline
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - name: "Batterie <Nr> socLevel"
              unique_id: "<deviceID>Batterie<Nr>socLevel"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: >
                {% if (value_json.packData | is_defined) %}
                  {% for i in value_json.packData %}
                    {% if i.sn == "<EureBatterieSeriennummer>" %}
                      {{ i.socLevel | int }}
                    {% endif %}
                  {% endfor %}
                {% endif %}
              unit_of_measurement: "%"
              device_class: "battery"
              device: 
                identifiers: "<EurePVHubSeriennummer>"
                
          binary_sensor:
            - name: "WiFi State"
              unique_id: "<deviceID>wifiState"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: >
                {% if (value_json.wifiState | is_defined) %}
                  {{ value_json.wifiState | abs() }}
                {% endif %}
              payload_on: '1'
              payload_off: '0'
              device_class: "connectivity"
              entity_category: diagnostic
              device: 
                identifiers: "<EurePVHubSeriennummer>"
                  
            - name: "Heat State"
              unique_id: "<deviceID>heatState"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.heatState | int }}"
              payload_on: '1'
              payload_off: '0'
              icon: mdi:heating-coil
              device: 
                identifiers: "<EurePVHubSeriennummer>"
              
          switch:
            - unique_id: "<deviceID>masterSwitch"
              state_topic: "<appKey>/<deviceID>/state"
              state_off: false
              command_topic: "<appKey>/<deviceID>/masterSwitch/set"
              name: "Master Switch"
              device_class: "switch"
              icon: mdi:gesture-tap-button
              value_template: "{{ value_json.masterSwitch | default('') }}"
              payload_on: true
              payload_off: false
              state_on: true
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - unique_id: "<deviceID>buzzerSwitch"
              state_topic: "<appKey>/<deviceID>/state"
              state_off: false
              command_topic: "<appKey>/<deviceID>/buzzerSwitch/set"
              name: "Buzzer Switch"
              device_class: "switch"
              icon: mdi:bullhorn-outline
              value_template: "{{ value_json.buzzerSwitch | default('') }}"
              payload_on: true
              payload_off: false
              state_on: true
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - unique_id: "<deviceID>autoRecover"
              state_topic: "<appKey>/<deviceID>/state"
              state_off: false
              command_topic: "<appKey>/<deviceID>/autoRecover/set"
              name: "Auto Recover"
              device_class: "switch"
              icon: mdi:restore
              value_template: "{{ value_json.autoRecover | default('') }}"
              payload_on: true
              payload_off: false
              state_on: true
              device: 
                identifiers: "<EurePVHubSeriennummer>"
      • Ggf. für Hyper 2000 und Ace 1500 dann noch zusätzlich:

            # Einzufügen bei den anderen Sensoren
            - name: "Grid Input Power"
              unique_id: "<deviceID>gridInputPower"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: >
                {% if states('sensor.solarflow_grid_input_power') not in ['unknown'] %}
                  {{ int(value_json.gridInputPower, 0) }}
                {% else %}
                  {{ int(0) }}
                {% endif %}
              unit_of_measurement: "W"
              device_class: "power"
              state_class: "measurement"
              device: 
                identifiers: "<EurePVHubSeriennummer>"
                    
            # Nur Hyper 2000
            - name: "AC Output Power"
              unique_id: "<deviceID>acOutputPower"
              state_topic: "<appKey>/<deviceID>/state"
                value_template: >
                  {% if states('sensor.solarflow_ac_output_power') not in ['unknown'] %}
                    {{ int(value_json.acOutputPower, 0) }}
                  {% else %}
                    {{ int(0) }}
                  {% endif %}
              unit_of_measurement: "W"
              device_class: "power"
              state_class: "measurement"
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            - name: "Hyper Tmp"
              unique_id: "<deviceID>hyperTmp"
              state_topic: "<appKey>/<deviceID>/state"
              unit_of_measurement: "°C"
              device_class: "temperature"
              value_template: "{{ value_json.hyperTmp | float / 10 - 273.15 }}"
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            # Nur Ace 1500
            - name: "DC Output Power"
              unique_id: "<deviceID>dcOutputPower"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: >
                {% if states('sensor.solarflow_dc_output_power') not in ['unknown'] %}
                  {{ int(value_json.dcOutputPower, 0) }}
                {% else %}
                  {{ int(0) }}
                {% endif %}
              unit_of_measurement: "W"
              device_class: "power"
              state_class: "measurement"
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            # Einzufügen bei den anderen Schaltern
            - unique_id: "<deviceID>acSwitch"
              state_topic: "<appKey>/<deviceID>/state"
              state_off: false
              command_topic: "<appKey>/<deviceID>/acSwitch/set"
              name: "AC Switch"
              device_class: "switch"
              icon: mdi:current-ac
              value_template: "{{ value_json.acSwitch | default('') }}"
              payload_on: true
              payload_off: false
              state_on: true
              device: 
                identifiers: "<EurePVHubSeriennummer>"
        
            # Nur Ace 1500
            - unique_id: "<deviceID>dcSwitch"
              state_topic: "<appKey>/<deviceID>/state"
              state_off: false
              command_topic: "<appKey>/<deviceID>/dcSwitch/set"
              name: "DC Switch"
              device_class: "switch"
              icon: mdi:current-dc
              value_template: "{{ value_json.dcSwitch | default('') }}"
              payload_on: true
              payload_off: false
              state_on: true
              device: 
                identifiers: "<EurePVHubSeriennummer>"
      • Speichert die Datei.

      • Öffnet die Entwicklerwerkzeuge und überprüft, ob eure Konfiguration fehlerfrei ist, danach startet Home Assistant neu.

      • Wenn ihr im weiteren Änderungen an dieser Sensoren- und Schalter-Konfiguration vornehmt, etwas hinzufügt oder entfernt, reicht es wenn ihr im Bereich YAML-Konfiguration neuladen auf Manuell konfigurierte MQTT-Entitäten klickt.

        grafik

      • Nun solltet ihr unter Geräte & Dienste in eurer MQTT-Integration ein neues Gerät namens SolarFlow vorfinden, welches unter den o. a. Namen die entsprechenden Sensoren und Schalter enthält. Die wenigsten werden von vorne herein bereits einen Wert haben, da wie anfänglich erwähnt der Zendure-Broker nur Werteänderungen ausspielt, sich also der jeweilige Wert im Vergleich zu seinem vorherigen Status geändert haben muss. Um eine Aktualisierung aller Sensoren zu erzwingen, könnt ihr einfach mal für ein paar Augenblicke in der Zendure-App auf der Detailansicht der Batterien, dort wo ihr auch die Temperaturen sehen könnt, verweilen.

      • Abschließend noch ein paar Einlassungen zu den von mir gemachten Anpassungen, in Ergänzung zu der Zendure Sensorbauanleitung:

        • Ich habe alle Sensoren um einen Default-Wert in der value_template-Zeile ergänzt. Durch den Umstand, dass nur Werteänderungen übermittelt werden, schreibt euch Home Assistant für jeden Sensor bei dem beim letzten Datenaustausch kein Wert mit dabei war eine Warning (Template variable warning: 'dict object' has no attribute 'blablabla' when rendering '{{ value_json.blablabla }}') in euer Log, da seitens Home Assistant die Erwartung vorhanden ist zu jedem Sensor einen Wert zu erhalten. Alle Power-Sensoren habe ich mit einer zuzätzlichen Überprüfung versehen, ob der Sensor auch ein Wert hat. Ist dem nämlich nicht so, wird der Sensor initial auf 0 W gesetzt. Da gerade die Batteriesensoren aktuell nicht definiert auf 0 W gesetzt werden, habe ich zwei kleine Automatisierungen erstellt, die dies umsetzen, da die Batterien ja nur geladen oder entladen werden können. Zuvor hatte ich diese Sensoren nur mit dem Default-Wert int(0) versehen. Dies sorgte dafür, dass Home Assistant weiterhin den letzten Wert angezeigte, bis ein neuer übermittelt wurde bzw., dass ein Sensor auch auf 0 gesetzt wurde, wenn er nicht mehr aktualisiert wurde. Dies klappt aber mittlerweile nicht mehr, was gerade bei den Batteriesensoren stört. Daher der u. a. neue Ansatz mit den Automatisierungen. Sicherlich nicht das Ende der Fahnenstange. Zudem habe ich eine Automatisierung erstellt, die einen jeweiligen Power-Sensor nach drei Minuten ohne Werteänderung auf 0 W setzt.

        • Ihr findet die Automatisierungen auch als einzelne Dateien im Code-Bereich.

        • Beachtet zudem, dass ich dort die "Standard Entitätennamen" verwende. Solltet ihr eure Sensoren also anders benennen, müsst ihr dies in der Automatisierung entsprechend anpassen, sonst wird sie nicht funktionieren.

        • Hier und Hier findet ihr noch Beispiele von Erweiterungen/Verbesserungen aus der Community zu den folgenden Automatisierungsbeispielen, bei der der Zustand der Batterien zusätzlich berücksichtigt wird.

          alias: Batterieladung
          description: ""
          trigger:
            - platform: numeric_state
              entity_id:
                - sensor.solarflow_output_pack_power
              above: 0
          condition:
            - condition: not
              conditions:
                - condition: state
                  entity_id: sensor.solarflow_pack_input_power
                  state: "0"
          action:
            - service: mqtt.publish
              metadata: {}
              data:
                qos: "0"
                topic: <appKey>/<deviceID>/state
                payload: "{\"packInputPower\":0}"
          mode: single
          alias: Batterieentladung
          description: ""
          trigger:
            - platform: numeric_state
              entity_id:
                - sensor.solarflow_pack_input_power
              above: 0
          condition:
            - condition: not
              conditions:
                - condition: state
                  entity_id: sensor.solarflow_output_pack_power
                  state: "0"
          action:
            - service: mqtt.publish
              metadata: {}
              data:
                qos: "0"
                topic: <appKey>/<deviceID>/state
                payload: "{\"outputPackPower\":0}"
          mode: single
          alias: SolarFlow Power-Sensoren nullen
          description: >-
            Wenn sich der Wert eines Power-Sensors innerhalb der letzten drei Minuten
            nicht geändert hat, wird er auf 0 W gesetzt.
          trigger:
            - platform: template
              value_template: >-
                {{ (as_timestamp(now()) -
                as_timestamp(states.sensor.solarflow_pack_input_power.last_changed)) > 180
                }}
              id: packInputPower
            - platform: template
              value_template: >-
                {{ (as_timestamp(now()) -
                as_timestamp(states.sensor.solarflow_output_pack_power.last_changed)) > 180
                }}
              id: outputPackPower
            - platform: template
              value_template: >-
                {{ (as_timestamp(now()) -
                as_timestamp(states.sensor.solarflow_solar_input_power.last_changed))
                > 180 }}
              id: solarInputPower
            - platform: template
              value_template: >-
                {{ (as_timestamp(now()) -
                as_timestamp(states.sensor.solarflow_output_home_power.last_changed)) > 180
                }}
              id: outputHomePower
            - platform: template
              value_template: >-
                {{ (as_timestamp(now()) -
                as_timestamp(states.sensor.solarflow_solar_power_1.last_changed)) > 180 }}
              id: solarPower1
            - platform: template
              value_template: >-
                {{ (as_timestamp(now()) -
                as_timestamp(states.sensor.solarflow_solar_power_2.last_changed)) > 180 }}
              id: solarPower2
          condition: []
          action:
            - choose:
                - conditions:
                    - condition: trigger
                      id:
                        - packInputPower
                    - condition: numeric_state
                      entity_id: sensor.solarflow_pack_input_power
                      above: 0
                  sequence:
                    - service: mqtt.publish
                      data:
                        qos: "0"
                        topic: <appKey>/<deviceID>/state
                        payload: "{\"packInputPower\":0}"
                - conditions:
                    - condition: trigger
                      id:
                        - outputPackPower
                    - condition: numeric_state
                      entity_id: sensor.solarflow_output_pack_power
                      above: 0
                  sequence:
                    - service: mqtt.publish
                      data:
                        qos: "0"
                        topic: <appKey>/<deviceID>/state
                        payload: "{\"outputPackPower\":0}"
                - conditions:
                    - condition: trigger
                      id:
                        - solarInputPower
                    - condition: numeric_state
                      entity_id: sensor.solarflow_solar_input_power
                      above: 0
                  sequence:
                    - service: mqtt.publish
                      data:
                        qos: "0"
                        topic: <appKey>/<deviceID>/state
                        payload: "{\"solarInputPower\":0}"
                - conditions:
                    - condition: trigger
                      id:
                        - outputHomePower
                    - condition: numeric_state
                      entity_id: sensor.solarflow_output_home_power
                      above: 0
                  sequence:
                    - service: mqtt.publish
                      data:
                        qos: "0"
                        topic: <appKey>/<deviceID>/state
                        payload: "{\"outputHomePower\":0}"
                - conditions:
                    - condition: trigger
                      id:
                        - solarPower1
                    - condition: numeric_state
                      entity_id: sensor.solarflow_solar_power_1
                      above: 0
                  sequence:
                    - service: mqtt.publish
                      data:
                        qos: "0"
                        topic: <appKey>/<deviceID>/state
                        payload: "{\"solarPower1\":0}"
                - conditions:
                    - condition: trigger
                      id:
                        - solarPower2
                    - condition: numeric_state
                      entity_id: sensor.solarflow_solar_power_2
                      above: 0
                  sequence:
                    - service: mqtt.publish
                      data:
                        qos: "0"
                        topic: <appKey>/<deviceID>/state
                        payload: "{\"solarPower2\":0}"
          mode: parallel
        • Solar Input Power, Pack Input Power, Output Pack Power und Output Home Power habe ich um state_class: measurement ergänzt, damit Home Assistant mit diesen auch rechnen kann. Ob das wirklich nötig ist weiß ich gerade gar nicht. Um die Werte aber im Energie Dashboard nutzen zu können müssen sie noch zu einem Verbrauchswert (Leistung mal Zeit) integriert werden. Dafür gibt es in der Helfersektion von Home Assistant den Riemann Summenintegralsensor. Die Methode habe ich auf Links gestellt und das metrische Präfix auf kilo gestellt. Wenn dann ein paar Werte durchgelaufen sind könnt ihr diese dann im Energie Dashboard verwenden.

          grafik

        • Output Limit und Input Limit haben die unit_of_measurement: "W" erhalten.

        • Remain Input Time und Remain Out Time haben die device_class: "duration" bekommen. Der übermittelte Wert ist die jeweilige Dauer in Minuten, sodass die unit_of_measurement: "min" ist. Durch die device_class rechnet Home Assistant das automatisch in eine Zeitangabe in h:min:s um.

        • SOC Set ist von Zendure schon mit unit_of_measurement: "%" angegeben, allerdings liefert der Sensor dann z.B. 1000 %, wenn die obere Ladegrenze 100 % ist. Keine Ahnung warum das so sein soll. Ich habe den Wert entsprechend noch durch 10 geteilt.

        • Einträge die einen gewissen Zustand darstellen habe ich soweit möglich als enum angelegt.

        • Ich habe alle Einträge um einen device-Block ergänzt und als eindeutigen identifier meine Seriennummer genommen. Durch diesen Block weiß Home Assistant, dass es sich um Entitäten des selben Gerätes handelt und erstellt entsprechend dieses Gerät, sodass ihr es unter Geräte & Dienste auch vorfinden könnt.

        • Durch den Zendure-Broker werden auch Werte ausgespielt, für die es keine Bauanleitung gibt. Ich habe sie einfach mitangelegt.

        • Natürlich könnt ihr jeder Entität hier schon ein eigenes Symbol verpassen. Ergänzt dafür für die jeweilige Entität eine icon-Zeile, z. B. icon: mdi:flash.

  3. Vor- und Nachteile

    Methode Vorteile Nachteile
    (1.) Keine vorherige Nutzung von MQTT in Home Assistant Schnell hinzugefügt und wenig Aufwand Nachträgliche Anpassung der Entitäten möglich aber ggf. umständig. Warnings im Log über fehlende dict object[^1]
    (2. iii.) Bereits MQTT in der Nutzung. Automatisches Anlegen der Entitäten durch Home Assistant Geringfügiger Mehraufwand ggü. (1.), aber in Home Assistant nicht anders möglich Nachträgliche Anpassung der Entitäten möglich aber ggf. umständig. Warings im Log über fehlende dict object[^1]
    (2. iv.) Bereits MQTT in der Nutzung. Manuelles Anlegen der Entitäten Vollständige Anpassungmöglichkeiten gegeben. Ausschließen von Warnings Ggf. umständlich und aufwendiger als (2. iii.). Nicht selbsterklärend, schwierig nachzuvollziehen für jemanden der nicht so in der Materie steckt

[^1]: Als "Lösung" ist mir hier bisher nur bekannt, die entsprechende Warning zu unterdrücken.

⚠️ **GitHub.com Fallback** ⚠️