Skip to content

ESPHome

stefanschoemaker edited this page May 14, 2025 · 1 revision

Hardware

Use this guide to connect to the inverter: https://github.com/klatremis/esphome-for-growatt#wiring-of-generic-esp32--ttl-module

I had success using this RS485 adapter: https://www.amazon.nl/dp/B09VGJCJKQ

ESPHOME Config

Replace all values with ###SOMETHING####

esphome:
  name: growatt
  friendly_name: Omvormer

esp8266:
  board: d1_mini

# Enable logging
logger:
  baud_rate: 0

# Enable Home Assistant API
api:
  encryption:
    key: ###YOURAPIKEY###

ota:
  - platform: esphome
    password: ###YOURPASSWORD###

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Growatt Fallback Hotspot"
    password: ###WIFIPASSWORD###

captive_portal:

substitutions:
  name: growatt
  device_name: Omvormer #name for all sensors in Home assistant
  device_description: "Esphome for growatt"
  modbus_controller_id: controller1
  modbus_update_interval: 15s #normal update
  skip_updates_slow: "6"  #how many times to skip normal update for slow updating sensors
  update_fast: 8s
  update_slow: 60s

uart:
  id: mod_bus
  tx_pin: RX
  rx_pin: TX
  baud_rate: 9600
  stop_bits: 1
 
modbus:
  id: modbus1
# flow_control_pin: 4 #for use when you use a rs485 board without auto flow control. Like the rs485 max board.
 
modbus_controller:
  - id: ${modbus_controller_id}
    address: 0x1
    modbus_id: modbus1
    setup_priority: -10
    update_interval: ${modbus_update_interval}

