Skip to content

Integration into Home Assistant

z-master42 edited this page Jan 8, 2025 · 21 revisions

Integrating SolarFlow into Home Assistant

Introduction

Zendure operates an MQTT broker for retrieving information for the products SuperBase V, Satellite Battery and SolarFlow. This is currently the only official way to access information outside of the app provided by Zendure. Since Zendure plays out the data via its own broker, the connection is forced to run through the internet. Purely local control is currently and officially not yet possible. MQTT is an open network protocol for machine-to-machine communication. As a rule, several clients are connected to a broker. The exchanged messages are defined hierarchically by topics. In order to access corresponding information or send commands, the corresponding topic must be subscribed to.

Preparation

The prerequisite for use is an account with Zendure (which everyone should have by installing the app for controlling the SolarFlow). To retrieve the MQTT data of your own SolarFlow, you also need an 'appKey' and an 'appSecret'. To get these two values, you need the email address you registered with at Zendure and the serial number of your PV hub. I used the command line tool curl to retrieve the serial number.

Procedure on a Microsoft operating system

  • Open the command prompt with Windows key + R.

  • Enter cmd.

  • Enter the following command in the command line:

    Region setting in the Zendure app to "Global"

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

    Region setting in the Zendure app on a "European country"

    curl -i -v --json "{'snNumber': 'YourHubSerialNumber', 'account': 'YourEmailaddress'}" https://app.zendure.tech/eu/developer/api/apply
    
  • Beforehand, of course, you have entered your serial number and the email address you use instead of the placeholders.

Proceeding on a Linux operating system

  • Open a terminal window with Ctrl+Alt+T.

  • Enter the following command in the command line:

    Region setting in the Zendure app to "Global"

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

    Region setting in the Zendure app on a "European country"

    curl -i -X POST -H 'Content-Type: application/json' -d '{"snNumber": "YourHubSerialNumber", "account": "YourEmailaddress"}' https://app.zendure.tech/v2/developer/api/apply
    
  • Beforehand, of course, you have entered your serial number and the email address you use instead of the placeholders.

Reply

If you have not made any mistakes, an answer should appear as follows:

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

Instead of the placeholders, you will find your 'appKey' and your 'appSecret'. Both are letter-number combinations.

Setting up MQTT in Home Assistant

