paint-brush
Turn Your Dumb Solar Inverter Into a Smart One With This Home Assistant Hackby@lifeofdanel
343 reads
343 reads

Turn Your Dumb Solar Inverter Into a Smart One With This Home Assistant Hack

by Daniel AnomfuemeMarch 6th, 2025
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

This tutorial explains how to locally integrate the Growatt SPF 6000 ES PLUS inverter with Home Assistant using Modbus RTU over TCP, eliminating reliance on Growatt's cloud service and enabling real-time energy monitoring.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coins Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Turn Your Dumb Solar Inverter Into a Smart One With This Home Assistant Hack
Daniel Anomfueme HackerNoon profile picture



It’s been 2 years since I fell into the Renewable Energy Rabbit Hole and something this has been able to do, is to also spark my interest in IoT devices and hardware. Something I have realized over the years is that it is not only important for you to have a Solar Inverter setup, you also need data from your inverter setup. Now some inverters come with a manageable energy monitoring solution, the best I have seen so far has to be from Victron with their VRM solution. It is without a doubt, the best OEM energy monitoring solution. You can see a demo of it here.


I used to use an SRNE inverter and thanks to the good folks at Home Assistant community forum, I was able to make my own data logger for it. However, I switched to the Growatt SPF 6000 ES PLUS a year ago and since, I have been using the OEM energy monitoring solution with their ShineWifi data logger. But it wasn’t sufficient for me as it could only update data every 5-minute interval and it relied on Growatt Cloud service, which is something I wanted to be less dependent on.


If you are someone like me who owns the same inverter and you are looking for a way to make it work locally on your Home Assistant network, this tutorial is for you. A basic overview of what we will be doing is taking the data from the Inverter’s Modbus RTU Protocol, sending it over TCP, then decoding the data in our Home Assistant instance so we can see it as entities inside Home Assistant.

Prerequisites

Connect Your Waveshare RS485 to ETH Adapter to Your Home network.

  • The Waveshare device comes with a power adapter too. You plug it into power, and plug it into the side that has the DC 5V port.

  • Then you take an Ethernet cable, plug one side to your home router or network switch’s LAN port, and plug the other side to the Waveshare device.

  • After that, you should observe to see if the Ethernet port has the usual still and blinking light to confirm the cable connection was successful.


Waveshare RS485 to ETH adapter


Visit the Waveshare Adapter Page

  • When you check at the back of your Waveshare device, you would see the Default IP, Username and Password. Mine has 192.168.0.7 as the Default IP, admin as the username and admin as password.

  • When you visit the default IP, you should see the Waveshare homepage which looks like this.


Waveshare hompage


  • However, if you can’t reach it, which I was unable to at first, it’s most likely because your router IP range is out of reach of the Waveshare device’s default IP. So let's fix that.


Temporarily Change Your Computer’s IP to Match the Waveshare

For me, my router’s range is 192.168.8.x, but the Waveshare was 192.168.0.7, so my computer wasn’t able to communicate with it unless I temporarily changed my PC’s IP to match. \

  • Go to your Network & Internet under settings.


  • Click on the properties



  • Click on edit IP Assignment and choose Manual

  • Toggle IPv4 and you would see a drop-down of settings

  • Set IP Address to 192.168.0.10 (or any other 192.168.0.x, except .7)

  • Subnet Mask (from router’s LAN settings) to 255.255.255.0

  • Gateway to 192.168.0.1 (if required, or leave blank)

  • Then save.


Access the Waveshare Device

  • Open a browser and go to the Waveshare default IP, 192.168.0.7
  • Login to the web interface using the default login details.
  • Go to the Local IP Config tab, set the IP to static and pick an IP available on your router’s IP range. For mine, I picked 192.168.8.15


  • Apply the changes and reboot the Waveshare device.
  • Restore your computer’s IP back to Automatic DHCP.
  • Now your Waveshare device should be accessible on your router’s network through the static IP you chose. Ideally, you should reserve that IP you chose for the Waveshare device on your router, through the router’s DHCP settings.


Connect your Inverter to the Waveshare Device

Now we can see the Waveshare device on our network, we are halfway there. Next thing we need to do is to connect the inverter itself to the Waveshare device.


  • Get another Ethernet cable and connect one end to the Growatt Inverter RS485 port, which is under the inverter.
  • Cut the other end of the Ethernet cable and expose pin 1 (Orange-white) and pin 2 (Orange).