sensor:
  - platform: modbus_controller
    name: ${device_name} status_code
    skip_updates: $skip_updates_slow
    address: 0
    register_type: "read"
    internal: true
    accuracy_decimals: 0
    id: status

  - platform: modbus_controller
    name: "${device_name} Input Power"
    address: 1
    register_type: "read"
    unit_of_measurement: kW
    device_class: power
    state_class: measurement
    icon: mdi:solar-power-variant
    value_type: U_WORD
    accuracy_decimals: 3
    filters:
      - multiply: 0.0001      
  
  - platform: modbus_controller
    name: "${device_name} PV1 voltage"
    address: 3
    register_type: "read"
    unit_of_measurement: V
    device_class: voltage
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 0
    filters:
      - multiply: 0.1
    
  - platform: modbus_controller
    name: "${device_name} PV1 current"
    address: 4
    register_type: "read"
    unit_of_measurement: A
    device_class: current
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1

  - platform: modbus_controller
    name: "${device_name} PV1 power"
    id: pv1_power
    address: 5
    register_type: "read"
    unit_of_measurement: kW
    device_class: power
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 3
    filters:
      - multiply: 0.0001

  - platform: modbus_controller
    name: "${device_name} PV2 voltage"
    address: 7
    register_type: "read"
    unit_of_measurement: V
    device_class: voltage
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 0
    filters:
      - multiply: 0.1
    
  - platform: modbus_controller
    name: "${device_name} PV2 current"
    address: 8
    register_type: "read"
    unit_of_measurement: A
    device_class: current
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1

  - platform: modbus_controller
    name: "${device_name} PV2 power"
    id: pv2_power
    address: 9
    register_type: "read"
    unit_of_measurement: kW
    device_class: power
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 3
    filters:
      - multiply: 0.0001

  - platform: template
    id: pv_power
    name: "${device_name} PV Power"
    unit_of_measurement: kW
    device_class: power
    state_class: measurement
    icon: mdi:solar-power-variant
    accuracy_decimals: 3
    lambda: |-
      return float((id(pv1_power).state + id(pv2_power).state));
    filters:
      # - throttle_average: 30s
      - filter_out: nan

  - platform: modbus_controller
    name: "${device_name} Output power"
    address: 35
    register_type: "read"
    unit_of_measurement: kW
    device_class: power
    state_class: measurement
    icon: mdi:solar-power
    value_type: U_DWORD
    accuracy_decimals: 3
    filters:
      - multiply: 0.0001
      # - throttle_average: 30s
      - filter_out: nan 

  - platform: modbus_controller
    name: "${device_name} Energy total"
    skip_updates: $skip_updates_slow
    address: 55
    register_type: "read"
    unit_of_measurement: kWh
    state_class: total_increasing
    device_class: energy
    icon: mdi:solar-power
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1
    
  - platform: modbus_controller
    name: "${device_name} PV1 energy total"
    skip_updates: $skip_updates_slow
    id: pv1_energy_total
    address: 61
    register_type: "read"
    unit_of_measurement: kWh
    state_class: total_increasing
    device_class: energy
    icon: mdi:solar-power
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1
    
  - platform: modbus_controller
    name: "${device_name} PV2 energy total"
    skip_updates: $skip_updates_slow
    id: pv2_energy_total
    address: 65
    register_type: "read"
    unit_of_measurement: kWh
    state_class: total_increasing
    device_class: energy
    icon: mdi:solar-power
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1

  - platform: template
    id: pv_energy_total
    name: "${device_name} PV Energy Total"
    unit_of_measurement: kWh
    device_class: energy
    state_class: total_increasing
    icon: mdi:solar-power-variant
    accuracy_decimals: 1
    lambda: |-
      return float((id(pv1_energy_total).state + id(pv2_energy_total).state));
    update_interval: ${update_slow}

  - platform: modbus_controller
    name: "${device_name} Temperature"
    address: 93
    register_type: "read"
    unit_of_measurement: °C
    device_class: temperature
    entity_category: diagnostic
    icon: mdi:thermometer
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1
    skip_updates: $skip_updates_slow

  - platform: modbus_controller
    name: "${device_name} Battery Discharging Power"
    address: 1009
    register_type: "read"
    unit_of_measurement: kW
    device_class: power
    icon: mdi:battery-arrow-down
    value_type: U_DWORD
    accuracy_decimals: 3
    filters:
      - multiply: 0.0001       

  - platform: modbus_controller
    name: "${device_name} Battery Charging Power"
    address: 1011
    register_type: "read"
    unit_of_measurement: kW
    device_class: power
    icon: mdi:battery-arrow-up-outline
    value_type: U_DWORD
    accuracy_decimals: 3
    filters:
      - multiply: 0.0001     

  - platform: modbus_controller
    name: "${device_name} Battery SoC"
    skip_updates: $skip_updates_slow
    address: 1014
    register_type: "read"
    unit_of_measurement: "%"
    icon: mdi:home-battery
    value_type: U_WORD
    accuracy_decimals: 0

  - platform: modbus_controller
    name: "${device_name} AC Power to User Total"
    address: 1021
    register_type: "read"
    unit_of_measurement: kW
    device_class: power
    state_class: measurement
    icon: mdi:transmission-tower-export
    value_type: U_DWORD
    accuracy_decimals: 3
    filters:
      - multiply: 0.0001

  - platform: modbus_controller
    name: "${device_name} AC Power to Grid Total"
    address: 1029
    register_type: "read"
    unit_of_measurement: kW
    device_class: power
    state_class: measurement
    icon: mdi:transmission-tower-import
    value_type: U_DWORD
    accuracy_decimals: 3
    filters:
      - multiply: 0.0001

  - platform: modbus_controller
    name: "${device_name} INV Power to Local Load Total"
    address: 1037
    register_type: "read"
    unit_of_measurement: kW
    device_class: power
    state_class: measurement    
    icon: mdi:home-import-outline
    value_type: U_DWORD
    accuracy_decimals: 3
    filters:
      - multiply: 0.0001

  - platform: modbus_controller
    name: "${device_name} Battery Temperature"
    address: 1040
    register_type: "read"
    unit_of_measurement: °C
    device_class: temperature
    state_class: measurement    
    icon: mdi:thermometer
    skip_updates: $skip_updates_slow
    value_type: U_WORD
    accuracy_decimals: 1

  - platform: modbus_controller
    name: "${device_name} Battery State"
    skip_updates: $skip_updates_slow
    address: 1041
    register_type: "read"
    icon: mdi:home-battery
    value_type: U_WORD
    
  - platform: modbus_controller
    name: "${device_name} Energy to User Total"
    skip_updates: $skip_updates_slow
    address: 1046
    register_type: "read"
    unit_of_measurement: kWh
    state_class: total_increasing
    device_class: energy
    icon: mdi:transmission-tower-export
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1

  - platform: modbus_controller
    name: "${device_name} Energy to Grid Total"
    skip_updates: $skip_updates_slow
    address: 1050
    register_type: "read"
    unit_of_measurement: kWh
    state_class: total_increasing
    device_class: energy
    icon: mdi:transmission-tower-import
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1

  - platform: modbus_controller
    name: "${device_name} Battery Discharge Total"
    skip_updates: $skip_updates_slow
    address: 1054
    register_type: "read"
    unit_of_measurement: kWh
    state_class: total_increasing
    device_class: energy
    icon: mdi:battery-arrow-down
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1

  - platform: modbus_controller
    name: "${device_name} Battery Charge Total"
    skip_updates: $skip_updates_slow
    address: 1058
    register_type: "read"
    unit_of_measurement: kWh
    state_class: total_increasing
    device_class: energy
    icon: mdi:battery-arrow-up-outline
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1

  - platform: modbus_controller
    name: "${device_name} Local Load Total"
    skip_updates: $skip_updates_slow
    address: 1062
    register_type: "read"
    unit_of_measurement: kWh
    state_class: total_increasing
    device_class: energy
    icon: mdi:home-import-outline
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1

