Smart Metering Growatt 600TL-X + ShineWifi-X with ESPHome + Shelly 3EM

Hello,

I’m not really good at programming but would like to share my smart meter that I created via Home Assistant.
My setup is a Growatt Inverter 600TL-X with a ShineWifi-X stick running ESPHome.
All energy is monitored by a Shelly3EM and a Shelly1PM (inverter).

The inverter is configured to produce 2000W - yes, it’s a 600W inverter, but who cares? :slight_smile:
The steps are 1-100, which corresponds to 20W per step.

The output power of the inverter should always be 60W above the actual consumption.
If the consumption is less than 100W, the inverter should always generate 120W (equivalent to 6).
Over 2000W consumption = set inverter to 100 - maximum power, regardless of whether there is enough sun or not.

Also, the automation only triggers every 10 seconds because the inverter is slow and can’t ramp up and down very quickly.

The ShineWifi-X Stick is flashed with ESPHome, this is the current Yaml code:

ESPHome YAML Code
substitutions:
  devicename: growattwr
  upper_devicename: growattwr-stick
  
esphome:
  name: $devicename
  platform: ESP8266
  board: esp07s
  #esp8266_restore_from_flash: true
  on_boot:
    priority: 600
    then:
      - delay: 10s
      - number.set:
          id: poweroutput
          value: 35        
        
logger:
  baud_rate: 0

api:
  encryption:
    key: ""

ota:
  password: ""
    
wifi:
  ssid: "SSID"
  password: ""
  fast_connect: true
  manual_ip:
    static_ip: 192.168.178.138
    gateway: 192.168.178.1
    subnet: 255.255.255.0
    dns1: 192.168.178.10
  ap:
    ssid: "Growatt Fallback Hotspot"
    password: ""

captive_portal:

web_server:
 port: 80

time:
  - platform: homeassistant
    id: homeassistant_time

output:
# Blue Led
  - id: light_bl
    platform: gpio
    pin: 16
# Green Led
  - id: light_gr
    platform: gpio
    pin: 0
# Red Led
  - id: light_rd
    platform: gpio
    pin: 2
    
uart:
 id: mod_bus
 tx_pin: 1
 rx_pin: 3
 baud_rate: 115200

modbus:
 id: modbus1
 uart_id: mod_bus
 flow_control_pin: GPIO4

modbus_controller:
 - id: growatt
   address: 0x1
   modbus_id: modbus1
   setup_priority: -10
   update_interval: 10s
   #command_throttle: 1s
   
text_sensor:
  - platform: template
    name: "${devicename} Status"
    icon: mdi:eye
    entity_category: diagnostic
    lambda: |-
      if (id(status).state == 1) {
        return {"Normal"};
      } else if (id(status).state == 0)  {
        return {"Waiting"};
      } else {
        return {"Fault!"};
      }

button:
  - platform: restart
    name: "Stick Restart"
    
switch:
  - platform: modbus_controller
    name: "${devicename} OnOff"
    address: 0
    register_type: holding   
    
number:
 - platform: modbus_controller
   name: "${devicename} Ausgangspower"
   id: poweroutput
   address: 3
   value_type: U_WORD
   min_value: 0
   max_value: 100
   entity_category: config
   
select:
  - platform: modbus_controller
    id: powermode
    name: "${devicename} Power Mode"
    address: 121
    value_type: U_WORD
    optionsmap:
      "600": 6
      "750": 7
      "1000": 10
      "1500": 15
      "2000": 20
    entity_category: config
    skip_updates: 60    
   
