-
Notifications
You must be signed in to change notification settings - Fork 5
Integration into Home Assistant
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.
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.
Depending on how far you have gone in Home Assistant, there are now several ways to reach your goal.
-
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 yourappSecret
. The URL of the broker and the port were also delivered to you with the above answer:mqtt.zen-iot.com
ormqtt-eu.zen-iot.com
with port1883
. -
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..Note: This will only create entities/sensors for you. No dedicated device containing the entities/sensors will be created.
-
-
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.
-
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. -
Under Advanced (button) you must then subscribe to your
appKey
as a topic, i.e. add it as a new subscription -->appKey/#
. -
The values should then come in at a fairly early stage. When you open everything up, it looks something like this:
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 itdeviceID
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 andpackInputPower
is the power that is taken from the batteries. -
-
-
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 theconfig
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.
-
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 -Menu -> File -> Open Folder... or alternatively use the key combination
Ctrl + K
and thenCtrl + 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
and name it
mosquitto
and create a new file with the namezendure.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.
- 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
-
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.
-
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 commandcd share
. - Check with
ls
if there is already a foldermosquitto
. If a new input line simply appears after the command, the folder does not yet exist, otherwise an entrymosquitto
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 theright mouse button
in the terminal window and selectPaste
. 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 totrue
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.
-
-
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.
-
Manual insertion
The manual creation of entities is done in Home Assistant via
configuration.yaml
. This is located in theconfig
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 theconfiguration.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.
-
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 valueint(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 toLeft
and the metric prefix toKilo
. Once you have run through a few values, you can use them in the Energy Dashboard. -
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 theunit_of_measurement: "min"
is. Due to thedevice_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 uniqueidentifier
. 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
andinverseMaxPower
. 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
.
-
-
-
-
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.