Credits: www.warehousecables.com


  • Then insert pin 1 into B- and pin 2 into A+ on the Waveshare device.



  • Now we are done with all physical connections, let’s go to the Waveshare IP. Click on the Serial Port tab. Change the Work Mode to TCP Server. Baud Rate to 9600, Data Size to 8, Parity to None, Stop Bits to 1, Local Port Number to 502. Then save and restart.



  • To confirm data is being transferred, look at the Current Status tab and see if the byte figures increase as it refreshes.


Configure Home Assistant for Modbus RTU Over TCP

The Modbus data is currently being sent from the inverter to your home network. What’s left is to have Home Assistant decode that information and have them as entities. Luckily Home Assistant has a Modbus integration built into it and we just need to configure it.


  • We start by modifying the configuration.yaml file on your Home Assistant network.
  • If you are like me, who likes to keep the configuration.yaml as lean as possible by having separate yaml files for things and then including them into the configuration.yaml file, go ahead and create a new file called modbus.yaml and a new file called templates.yaml.
  • Include the modbus.yaml into the configuration.yaml by adding the code below.
modbus: !include modbus.yaml
template: !include templates.yaml
  • Then go to the modbus.yaml and paste this code inside it.
#Modbus
- name: growatt
  type: rtuovertcp
  host: 192.168.8.15 # Waveshare RS485 adapter IP
  port: 502 # Change if necessary
  delay: 5
  timeout: 5
  message_wait_milliseconds: 500

  sensors:
    # System Status
    - name: "Growatt System Status"
      slave: 1
      address: 0
      input_type: input
      data_type: uint16

    # Solar PV Sensors
    - name: "Growatt PV1 Voltage"
      slave: 1
      address: 1
      input_type: input
      data_type: uint16
      scale: 0.1
      unit_of_measurement: "V"

    - name: "Growatt PV2 Voltage"
      slave: 1
      address: 2
      input_type: input
      data_type: uint16
      scale: 0.1
      unit_of_measurement: "V"

    - name: "Growatt PV1 Current"
      slave: 1
      address: 7
      input_type: input
      data_type: uint16
      scale: 0.1
      unit_of_measurement: "A"

    - name: "Growatt PV2 Current"
      slave: 1
      address: 8
      input_type: input
      data_type: uint16
      scale: 0.1
      unit_of_measurement: "A"

    # PV Power (Read High & Low Registers)
    - name: "Growatt PV1 Charge Power High"
      slave: 1
      address: 3
      input_type: input
      data_type: uint16

    - name: "Growatt PV1 Charge Power Low"
      slave: 1
      address: 4
      input_type: input
      data_type: uint16

    - name: "Growatt PV2 Charge Power High"
      slave: 1
      address: 5
      input_type: input
      data_type: uint16

    - name: "Growatt PV2 Charge Power Low"
      slave: 1
      address: 6
      input_type: input
      data_type: uint16

    # Inverter & System Temperature Sensors
    - name: "Growatt Inverter Temperature"
      slave: 1
      address: 25
      input_type: input
      data_type: uint16
      scale: 0.1
      unit_of_measurement: "°C"

    - name: "Growatt DC-DC Temperature"
      slave: 1
      address: 26
      input_type: input
      data_type: uint16
      scale: 0.1
      unit_of_measurement: "°C"

    # Battery Sensors
    - name: "Growatt Battery Voltage"
      slave: 1
      address: 17
      input_type: input
      data_type: uint16
      scale: 0.01
      unit_of_measurement: "V"

    - name: "Growatt Battery SOC"
      slave: 1
      address: 18
      input_type: input
      data_type: uint16
      unit_of_measurement: "%"

    - name: "Growatt Battery Power High"
      slave: 1
      address: 77
      input_type: input
      data_type: int16

    - name: "Growatt Battery Power Low"
      slave: 1
      address: 78
      input_type: input
      data_type: int16

    # AC Output Sensors
    - name: "Growatt AC Output Voltage"
      slave: 1
      address: 22
      input_type: input
      data_type: uint16
      scale: 0.1
      unit_of_measurement: "V"

    - name: "Growatt AC Output Frequency"
      slave: 1
      address: 23
      input_type: input
      data_type: uint16
      scale: 0.01
      unit_of_measurement: "Hz"

    # AC Charge Power (High & Low)
    - name: "Growatt AC Charge Power High"
      slave: 1
      address: 13
      input_type: input
      data_type: uint16

    - name: "Growatt AC Charge Power Low"
      slave: 1
      address: 14
      input_type: input
      data_type: uint16

    # Grid Sensors
    - name: "Growatt Grid Voltage"
      slave: 1
      address: 20
      input_type: input
      data_type: uint16
      scale: 0.1
      unit_of_measurement: "V"

    - name: "Growatt Grid Frequency"
      slave: 1
      address: 21
      input_type: input
      data_type: uint16
      scale: 0.01
      unit_of_measurement: "Hz"

    # Load Power
    - name: "Growatt Load Power High"
      slave: 1
      address: 9
      input_type: input
      data_type: int16

    - name: "Growatt Load Power Low"
      slave: 1
      address: 10
      input_type: input
      data_type: int16

    # Load Percentage
    - name: "Growatt Load Percentage"
      slave: 1
      address: 27
      input_type: input
      data_type: uint16
      scale: 0.1
      unit_of_measurement: "%"

    # Fan Speeds
    - name: "Growatt MPPT Fan Speed"
      slave: 1
      address: 81
      input_type: input
      data_type: uint16
      unit_of_measurement: "%"

    - name: "Growatt Inverter Fan Speed"
      slave: 1
      address: 82
      input_type: input
      data_type: uint16
      unit_of_measurement: "%"

    # Fault Codes
    - name: "Growatt Fault Bit"
      slave: 1
      address: 40
      input_type: input
      data_type: uint16

    - name: "Growatt Warning Bit"
      slave: 1
      address: 41
      input_type: input
      data_type: uint16

    - name: "Growatt Fault Value"
      slave: 1
      address: 42
      input_type: input
      data_type: uint16

    - name: "Growatt Warning Value"
      slave: 1
      address: 43
      input_type: input
      data_type: uint16

    # Work Time Total (High & Low)
    - name: "Growatt Work Time Total High"
      slave: 1
      address: 30
      input_type: input
      data_type: uint16

    - name: "Growatt Work Time Total Low"
      slave: 1
      address: 31
      input_type: input
      data_type: uint16

      # AC Input Power (High & Low)
    - name: "Growatt AC Input Power High"
      slave: 1
      address: 36
      input_type: input
      data_type: uint16

    - name: "Growatt AC Input Power Low"
      slave: 1
      address: 37
      input_type: input
      data_type: uint16

      # PV Energy Today & Total
    - name: "Growatt Solar Energy Today High"
      slave: 1
      address: 48
      input_type: input
      data_type: uint16

    - name: "Growatt Solar Energy Today Low"
      slave: 1
      address: 49
      input_type: input
      data_type: uint16

    - name: "Growatt Solar Energy Total High"
      slave: 1
      address: 50
      input_type: input
      data_type: uint16

    - name: "Growatt Solar Energy Total Low"
      slave: 1
      address: 51
      input_type: input
      data_type: uint16

    # AC Charge Energy
    - name: "Growatt AC Charge Energy Today High"
      slave: 1
      address: 56
      input_type: input
      data_type: uint16

    - name: "Growatt AC Charge Energy Today Low"
      slave: 1
      address: 57
      input_type: input
      data_type: uint16

    - name: "Growatt AC Charge Energy Total High"
      slave: 1
      address: 58
      input_type: input
      data_type: uint16

    - name: "Growatt AC Charge Energy Total Low"
      slave: 1
      address: 59
      input_type: input
      data_type: uint16

    # Battery Discharge Energy
    - name: "Growatt Battery Discharge Energy Today High"
      slave: 1
      address: 60
      input_type: input
      data_type: uint16

    - name: "Growatt Battery Discharge Energy Today Low"
      slave: 1
      address: 61
      input_type: input
      data_type: uint16

    - name: "Growatt Battery Discharge Energy Total High"
      slave: 1
      address: 62
      input_type: input
      data_type: uint16

    - name: "Growatt Battery Discharge Energy Total Low"
      slave: 1
      address: 63
      input_type: input
      data_type: uint16

    # AC Discharge Energy
    - name: "Growatt AC Discharge Energy Today High"
      slave: 1
      address: 64
      input_type: input
      data_type: uint16

    - name: "Growatt AC Discharge Energy Today Low"
      slave: 1
      address: 65
      input_type: input
      data_type: uint16

    - name: "Growatt AC Discharge Energy Total High"
      slave: 1
      address: 66
      input_type: input
      data_type: uint16

    - name: "Growatt AC Discharge Energy Total Low"
      slave: 1
      address: 67
      input_type: input
      data_type: uint16

    # AC Charge Current & AC Discharge Power
    - name: "Growatt AC Charge Battery Current"
      slave: 1
      address: 68
      input_type: input
      data_type: uint16
      scale: 0.1
      unit_of_measurement: "A"

    - name: "Growatt AC Discharge Power High"
      slave: 1
      address: 69
      input_type: input
      data_type: uint16

    - name: "Growatt AC Discharge Power Low"
      slave: 1
      address: 70
      input_type: input
      data_type: uint16


  • Then go to the templates.yaml and paste this code inside it.