sensor:
  - platform: wifi_signal
    name: "${devicename} WiFi Signal"
    update_interval: 360s
    entity_category: diagnostic   
   
  - platform: modbus_controller
    address: 0
    register_type: "read"
    internal: true
    id: status
    skip_updates: 6
    
  - platform: modbus_controller
    name: "${devicename} DC Power"
    address: 3005 #5
    register_type: "read"
    unit_of_measurement: W
    device_class: power
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    skip_updates: 3
  - platform: modbus_controller
    name: "${devicename} DC Volt"
    address: 3003 #3
    register_type: "read"
    unit_of_measurement: V
    device_class: voltage
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    skip_updates: 6

  - platform: modbus_controller
    name: "${devicename} DC Strom"
    address: 3004 #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
    skip_updates: 6

  - platform: modbus_controller
    name: "${devicename} AC Power"
    address: 3023 #40
    register_type: "read"
    unit_of_measurement: W
    device_class: power
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    skip_updates: 6
    
  - platform: modbus_controller
    name: "${devicename} AC Frequenz"
    address: 3025 #37
    register_type: "read"
    unit_of_measurement: Hz
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.01
    skip_updates: 6

  - platform: modbus_controller
    name: "${devicename} AC Volt"
    address: 3026 #38
    register_type: "read"
    unit_of_measurement: V
    device_class: voltage
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    skip_updates: 6

  - platform: modbus_controller
    name: "${devicename} AC Strom"
    address: 3027 #39
    register_type: "read"
    unit_of_measurement: A
    device_class: current
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    skip_updates: 6

  - platform: modbus_controller
    name: "${devicename} AC Power VA"
    address: 3028 #40
    register_type: "read"
    unit_of_measurement: VA
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    skip_updates: 6
 
  - platform: modbus_controller
    name: "${devicename} Energy Heute"
    address: 3049 #53
    register_type: "read"
    unit_of_measurement: kWh
    device_class: energy
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    skip_updates: 6

  - platform: modbus_controller
    name: "${devicename} Energy Total"
    address: 3051 #55
    register_type: "read"
    unit_of_measurement: kWh
    state_class: total_increasing
    device_class: energy
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    skip_updates: 6
    
  - platform: modbus_controller
    name: "${devicename} Temperatur"
    address: 3093
    register_type: "read"
    unit_of_measurement: °C
    device_class: temperature
    icon: mdi:thermometer
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    skip_updates: 6

What does this Automation do ?