number:
  - platform: modbus_controller
    name: "${device_name} Discharge SoC"
    address: 1071
    value_type: U_WORD
    min_value: 0
    max_value: 100
    step: 1
    skip_updates: $skip_updates_slow

  - platform: modbus_controller
    name: "${device_name} Discharge Power Rate Percentage"
    address: 1070
    value_type: U_WORD
    min_value: 0
    max_value: 100
    step: 1
    skip_updates: $skip_updates_slow
      
  - platform: modbus_controller
    name: "${device_name} Charge SoC"
    address: 1091
    value_type: U_WORD
    min_value: 0
    max_value: 100
    step: 1
    skip_updates: $skip_updates_slow

  - platform: modbus_controller
    name: "${device_name} Charge Power Rate Percentage"
    address: 1090
    value_type: U_WORD
    min_value: 0
    max_value: 100
    step: 1

  # - platform: modbus_controller
  #   name: "${device_name} Export Limit Power Rate Percentage"
  #   address: 123
  #   value_type: S_WORD
  #   min_value: -100.0
  #   max_value: 100.0
  #   step: 0.1
  #   lambda: |-
  #     // Convert the raw value (e.g., 1000) to a percentage (100.0)
  #     return x / 10.0;
  #   write_lambda: |-
  #     // Convert the percentage back to the raw value for Modbus (e.g., 100.0 to 1000)
  #     return x * 10;

  - platform: modbus_controller
    name: "Export Limit Power Rate Percentage"
    address: 3
    value_type: U_WORD
    min_value: 0
    max_value: 100
    step: 1

  - platform: modbus_controller
    id: bat_first_start
    address: 1100
    value_type: U_WORD
    internal: true

  - platform: modbus_controller
    id: bat_first_stop
    address: 1101
    value_type: U_WORD
    internal: true

  - platform: modbus_controller
    id: bat_first_enable
    address: 1102
    value_type: U_WORD
    internal: true

  - platform: modbus_controller
    id: grid_first_start
    address: 1080
    value_type: U_WORD
    internal: true

  - platform: modbus_controller
    id: grid_first_stop
    address: 1081
    value_type: U_WORD
    internal: true

  - platform: modbus_controller
    id: grid_first_enable
    address: 1082
    value_type: U_WORD
    internal: true

  - platform: modbus_controller
    id: load_first_enable
    address: 122
    value_type: U_WORD
    internal: true
    
select:
  - platform: modbus_controller
    name: "${device_name} AC Charging"
    icon: mdi:battery-charging-100
    address: 1092
    value_type: U_WORD
    optionsmap:
      "Disabled": 0
      "Enabled": 1
    skip_updates: $skip_updates_slow

  - platform: modbus_controller
    name: "${device_name} Inverter Priority"
    icon: mdi:arrow-decision-outline
    id: inverter_priority
    address: 1044
    value_type: U_WORD
    optionsmap:
      "Load First": 0
      "Battery First": 1
      "Grid First": 2  
    skip_updates: $skip_updates_slow

  - platform: modbus_controller
    name: "Save Export limit to memory"
    icon: mdi:memory
    address: 2
    value_type: U_WORD
    optionsmap:
      "Save to RAM": 0
      "Save to EEPROM": 1
    skip_updates: $skip_updates_slow