- sensor:
    # Growatt Output Power
    - name: "Growatt Output Power"
      unit_of_measurement: "W"
      state_class: measurement
      device_class: power
      state: >
        {% set high = states('sensor.growatt_output_power_high') | int(0) %}
        {% set low = states('sensor.growatt_output_power_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt Battery Power
    - name: "Growatt Battery Power"
      unit_of_measurement: "W"
      state_class: measurement
      device_class: power
      state: >
        {% set high = states('sensor.growatt_battery_power_high') | int(0) %}
        {% set low = states('sensor.growatt_battery_power_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt PV1 Charge Power
    - name: "Growatt PV1 Charge Power"
      unit_of_measurement: "W"
      state_class: measurement
      device_class: power
      state: >
        {% set high = states('sensor.growatt_pv1_charge_power_high') | int(0) %}
        {% set low = states('sensor.growatt_pv1_charge_power_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt PV2 Charge Power
    - name: "Growatt PV2 Charge Power"
      unit_of_measurement: "W"
      state_class: measurement
      device_class: power
      state: >
        {% set high = states('sensor.growatt_pv2_charge_power_high') | int(0) %}
        {% set low = states('sensor.growatt_pv2_charge_power_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt Load Power
    - name: "Growatt Load Power"
      unit_of_measurement: "W"
      state_class: measurement
      device_class: power
      state: >
        {% set high = states('sensor.growatt_load_power_high') | int(0) %}
        {% set low = states('sensor.growatt_load_power_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt Load Power Alternative (Handles Negative Power)
    - name: "Growatt Load Power Signed"
      unit_of_measurement: "W"
      state_class: measurement
      device_class: power
      state: >
        {% set high = states('sensor.growatt_load_power_high') | int(0) %}
        {% set low = states('sensor.growatt_load_power_low') | int(0) %}
        {% if high > 32767 %}  # Handle negative values (Two's complement)
          {% set high = high - 65536 %}
        {% endif %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt AC Charge Power
    - name: "Growatt AC Charge Power"
      unit_of_measurement: "W"
      state_class: measurement
      device_class: power
      state: >
        {% set high = states('sensor.growatt_ac_charge_power_high') | int(0) %}
        {% set low = states('sensor.growatt_ac_charge_power_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt Work Time Total
    - name: "Growatt Work Time Total"
      unit_of_measurement: "Hours"
      state_class: measurement
      state: >
        {% set high = states('sensor.growatt_work_time_total_high') | int(0) %}
        {% set low = states('sensor.growatt_work_time_total_low') | int(0) %}
        {% set total_seconds = ((high * 65536) + low) * 0.5 %}
        {{ (total_seconds / 3600) | round(2) }} # Convert seconds to hours

    # Growatt AC Input Power
    - name: "Growatt AC Input Power"
      unit_of_measurement: "W"
      state_class: measurement
      device_class: power
      state: >
        {% set high = states('sensor.growatt_ac_input_power_high') | int(0) %}
        {% set low = states('sensor.growatt_ac_input_power_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

      # Growatt Solar Energy Today
    - name: "Growatt Solar Energy Today"
      unit_of_measurement: "kWh"
      state_class: total_increasing
      state: >
        {% set high = states('sensor.growatt_solar_energy_today_high') | int(0) %}
        {% set low = states('sensor.growatt_solar_energy_today_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt Solar Energy Total
    - name: "Growatt Solar Energy Total"
      unit_of_measurement: "kWh"
      state_class: total_increasing
      state: >
        {% set high = states('sensor.growatt_solar_energy_total_high') | int(0) %}
        {% set low = states('sensor.growatt_solar_energy_total_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt AC Charge Energy Today
    - name: "Growatt AC Charge Energy Today"
      unit_of_measurement: "kWh"
      state_class: total_increasing
      state: >
        {% set high = states('sensor.growatt_ac_charge_energy_today_high') | int(0) %}
        {% set low = states('sensor.growatt_ac_charge_energy_today_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt AC Charge Energy Total
    - name: "Growatt AC Charge Energy Total"
      unit_of_measurement: "kWh"
      state_class: total_increasing
      state: >
        {% set high = states('sensor.growatt_ac_charge_energy_total_high') | int(0) %}
        {% set low = states('sensor.growatt_ac_charge_energy_total_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt AC Discharge Power
    - name: "Growatt AC Discharge Power"
      unit_of_measurement: "W"
      state_class: measurement
      device_class: power
      state: >
        {% set high = states('sensor.growatt_ac_discharge_power_high') | int(0) %}
        {% set low = states('sensor.growatt_ac_discharge_power_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt AC Discharge Energy Today
    - name: "Growatt AC Discharge Energy Today"
      unit_of_measurement: "kWh"
      state_class: total_increasing
      state: >
        {% set high = states('sensor.growatt_ac_discharge_energy_today_high') | int(0) %}
        {% set low = states('sensor.growatt_ac_discharge_energy_today_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt AC Discharge Energy Total
    - name: "Growatt AC Discharge Energy Total"
      unit_of_measurement: "kWh"
      state_class: total_increasing
      state: >
        {% set high = states('sensor.growatt_ac_discharge_energy_total_high') | int(0) %}
        {% set low = states('sensor.growatt_ac_discharge_energy_total_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt Battery Discharge Energy Today
    - name: "Growatt Battery Discharge Energy Today"
      unit_of_measurement: "kWh"
      state_class: total_increasing
      state: >
        {% set high = states('sensor.growatt_battery_discharge_energy_today_high') | int(0) %}
        {% set low = states('sensor.growatt_battery_discharge_energy_today_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt Battery Discharge Energy Total
    - name: "Growatt Battery Discharge Energy Total"
      unit_of_measurement: "kWh"
      state_class: total_increasing
      state: >
        {% set high = states('sensor.growatt_battery_discharge_energy_total_high') | int(0) %}
        {% set low = states('sensor.growatt_battery_discharge_energy_total_low') | int(0) %}
        {{ ((high * 65536) + low) * 0.1 }}

    # Growatt System Status (Human-Readable)
    - name: "Growatt System Status Description"
      state: >
        {% set status = states('sensor.growatt_system_status') | int(0) %}
        {% if status == 0 %} Standby
        {% elif status == 1 %} No Use
        {% elif status == 2 %} Discharge
        {% elif status == 3 %} Fault
        {% elif status == 4 %} Flash
        {% elif status == 5 %} PV charge
        {% elif status == 6 %} AC charge
        {% elif status == 7 %} Combine charge
        {% elif status == 8 %} Combine charge and Bypass
        {% elif status == 9 %} PV charge and Bypass
        {% elif status == 10 %} AC charge and Bypass
        {% elif status == 11 %} Bypass
        {% elif status == 12 %} PV charge and Discharge
        {% else %} Unknown Status ({{ status }})
        {% endif %}


  • Finally restart your Home Assistant network.

Conclusion

Now the Modbus sensor entities we created should be showing up in Home Assistant. You can confirm by going to the Entities tab in Home Assistant and filtering by Modbus integration or you search “Growatt” there.


Also for some context, you would observe in the modbus.yaml file, there are High and Low of some values. Those values are stored as two registers (high and low bytes), so they need to be combined correctly. Since Home Assistant doesn’t support direct bit-shifting in Modbus, we use template sensors in the templates.yaml file to combine the values.


Thanks for reading and feel free to share your experience in the comment section.