Depending on how far you have gone in Home Assistant, there are now several ways to reach your goal.

  1. Option: No MQTT device connected to Home Assistant yet

    If this is your first contact with MQTT, things will go quite quickly.

    • Add a new integration via Settings - Devices & Services. Look for MQTT there and select that without any other designations.

    • The username is your appKey and the password your appSecret. The URL of the broker and the port were also delivered to you with the above answer: mqtt.zen-iot.com or mqtt-eu.zen-iot.com with port 1883.

    • In order for data to come in, you have to subscribe to a topic, as mentioned at the beginning. This is done here by activating Enable Discovery on the configuration page and entering your appKey as Discovery prefix..

      grafik

      Note: This will only create entities/sensors for you. No dedicated device containing the entities/sensors will be created.

  2. Option: You already use an MQTT broker in Home Assistant

    If you have already gained experience with MQTT, e.g. because you have connected sensors or sockets with Home Assistant via it, it is very likely that you have installed the Mosquitto broker add-on and configured the MQTT integration for the connection with it.

    Problem: Only one MQTT integration can be installed in Home Assistant.

    Solution: You have to build a bridge to the MQTT broker from Zendure. There are two ways to proceed. Either you let Home Assistant create all available sensors automatically or you add them manually. First of all: I added mine manually, so I had the option of adapting them at the same time.

    1. Check beforehand

      • In order to check whether the Zendure broker is transmitting data from your SolarFlow at all, you can use the programme MQTT-Explorer, which is available for the most common operating systems.

      • Create a new connection with your access data (appKey, appSecret, mqttUrl) as shown in the screenshot.

        grafik

      • Under Advanced (button) you must then subscribe to your appKey as a topic, i.e. add it as a new subscription --> appKey/#.

        grafik

      • The values should then come in at a fairly early stage. When you open everything up, it looks something like this:

        grafik Under the broker address, an entry appears that reads like your appKey (all in 🔴). Below that there are three more entries; switch, sensor, and one that identifies your SolarFlow. We will therefore call it deviceID from now on (all in 🔵).

        • switch contains as entries the building instructions for the switches available so far.
        • sensor contains as entries the building instructions for the sensors available so far.
        • deviceID contains the status of the sensors as entries, but only those whose value has changed.

        The following sensors and switches are currently available (the list does not claim to be complete):

        Field Description Device class Automatic detection [Hub 1200] (2. iii.)
        electricLevel Charge level across all batteries in % Sensor Yes
        remainOutTime Remaining time until batteries are discharged in min. I do not know whether the discharge limit is taken into account Sensor Yes
        remainInputTime Remaining time until batteries are charged in min. I do not know whether the charging limit is taken into account Sensor Yes
        socSet (Upper) charge limit in % * 10 Sensor Yes
        inputLimit Function not known. Permanently set to 0 W Sensor No
        outputLimit Maximum power output to the “house” (upper limit) in W Sensor Yes
        solarInputPower Current solar power across all inputs in W Sensor Yes
        packInputPower Current discharge power of the batteries in W Sensor Yes
        outputPackPower Current charging power of the batteries in W Sensor Yes
        outputHomePower Current power output to the “house” in W Sensor Yes
        packNum Number of connected batteries Sensor Yes
        packState Status of all batteries (0: Standby, 1: At least one battery is charged, 2: At least one battery is discharged) Sensor Yes
        buzzerSwitch Confirmation tone On or Off Switch Yes
        masterSwitch Function not known. Always set to On Switch Yes
        wifiState WLAN connection status (1: Connected, 0: Disconnected) Sensor No
        hubState Behavior of the hub/hyper when the discharge limit is reached (0: stop output and standby 1: stop output and switch off) Sensor No
        inverseMaxPower Maximum permissible output power to the “house” / legal upper limit in W Sensor No
        packData Collective topic about the battery values, filterable via the respective battery serial number (maxVol: voltage of the cell with the highest voltage in V * 100, minVol: voltage of the cell with the lowest voltage in V * 100, maxTemp: temperature of the cell with the highest temperature in K, socLevel: charge level in %, sn: serial number) Sensor Yes
        solarPower1 Current solar power input PV 1 in W Sensor Yes
        solarPower2 Current solar power input PV 2 in W Sensor Yes
        passMode Bypass mode (0: Automatic, 1: Always off, 2: Always on) Sensor Yes
        autoRecover Automatic reset of bypass mode (0: Off 1: On) Switch Yes
        sn Serial number of the Hub/Hyper Sensor No
        heatState Battery heater AB2000 and S series (0: Off, 1: On) Sensor No
        acMode Function not known. Permanently set to 2 Sensor No

        Special Hyper 2000 and Ace 1500 sensors and switches:

        Field Description Device class Automatic detection (2. iii.)
        gridInputPower Power consumption from the grid in W Sensor Yes
        hyperTmp Temperature of Hyper 2000 Sensor No
        acOutputPower (Hyper 2000) Function not known. Presumably power output in conjunction with the off-grid socket strip accessory in W Sensor Yes
        dcOutputPower (Ace 1500) Presumed power output USB and XT-60 in W Sensor Yes
        acSwitch Presumably power output via existing sockets (Ace 1500) or the off-grid socket strip Switch Yes
        dcSwitch (Ace 1500) Presumably power output USB and XT-60 Switch Yes

        *Note: The sensors that are specified as switches are only used to display information. The function cannot be switched on or off via the switch. In addition, all sensors are named from the hub's perspective. This can lead to confusion, because outputPackPower is the power that goes into the batteries and packInputPower is the power that is taken from the batteries.

    2. Same start

      • The beginning is the same for both options.

      • First, the bridge to the Zendure broker must be established. To do this, you must access the share directory of your Home Assistant. This is not possible via the File Editor add-on, for example. This only gives you access to the config directory. You can do this via the Studio Code Server addon, the Samba share addon or the Terminal & SSH addon. I will explain the individual methods below. First create a file with the following content. It doesn't matter what the file is called or which editor or word processing program you use.

      • Create a new text file.

      • Insert the following content:

        connection zendure-broker
        address <mqttUrl>:1883
        remote_username <appKey>
        remote_password <appSecret>
        remote_clientid <appKey>
        topic <appKey>/# in
        
      • Replace everything between <> including the <> with your own data, of course. There must be no <> left.

      1. Studio Code Server addon

        • Install the Studio Code Server addon in the addon area and then switch to the configuration page of the addon. Click on Display unused optional configuration options and enter /root under config_path. Click on Save and then start the addon and open the user interface.
        • In the user interface, go to the top left via the burger-Menu -> File -> Open Folder... or alternatively use the key combination Ctrl + K and then Ctrl + O.
        • In the selection menu that appears, search for share and then click on OK.
        • The directory will probably be empty. Create a new folder using the corresponding icon folder and name it mosquitto and create a new file file with the name zendure.conf in this new folder.
        • Now copy the contents of your previously created text file into the new file.
        • If you want Home Assistant to create the sensor entities automatically, copy the other lines from iii.
        • All changes you make in the user interface are saved directly.
      2. Samba share-Addon

        • Install the Samba share addon in the addon area and then switch to the configuration page of the addon. Enter a username and a password and click on Save. Then start the addon.
        • Add a new network address in the file explorer of your operating system and enter the IP address of your Home Assistant followed by \share as the path. For example \\192.168.1.42\share.
        • Log in with the username and password assigned in the addon.
        • The directory is probably empty. Create a new folder called mosquitto and in it a new file called 'zendure.conf'.
        • Now copy the content from your previously created text file into the new file.
        • If you want Home Assistant to create the sensor entities automatically, copy the other lines from iii.
        • Save the file.
      3. Terminal & SSH-Addon

        • Install the Terminal & SSH Addon in the Addon area, then start it and open the user interface.
        • Change to the share directory with the command cd share.
        • Check with ls if there is already a folder mosquitto. If a new input line simply appears after the command, the folder does not yet exist, otherwise an entry mosquitto appears.
        • If the folder does not exist, create it with mkdir mosquitto.
        • Change to the directory with cd mosquitto.
        • Now create a new file and edit it with the editor nano. Enter nano zendure.conf.
        • Now copy the content from your previously created text file and paste it into the terminal window. To do this, hold down the Shift key on your keyboard and click with the right mouse button in the terminal window and select Paste. Now the contents of your text file should appear in the terminal window.
        • If you want Home Assistant to create the sensor entities for you automatically, skip the next steps and continue with iii.
        • Close the nano editor with Ctrl + X.
        • Confirm the question that appears at the bottom of the terminal with y.
        • Confirms the next question with Enter.

      • In the configuration of the Mosquitto add-on, check whether active is set to true under Customize.
      • Finally, restart the Mosquitto-Addon.
      • An entry similar to Connecting bridge external-bridge (mqtt.zen-iot.com:1883) should then appear in the log. You may have to refresh the log several times (patience). If something comes up with a timeout or something like that, simply restart the addon.
    3. Automatic insertion

      In order for Home Assistant to automatically create the sensors and switches, it must know how they are structured. Home Assistant also has specifications as to how the topics must look for this to work. Simply add two more lines to your zendure.conf with:

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

      Note: This will only create entities/sensors for you. No dedicated device containing the entities/sensors will be created. This means that you will only find the individual entities if you search for them by their respective names.

      Now continue upstairs from where you just jumped off.

    4. Manual insertion

      The manual creation of entities is done in Home Assistant via configuration.yaml. This is located in the config directory. You can also access this via Samba. However, the way via the File Editor Addon is just as good. In this addon, the inserted code is marked in colour for better readability and if there are formatting or syntax errors, these are displayed directly. In general, you can write everything in the configuration.yaml. Over time, however, it becomes a bit confusing, as everything is written one below the other in a text file. It is better to store the corresponding configurations in new files.

      • Open your configuration.yaml.

      • Add a new line under the following block, which is located at the beginning of the file: mqtt: !include mqtt.yaml.

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

        Note: The block does not have to look exactly like this. Here, too, it depends on how far you have already gone in Home Assistant. If I remember correctly, however, at least one !include line should already be present.

      • Create a new file mqtt.yaml and insert content below.

      • Replace everything between <> including the <> with your own data. There must be no more <>.

           # The final entity names are made up of the sensor name and the device name
           # For the first sensor here: SolarFlow Hub State (sensor.solarflow_hub_state)
           # NOTE: The following comments must be removed before use!
           sensor:
             - name: "Hub State"
               unique_id: "<deviceID>hubState"
               state_topic: "<appKey>/<deviceID>/state"
               value_template: "{{ value_json.hubState | int }}"
               device: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 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'] %} # Must be adapted to your entity name if necessary
                   {{ int(value_json.solarInputPower, 0) }}
                 {% else %}
                   {{ int(0) }}
                 {% endif %}
               state_class: "measurement"
               device: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - 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'] %} # Must be adapted to your entity name if necessary
                   {{ int(value_json.packInputPower, 0) }}
                 {% else %}
                   {{ int(0) }}
                 {% endif %}
               state_class: "measurement"
               device: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - 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'] %} # Must be adapted to your entity name if necessary
                   {{ int(value_json.outputPackPower, 0) }}
                 {% else %}
                   {{ int(0) }}
                 {% endif %}
               state_class: "measurement"
               device: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - 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'] %} # Must be adapted to your entity name if necessary
                   {{ int(value_json.outputHomePower, 0) }}
                 {% else %}
                   {{ int(0) }}
                 {% endif %}
               state_class: "measurement"
               device: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - name: "Output Limit"
               unique_id: "<deviceID>outputLimit"
               state_topic: "<appKey>/<deviceID>/state"
               value_template: "{{ value_json.outputLimit | int }}"
               unit_of_measurement: "W"
               device: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - name: "Input Limit"
               unique_id: "<deviceID>inputLimit"
               state_topic: "<appKey>/<deviceID>/state"
               value_template: "{{ value_json.inputLimit | int }}"
               unit_of_measurement: "W"
               device: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - 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: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - 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: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - name: "Pack State"
               unique_id: "<deviceID>packState"
               state_topic: "<appKey>/<deviceID>/state"
               value_template: "{{ value_json.packState | int }}"
               device: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - name: "Pack Num"
               unique_id: "<deviceID>packNum"
               state_topic: "<appKey>/<deviceID>/state"
               value_template: "{{ value_json.packNum | int }}"
               device: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - 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: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
             - name: "SOC Set"
               unique_id: "<deviceID>socSet"
               state_topic: "<appKey>/<deviceID>/state"
               unit_of_measurement: "%"
               value_template: "{{ value_json.socSet | int / 10 }}"
               device: 
                 name: "SolarFlow"
                 identifiers: "<YourPVHubSerialNumber>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
            - name: "Inverse Max Power"
              unique_id: "<deviceID>inverseMaxPower"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.inverseMaxPower | int }}"
              unit_of_measurement: "W"
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - 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 %}
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller
        
            - 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'] %}
                   {{ int(value_json.solarPower1, 0) }}
                 {% else %}
                   {{ int(0) }}
                 {% endif %}
              unit_of_measurement: "W"
              device_class: "power"
              state_class: "measurement"
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - 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'] %}
                   {{ int(value_json.solarPower2, 0) }}
                 {% else %}
                   {{ int(0) }}
                 {% endif %}
              unit_of_measurement: "W"
              device_class: "power"
              state_class: "measurement"
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - name: "Pass Mode"
              unique_id: "<deviceID>passMode"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.passMode | int }}"
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - name: "Heat State"
              unique_id: "<deviceID>heatState"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.heatState | int }}"
              device: 
                name: "SolarFlow"
                identifiers: "<EurePVHubSeriennummer>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - name: "AC Mode"
              unique_id: "<deviceID>acMode"
              state_topic: "<appKey>/<deviceID>/state"
              value_template: "{{ value_json.acMode | int }}"
              device: 
                name: "SolarFlow"
                identifiers: "<EurePVHubSeriennummer>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - 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 == "<YourBatterySerialNumber>" %}
                      {{ (i.maxTemp | float - 273.15) | round(2) }}
                    {% endif %}
                  {% endfor %}
                {% endif %}
              unit_of_measurement: "°C"
              device_class: "temperature"
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - 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 == "<YourBatterySerialNumber>" %}
                      {{ i.maxVol | float / 100 }}
                    {% endif %}
                  {% endfor %}
                {% endif %}
              unit_of_measurement: "V"
              device_class: "voltage"
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - 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 == "<YourBatterySerialNumber>" %}
                      {{ i.minVol | float / 100 }}
                    {% endif %}
                  {% endfor %}
                {% endif %}
              unit_of_measurement: "V"
              device_class: "voltage"
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - 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 == "<YourBatterySerialNumber>" %}
                      {{ i.socLevel | int }}
                    {% endif %}
                  {% endfor %}
                {% endif %}
              unit_of_measurement: "%"
              device_class: "battery"
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
          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"
              value_template: "{{ value_json.masterSwitch | default('') }}"
              payload_on: true
              payload_off: false
              state_on: true
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - unique_id: "<deviceID>buzzerSwitch"
              state_topic: "<appKey>/<deviceID>/state"
              state_off: false
              command_topic: "<appKey>/<deviceID>/buzzerSwitch/set"
              name: "Buzzer Switch"
              device_class: "switch"
              value_template: "{{ value_json.buzzerSwitch | default('') }}"
              payload_on: true
              payload_off: false
              state_on: true
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            - unique_id: "<deviceID>autoRevover"
              state_topic: "<appKey>/<deviceID>/state"
              state_off: false
              command_topic: "<appKey>/<deviceID>/autoRevover/set"
              name: "Auto Recover"
              device_class: "switch"
              value_template: "{{ value_json.autoRevover | default('') }}"
              payload_on: true
              payload_off: false
              state_on: true
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
      • If necessary for Hyper 2000 and Ace 1500 then additionally:

            # To be added to the other sensors
            - 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: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
            
            # Hyper 2000 only
            - 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: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
             - name: "Hyper Tmp"
               unique_id: "<deviceID>hyperTmp"
               state_topic: "<appKey>/<deviceID>/state"
               unit_of_measurement: "°C"
               value_template: "{{ value_json.hyperTmp | float / 10 - 273.15 }}"
               device: 
                 name: "SolarFlow"
                 identifiers: "<EurePVHubSeriennummer>"
                 manufacturer: "Zendure"
                 model: "SmartPV Hub 1200 Controller"
        
            # Ace 1500 only
            - 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: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            # To be added to the other switches
            - unique_id: "<deviceID>acSwitch"
              state_topic: "<appKey>/<deviceID>/state"
              state_off: false
              command_topic: "<appKey>/<deviceID>/acSwitch/set"
              name: "AC Switch"
              device_class: "switch"
              value_template: "{{ value_json.acSwitch | default('') }}"
              payload_on: true
              payload_off: false
              state_on: true
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
        
            # Ace 1500 only
            - unique_id: "<deviceID>dcSwitch"
              state_topic: "<appKey>/<deviceID>/state"
              state_off: false
              command_topic: "<appKey>/<deviceID>/dcSwitch/set"
              name: "DC Switch"
              device_class: "switch"
              value_template: "{{ value_json.dcSwitch | default('') }}"
              payload_on: true
              payload_off: false
              state_on: true
              device: 
                name: "SolarFlow"
                identifiers: "<YourPVHubSerialNumber>"
                manufacturer: "Zendure"
                model: "SmartPV Hub 1200 Controller"
      • Save the file.

      • Open the developer tools and check whether your configuration is error-free, then restart Home Assistant.

      • If you make further changes to this sensor and switch configuration, add or remove something, it is sufficient to click on Manually configured MQTT entities in the Reload YAML configuration area.

        grafik

      • You should now find a new device called SolarFlow under Devices & Services in your MQTT integration, which contains the corresponding sensors and switches under the above-mentioned names. Very few of them will already have a value from the outset because, as mentioned at the beginning, the Zendure broker only outputs value changes, i.e. the respective value must have changed compared to its previous status. To force an update of all sensors, you can simply pause for a few moments in the Zendure app on the detailed view of the batteries, where you can also see the temperatures.

      • Finally, a few comments on the adjustments I have made, in addition to the Zendure sensor building instructions:

        • I added a default value to all sensors in the value_template line. Due to the fact that only value changes are transmitted, Home Assistant writes a warning (Template variable warning: 'dict object' has no attribute 'blablabla' when rendering '{{ value_json.blablabla }}') in your log for each sensor for which no value was included in the last data exchange, as Home Assistant expects to receive a value for each sensor. I have provided all power sensors with an additional check to see whether the sensor also has a value. If this is not the case, the sensor is initially set to 0 W. As the battery sensors in particular are not currently set to 0 W, I have created two small automations that implement this, as the batteries can only be charged or discharged. Previously, I had only set these sensors to the default value int(0). This ensured that Home Assistant continued to display the last value until a new one was transmitted or that a sensor was also set to 0 if it was no longer updated. However, this no longer works, which is particularly annoying for battery sensors. Hence the new approach with automation, among other things. Certainly not the end of the line. I have also created an automation that sets a respective power sensor to 0 W after three minutes without a value change.

        • You can also find the automations as individual files in the code area.

        • Please also note that I use the "standard entity names" there. So if you name your sensors differently, you will have to adapt this accordingly in the automation, otherwise it will not work.

        • Here you will find examples of extensions/improvements from the community for the following automation examples, in which the status of the batteries is also taken into account.

          alias: Battery charging
          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: Battery discharging
          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: Zeroing SolarFlow power sensors
          description: >-
            If the value of a power sensor has not changed within the last
            three minutes, it is set to 0 W.
          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
        • I have added state_class: measurement to Solar Input Power, Pack Input Power, Output Pack Power and Output Home Power so that Home Assistant can also calculate with these. I don't know if this is really necessary at the moment. However, in order to be able to use the values in the energy dashboard, they still have to be integrated into a consumption value (power times time). For this purpose, there is the Riemann Sum Integral Sensor in the Help section of Home Assistant. I have set the method to Left and the metric prefix to Kilo. Once you have run through a few values, you can use them in the Energy Dashboard.

          grafik

        • Output Limit and Input Limit have been given the unit_of_measurement: "W".

        • Remain Input Time and Remain Out Time have been given the device_class: "duration". The transmitted value is the respective duration in minutes, so that the unit_of_measurement: "min" is. Due to the device_class, Home Assistant automatically converts this into a time specification in h:min:s.

        • SOC Set is already specified by Zendure with unit_of_measurement: "%", but the sensor then delivers e.g. 1000 % if the upper charge limit is 100 %. I have no idea why this should be the case. I have divided the value accordingly by 10.

        • I have added a device block to all entries and used my serial number as a unique identifier. Through this block, Home Assistant knows that they are entities of the same device and creates this device accordingly, so that you can also find it under Devices & Services.

        • The Zendure broker also plays out two values for which there are no instructions. I have simply added them. These are wifiState and inverseMaxPower. What the first one represents should be clear. The second represents the maximum acceptable input power to the inverter.

        • Of course, you can already give each entity its own symbol here. Add an icon line for the respective entity, e.g. icon: mdi:flash.

  3. Advantages and disadvantages

    Method Advantages Disadvantages
    (1.) No previous use of MQTT in Home Assistant Quickly added and little effort Subsequent adjustment of entities possible but possibly circumstantial. Warnings in log about missing dict object[^1]
    (2. iii.) Already MQTT in use. Automatic creation of entities by Home Assistant Slight additional effort compared to (1.), but not otherwise possible in Home Assistant Subsequent adjustment of entities possible but possibly awkward. Warings in log about missing dict object[^1]
    (2. iv.) Already MQTT in use. Manual creation of entities Full customisation possibilities given. Exclude warnings May be more cumbersome and complex than (2. iii.). Not self-explanatory, difficult to comprehend for someone who is not so familiar with the subject

[^1]: The only "solution" I know of so far is to suppress the corresponding Warning.