Show me....
action:
  - service: number.set_value
    target:
      entity_id: number.growattwr_ausgangspower
    data_template:
      value: >
        {% set current_consumption = states('sensor.aktuellerverbrauch') | int %}
        {% set calculated_value = ((current_consumption + 60)) // 20 %}
        {% if calculated_value > 100 %}
          100
        {% elif current_consumption < 100 %}
          6
        {% else %}
          {{ calculated_value }}
        {% endif %}
    • Service: number.set_value indicates that the action is to set the value of a number entity.
  • Target: entity_id: number.growattwr_ausgangspower specifies the entity whose value will be set. In this case, it’s likely a number entity representing the output power of a device.
  • Data Template: This section allows dynamic data manipulation based on the current state of other entities. It calculates a new value for the number.growattwr_ausgangspower entity based on the current consumption (sensor.aktuellerverbrauch).
  1. Data Template Explanation: Let’s break down what the data template does:
  • Setting Variables: It sets two variables:
    • current_consumption: Extracts the current consumption from sensor.aktuellerverbrauch and converts it to an integer.
    • calculated_value: Calculates a new value based on the current consumption.
  • Calculating New Value:
    • calculated_value is computed by adding 60 to the current consumption and then dividing by 20. This seems to represent some kind of scaling operation.
  • Conditional Output:
    • If the calculated value exceeds 100, it caps it at 100.
    • If the current consumption is less than 100, it sets the value to 6.
    • Otherwise, it uses the calculated value.

Now, let’s do some math examples with different power consumptions:

  1. For 80W:
  • calculated_value = ((80 + 60) // 20) = 140 // 20 = 7
  • Since 7 is less than 100, the output will be 7.
  1. For 1200W:
  • calculated_value = ((1200 + 60) // 20) = 1260 // 20 = 63
  • Since 63 is less 100, the output will be 63.
  1. For 2500W:
  • calculated_value = ((2500 + 60) // 20) = 2560 // 20 = 128
  • Since 128 exceeds 100, the output will be capped at 100.

Here is the full YAML:

Full Automation YAML
alias: 00// Test Smart Meter Growatt ESPHome / Shelly3EM
description: ""
trigger:
  - platform: time_pattern
    seconds: /10
    alias: query every 10s
condition:
  - condition: and
    conditions:
      - condition: sun
        after: sunrise
        before: sunset
        after_offset: "+00:30:00"
        alias: only trigger 30Min after sunrise and stop at sunset
      - condition: template
        value_template: "{{ states('number.growattwr_ausgangspower') != 'unavailable' }}"
        alias: if Growatt is 'unavailable' dont set - (offline)
      - condition: template
        value_template: >
          {{ states('number.growattwr_ausgangspower') | int !=
          ((states('sensor.aktuellerverbrauch') | int + 60)) // 20 }}
        alias: >-
          test whether “number.growattwr_ausgangspower” corresponds to the value
          to be set
    alias: test if 3 conditions match
action:
  - service: number.set_value
    target:
      entity_id: number.growattwr_ausgangspower
    data_template:
      value: >
        {% set current_consumption = states('sensor.aktuellerverbrauch') | int
        %} {% set calculated_value = ((current_consumption + 60)) // 20%} {% if
        calculated_value > 100 %}
         100
        {% elif current_consumption < 100 %}
         6
        {% else %}
         {{ calculated_value }}
        {% endif %}
    alias: Calculation template for setting number.growattwr_ausgangspower
trace:
  stored_traces: 10
Sensors i use, written in the external sensor.yaml
# Sensor for Riemann sum of energy import (W -> Wh)
 - platform: integration
   source: sensor.aktuellerimport
   name: Import
   unit_prefix: k
   round: 2
   method: left
# Sensor for Riemann sum of energy export (W -> Wh)
 - platform: integration
   source: sensor.aktuellerexport
   name: Export
   unit_prefix: k
   round: 2
   method: left
# Sensor for Riemann sum of energy consumption (W -> Wh)
 - platform: integration
   source: sensor.aktuellerverbrauch
   name: Verbrauch
   unit_prefix: k
   round: 2
   method: left
Templates for the Shelly3EM - external template.yaml
- sensor:
#IMPORT SENSOR
  - name: aktuellerimport
    unit_of_measurement: W
    state_class: measurement
    device_class: power
    unique_id: aktuellerimport
    state:
      "{{ max(0,states('sensor.shelly3em_channel_a_power')|float +
      states('sensor.shelly3em_channel_b_power')|float +
      states('sensor.shelly3em_channel_c_power')|float )}}"
    availability:
      "{{ [ states('sensor.shelly3em_channel_a_power'),
      states('sensor.shelly3em_channel_b_power'),
      states('sensor.shelly3em_channel_c_power') ] | map('is_number') | min }}"
#EXPORT SENSOR
  - name: aktuellerexport
    unit_of_measurement: W
    state_class: measurement
    device_class: power
    unique_id: aktuellerexport
    state:
      "{{ min(0,states('sensor.shelly3em_channel_a_power')|float +
      states('sensor.shelly3em_channel_b_power')|float +
      states('sensor.shelly3em_channel_c_power')|float)|abs}}"
    availability:
      "{{ [ states('sensor.shelly3em_channel_a_power'),
      states('sensor.shelly3em_channel_b_power'),
      states('sensor.shelly3em_channel_c_power') ] | map('is_number') | min }}"
#VERBRAUCH SENSOR
  - name: aktuellerverbrauch
    unit_of_measurement: W
    state_class: measurement
    device_class: power
    unique_id: aktuellerverbrauch
    state: "{{ states('sensor.sum_channel_shelly')|float + states('sensor.shellypv_power')|float}}"
    availability:
      "{{ [ states('sensor.sum_channel_shelly'), states('sensor.shellypv_power') ]
       | map('is_number') | min }}"

If anyone has any ideas to improve the code, please let me know what you think… I’m a beginner and just proud of my first setup :slight_smile:
If I’ve forgotten anything, let me know.