text_sensor:
  - platform: template
    name: "${device_name} Status state"
    icon: mdi:eye
    entity_category: diagnostic
    lambda: |-
      if ((id(status).state) == 1) {
        return {"Normal"};
      } else if ((id(status).state) == 0)  {
        return {"Standby"};
      } else if ((id(status).state) == 2)  {
        return {"Discharge"};
      } else if ((id(status).state) == 3)  {
        return {"Fault"};
      } else if ((id(status).state) == 4)  {
        return {"Flash"};
      } else if ((id(status).state) == 5)  {
        return {"PV Charging"};
      } else if ((id(status).state) == 6)  {
        return {"AC Charging"};
      } else if ((id(status).state) == 7)  {
        return {"Combined Charging"};
      } else if ((id(status).state) == 8)  {
        return {"Combined Charging & Bypass"};
      } else if ((id(status).state) == 9)  {
        return {"PV Charging & Bypass"};
      } else if ((id(status).state) == 10)  {
        return {"AC Charging & Bypass"};
      } else if ((id(status).state) == 11)  {
        return {"Bypass"};
      } else if (id(status).state == 12)  {
        return {"PV Charge and Discharge"};
      } else {
        return {"Unknown"};
      }

  - platform: modbus_controller
    name: "${device_name} Fault code"
    skip_updates: $skip_updates_slow
    address: 105
    register_type: "read"
    icon: mdi:eye
    entity_category: diagnostic
    raw_encode: HEXBYTES
    lambda: |-
      uint16_t value = modbus_controller::word_from_hex_str(x, 0);
      switch (value) {
        case 0:  return std::string("No error");
        case 24: return std::string("Auto test failed");
        case 25: return std::string("No AC connection");
        case 26: return std::string("PV isolation low");
        case 27: return std::string("Residual I high");
        case 28: return std::string("Output high DCI");
        case 29: return std::string("PV voltage high");
        case 30: return std::string("AC voltage out of range");
        case 31: return std::string("AC frequency out of range");
        case 32: return std::string("Module too hot");
        // case 1~23 " Error: 99+x
        default: return std::string("Fault code: " + to_string(value));
      }
      return x;

switch:
  - platform: template
    name: "${device_name} Battery First Mode"
    icon: mdi:battery-clock
    lambda: |-
      return id(inverter_priority).state == "Battery First";
    turn_on_action:
      - number.set:
          id: grid_first_start
          value: 0
      - delay: 100ms
      - number.set:
          id: grid_first_stop
          value: 0
      - delay: 100ms
      - number.set:
          id: grid_first_enable
          value: 0     # Disable
      - number.set:
          id: bat_first_start
          value: 1    # 00:01
      - delay: 100ms
      - number.set:
          id: bat_first_stop
          value: 5947 # 23:59
      - delay: 100ms
      - number.set:
          id: bat_first_enable
          value: 1    # Enable
    turn_off_action:
      - number.set:
          id: bat_first_start
          value: 0    # 00:00
      - delay: 100ms
      - number.set:
          id: bat_first_stop
          value: 0    # 00:00
      - number.set:
          id: bat_first_enable
          value: 0    # Disable
    optimistic: false

  - platform: template
    name: "${device_name} Grid First Mode"
    icon: mdi:battery-clock
    lambda: |-
      return id(inverter_priority).state == "Grid First";
    turn_on_action:
      - number.set:
          id: bat_first_start
          value: 0    # 00:00
      - delay: 100ms
      - number.set:
          id: bat_first_stop
          value: 0    # 00:00
      - number.set:
          id: bat_first_enable
          value: 0    # Disable
      - number.set:
          id: grid_first_start
          value: 1     # 00:01
      - delay: 100ms
      - number.set:
          id: grid_first_stop
          value: 5947  # 23:59
      - delay: 100ms
      - number.set:
          id: grid_first_enable
          value: 1     # Enable
    turn_off_action:
      - number.set:
          id: grid_first_start
          value: 0
      - delay: 100ms
      - number.set:
          id: grid_first_stop
          value: 0
      - delay: 100ms
      - number.set:
          id: grid_first_enable
          value: 0     # Disable
    optimistic: false

  - platform: template
    name: "${device_name} Load First Mode"
    icon: mdi:battery-clock
    lambda: |-
      return id(inverter_priority).state == "Load First";
    turn_on_action:
      - number.set:
          id: bat_first_start
          value: 0    # 00:00
      - delay: 100ms
      - number.set:
          id: bat_first_stop
          value: 0    # 00:00
      - number.set:
          id: bat_first_enable
          value: 0    # Disable
      - number.set:
          id: grid_first_start
          value: 0     # 00:01
      - delay: 100ms
      - number.set:
          id: grid_first_stop
          value: 0  # 23:59
      - delay: 100ms
      - number.set:
          id: load_first_enable
          value: 1     # Enable
    turn_off_action:
      - number.set:
          id: load_first_enable
          value: 0     # Disable
    optimistic: false

Clone this wiki locally