ESPHome modbus Growatt ShineWiFi-S

Will try with my new firmware.

My firmware was updated today following some issues with the SoC , experienced sudden drops around 20% of SOC all the way to 10% as well as over 20% losses (eg. Gap between discharge and charge levels).

Anyone else experiencing the same issues?

Growatt have updated my BMS firmware with the following remark:

It’s important to understand, Ronald, that you have the right to raise concerns about your battery, whether it’s about programming issues, charging and discharging cycles, or any errors that occur without any changes made on your end.

However, if you’re changing the EMS commands daily, as mentioned, this is not advisable for the battery’s health. Constant changes can lead to degradation and imbalance over time, potentially necessitating battery replacement. Therefore, I strongly advise against altering the parameters frequently, whether every 5 minutes or daily.

To give you an analogy, imagine you buy an iPhone and modify its software and programming to suit your needs, but these changes damage the device beyond the manufacturer’s settings. If the phone is damaged, it’s not due to the phone itself but the software and modifications you’ve applied. That’s why I’m asking you to avoid making daily changes to the parameters.

Let’s ensure the longevity and efficiency of your battery system by sticking to recommended settings and updates.

Can confirm the ac on/off still works (sph8000 tl3 bh-up)

Hi all,
I’m attempting to flash my ShineWifi-X device with ESPHome.

I have bridged the GND and GP100 pins, and when connected the red and blue flights are lit.

I have installed the CH340 drivers on my Windows PC, and I’m using esphome web to flash (https://web.esphome.io/).

I am able to connect to the device and it shows ‘connected’, however when I click ‘initialize for first use’, it always fails to connect and displays ‘failed to initialize’.

I’ve tried different USB ports on my PC and am not using any cables, but the problem persists.

Alternatively, I also used the esphome-flasher exe tool, but it displays this error

“Unexpected error: could not open port ‘COM6’: PermissionError(13, ‘Access is denied.’, None, 5)”

I can see at least one other post here where someone had this same issue, and it looks like they just used a ESP board instead. I’d much prefer not to, given I’ve already purchased the dongle.

Has anyone else experienced this and were you able to overcome it?

Thanks!

Hi!
I finally was able to connect a ESP8266 board (DIY), and a RS485 → TTL card to my MID 10KTL3-X inverter today. The code is mostly collected here, and at a Norwegian forum. I get data from the inverter, but I also get a CRC-error for each line of data (I think…). See attached picture. I read somewhere that this can be corrected by adding this: esp32:
board: nodemcu-32s
framework:
type: arduino
version: 2.0.6

I don`t know if this is also correct for ESP8266? I used board: ESP12e in my code. Is the board type important? Can attach code later…

Thanks to this thread, I figured out a ESPHome config that works for my ShineWiFi-X. Inverter is Growatt MID-15X-TL, no batteries, just solar.

Some sensors are hashed out because I was struggling with fallouts and resets. You might need to adjust some values for your own setup.

Config is built to make sure I’m able to tune production rate close to actual consumption, to avoid export when export price is negative.

Automations in Home Assistant takes care of the adjustment the production rate.

Here goes.

growatt.yaml:

substitutions:
  device_description: Growatt MID-nnX-TL
  friendly_name: Growatt MID-nnX-TL
  sensor_prefix: MID-nnX-TL-GW1
  name: Growatt
  publish_interval: 10s
  update_realtime: 20s

 # Interval in seconds
  interval0: "1"
  interval1: "5"
  interval2: "10"
  interval3: "20"
  interval4: "30"
  interval5: "60"

esphome:
  name: growatt
  friendly_name: Growatt

globals:
  - id: last_selected_interval
    type: int
    restore_value: true
    initial_value: "10"

esp8266:
  board: esp07s

# Enable logging
logger:
  baud_rate: 0

# Mqtt
mqtt:
  broker: xxx.xxx.xxx.xxx
  username: "growatt"
  password: "*************"
  birth_message:
    topic: sensor/${friendly_name}/status
    payload: online
  will_message:
    topic: sensor/${friendly_name}/status
    payload: offline

# Enable Home Assistant API
api:
  encryption:
    key: "****************************+**************="

ota:
  password: "************3458f3dbc***********"

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: "bkAhgdXX4p2T"

captive_portal:

web_server:
  port: 80

time:
  - platform: homeassistant
    id: homeassistant_time

uart:
  id: mod_bus
  tx_pin: 1
  rx_pin: 3
  baud_rate: 115200
  
modbus:
  id: modbus1
  uart_id: mod_bus
  
modbus_controller:
  - id: growatt
    address: 0x1
    modbus_id: modbus1
    update_interval: ${interval2}s  # use the substitution variable for the update interval
    setup_priority: -10  

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

interval:
  - interval: ${publish_interval}
    then:
        - wait_until:
            mqtt.connected:
        - mqtt.publish_json:
            topic: "sensor/${friendly_name}/data"
            payload:
              root["Pv1Power"] = id(Pv1Power).state;
              
        - logger.log: "MQTT Data published"

select:
  - platform: template
    id: modbus_interval_select
    name: "Update interval seconds"
    icon: mdi:update
    optimistic: true
    restore_value: true
    initial_option: $interval2
    options:
      - $interval0
      - $interval1
      - $interval2
      - $interval3
      - $interval4
    on_value:
      then:
        - globals.set:
            id: last_selected_interval
            value: !lambda 'return std::stoi(x);'
        - wait_until:
            condition:
              lambda: 'return id(last_selected_interval) == std::stoi(x);'
        - lambda: |-
            ESP_LOGD("main", "Value updated to %s", x.c_str());

        - delay: 2s 
        - if:
            condition:
              lambda: 'return id(last_selected_interval) != 0;'
            then:
              - lambda: |-
                  int interval_ms = std::stoi(id(modbus_interval_select).state) * 1000;
                  ESP_LOGD("main", "Selected interval: %d", interval_ms);
                  id(growatt).set_update_interval(interval_ms);
                  static bool first_change_after_restart = true;
                  if (!first_change_after_restart) {
                    delay(5000);
                    ESP.restart();
                  } else {
                    first_change_after_restart = false;
                  }

text_sensor:
  - platform: template
    name: "${sensor_prefix} 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!"};
      }


#switch:
#  - platform: modbus_controller
#    name: "${sensor_prefix} OnOff"
#    address: 0
#    register_type: holding

button:
  - platform: restart
    name: "${friendly_name} Restart"

number:
  - platform: modbus_controller
    name: "${sensor_prefix} Active Power Rate"
    id: poweroutput
    address: 3
    value_type: U_WORD
    min_value: 0
    max_value: 100
    entity_category: config

sensor:
  - platform: mqtt_subscribe
    name: "${sensor_prefix} Configured Power Rate"
    id: config_power_rate
    topic: sensor/${friendly_name}/config 
    on_value:
      then:
        - lambda: |-
            esphome::modbus_controller::ModbusController *controller = id(growatt);
            uint16_t reg = 3; // Register: Max output active power (in %)
            float value = id(config_power_rate).state;
            uint16_t register_value = static_cast<uint16_t>(value);
            modbus_controller::ModbusCommandItem setOutputPower_command = modbus_controller::ModbusCommandItem::create_write_single_command(controller, reg, register_value);
            controller->queue_command(setOutputPower_command);
#        - delay: 5s 
    
  - platform: wifi_signal
    name: "WiFi Signal Sensor"
    update_interval: 60s

  - platform: modbus_controller
    address: 0
    register_type: "read"
    internal: True
    id: status

  - platform: modbus_controller
    name: "${sensor_prefix} Curr_Energy"
    address: 1
    register_type: "read"
    unit_of_measurement: W
    device_class: energy
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    register_count: 2


  - platform: modbus_controller
    name: "${sensor_prefix} Pv1 Volt"
    address: 3
    register_type: "read"
    unit_of_measurement: V
    device_class: voltage
    entity_category: diagnostic
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    register_count: 2

#  - platform: modbus_controller
#    name: "${sensor_prefix} Pv1 Amps"
#    address: 4
#    register_type: "read"
#    unit_of_measurement: A
#    device_class: current
#    entity_category: diagnostic
#    icon: mdi:flash
#    value_type: U_WORD
#    accuracy_decimals: 1
#    filters:
#    - multiply: 0.1

  - platform: modbus_controller
    name: "${sensor_prefix} Pv1 Power"
    id: Pv1Power
    address: 5
    register_type: "read"
    unit_of_measurement: W
    device_class: power
    entity_category: diagnostic
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
#    register_count: 2

  - platform: modbus_controller
    name: "${sensor_prefix} Pv2 Volt"
    address: 7
    register_type: "read"
    unit_of_measurement: V
    device_class: voltage
    entity_category: diagnostic
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    register_count: 2

#  - platform: modbus_controller
#    name: "${sensor_prefix} Pv2 Amps"
#    address: 8
#    register_type: "read"
#    unit_of_measurement: A
#    device_class: current
#    entity_category: diagnostic
#    icon: mdi:flash
#    value_type: U_WORD
#    accuracy_decimals: 1
#    filters:
#    - multiply: 0.1

  - platform: modbus_controller
    name: "${sensor_prefix} Pv2 Power"
    address: 9
    register_type: "read"
    unit_of_measurement: W
    device_class: power
    entity_category: diagnostic
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
    register_count: 2

  - platform: modbus_controller
    name: "${sensor_prefix} Pv3 Volt"
    address: 11
    register_type: "read"
    unit_of_measurement: V
    device_class: voltage
    entity_category: diagnostic
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
#    register_count: 2

#  - platform: modbus_controller
#    name: "${sensor_prefix} Pv3 Amps"
#    address: 12
#    register_type: "read"
#    unit_of_measurement: A
#    device_class: current
#    entity_category: diagnostic
#    icon: mdi:flash
#    value_type: U_WORD
#    accuracy_decimals: 1
#    filters:
#    - multiply: 0.1

  - platform: modbus_controller
    name: "${sensor_prefix} Pv3 Power"
    address: 13
    register_type: "read"
    unit_of_measurement: W
    device_class: power
    entity_category: diagnostic
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1
#    register_count: 2

  - platform: modbus_controller
    name: "${sensor_prefix} Pv4 Volt"
    address: 15
    register_type: "read"
    unit_of_measurement: V
    device_class: voltage
    entity_category: diagnostic
    icon: mdi:flash
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1    
#    register_count: 2

#  - platform: modbus_controller
#    name: "${sensor_prefix} Pv4 Amps"
#    address: 16
#    register_type: "read"
#    unit_of_measurement: A
#    device_class: current
#    entity_category: diagnostic
#    icon: mdi:flash
#    value_type: U_WORD
#    accuracy_decimals: 1
#    filters:
#    - multiply: 0.1        

  - platform: modbus_controller
    name: "${sensor_prefix} Pv4 Power"
    address: 17
    register_type: "read"
    unit_of_measurement: W
    device_class: power
    entity_category: diagnostic
    icon: mdi:flash
    value_type: U_DWORD
    accuracy_decimals: 1
    filters:
    - multiply: 0.1


#  - platform: modbus_controller
#    name: "${sensor_prefix} GW1 P1 Volt"
#    address: 50
#    register_type: "read"
#    unit_of_measurement: V
#    device_class: voltage
#    entity_category: diagnostic
#    icon: mdi:flash
#    value_type: U_WORD
#    accuracy_decimals: 1
#    filters:
#    - multiply: 0.1

#  - platform: modbus_controller
#    name: "${sensor_prefix} GW1_P2_Volt"
#    address: 51
#    register_type: "read"
#    unit_of_measurement: V
#    device_class: voltage
#    entity_category: diagnostic
#    icon: mdi:flash
#    value_type: U_WORD
#    accuracy_decimals: 1
#    filters:
#    - multiply: 0.1

#  - platform: modbus_controller
#    name: "${sensor_prefix} GW1_P3_Volt"
#    address: 52
#    register_type: "read"
#    unit_of_measurement: V
#    device_class: voltage
#    entity_category: diagnostic
#    icon: mdi:flash
#    value_type: U_WORD
#    accuracy_decimals: 1
#    filters:
#    - multiply: 0.1

  - platform: modbus_controller
    name: "${sensor_prefix} PV_Energy_Today"
    address: 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
#    register_count: 2

  - platform: modbus_controller
    name: "${sensor_prefix} PV_Energy_Lifetime"
    address: 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

#  - platform: modbus_controller
#    name: "${sensor_prefix} Inverter_Temp"
#    address: 93
#    register_type: "read"
#    unit_of_measurement: C
#    device_class: temperature
#    icon: mdi:thermometer
#    value_type: U_WORD
#    accuracy_decimals: 1
#    filters:
#    - multiply: 0.1

Hi Ronald,

This is my first ESP project and would love to know how you did the Export Limit?
I understand its not only a read but you also need a Write.
How to do?

Thanks

Regards,
Bjorn

Hi Bjorn, which growatt inverter do you have? The mod, min and sph all use different addresses.

Hi, I have the Growatt MIN 3000TL-XE inverter.

Best to Google a bit on this specific make, im not sure if it supports export control out of the box. Also good to check the modbus registers for this type.

Mine, an sph 8000, responds as follows:

   - platform: modbus_controller
     name: "${devicename} Export Control"
     icon: mdi:home-import-outline
     address: 122
     value_type: U_WORD
     optionsmap:
       "Disabled": 0
       "Enabled": 1

Thanks,

i’m still confused. If i want to use the ShineWifi’-X stick,
Do i need to add a 1K resistor between SW1 and the GPI00?? like they mention on the openinvertergateway github

I would not know. I use a wemos with an rs485 module. No resistor in my setup.

1 Like

will never do what is is supposed to do unless you type == instead of =. The way it is written now, the statement is always true.

1 Like

Thank you!

I’ve now got my SPH5000 set up to my satisfaction - thank you very much to @scudderfish, @Plawa and @RT1080, your posts here were invaluable.

For the record - I’m using a mini_d1 connected to the serial port (as per @scudderfish) and am no longer using the shine dongle. Switching between Battery First / Load First (I’ve not needed grid first yet) using the code here is working flawlessly, so I’ve no need for the growatt servers.

Cheers,
Tim

3 Likes

I’m using ESPHome in the ShineWifi-X to read PV and battery info from my MOD 10KTL3-XH(BP).

All registers from the pdf work, but some of them (depending on the order in which they are placed in the yaml, is my current theory) now and then give 0, 1 or a massive number (around max int 16 or 32 bit) back. For example, the battery SOC (0-100) jumps to 0 or 1 all the time. The register for total energy into the battery, jumps to the massive number all the time and then comes back to normal.

Is this a known issue? I also have a WaveShare PoE, does the modbus from the big connector maybe give better results?

This might be a stupid qestion, but ill try anyway.
I have a modbus line going from my raspberry pi to a systemair ventilation unit. Could I just tap in on this line and connect my growatt inverter to the same bus?

Hello :wave:,

Add the moment I have shinewife-X stick flashed with: GitHub - pvprodk/GrowattESPHome: ESPHome based code for Growatt inverters

It all works fine, but add the moment I can only read data.

In this topic I saw several times that some have the ability to reduce there output.

Can I achieve something similar?

The goal is, when I return to the grid, to lower the inverter so it stays around 0 watts uses. ( we have to pay now for everything that we don’t use and that’s put back on the grid)

Depends on your inverter and if you have an external meter connected (either via clamps or a modbus connection)

If so yes, it’s possible to limit export.

I have a Min TL-XH. I know, SPH and MIN uses different Modbus Adresses.

I testes this YAML, but the Error Message i got was:
INFO ESPHome 2024.7.0
INFO Reading configuration /config/esphome/growatt.yaml…
Failed config

esphome: None
name: growatt
friendly_name: Growatt
on_boot:
priority: -100
then:
- delay: 20s
- logger.log: ************* In da boot *******************
-
Unable to find action with the name ‘script.execute’.
script.execute:
id: setClock
- script.execute:
id: |-
setBatteryTimes

Used YAML:

esphome:
  name: "growatt"
  friendly_name: Growatt
  on_boot:
    priority: -100
    then:
      - delay: 20s
      - logger.log: "************* In da boot *******************"
      - script.execute:
          id: setClock
      - script.execute:
          id: setBatteryTimes
      
             std::vector<uint16_t> gridfirst_data = {0,23<<8|59,0,0,0,0,0,0,0};
             std::vector<uint16_t> batteryfirst_data = {100,100,1,0,0,0,0,0,0,0,0,23<<8|59,0,0,0,0,0,0,0};
             std::vector<uint16_t> loadfirst_data = {0,23<<8|59,0,0,0,0,0,0,0};

             // Create a modbus command item with the time information as the payload
             esphome::modbus_controller::ModbusCommandItem set_gridfirst_command =
                 esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller, 1080, gridfirst_data.size(), gridfirst_data);
             esphome::modbus_controller::ModbusCommandItem set_batfirst_command =
                 esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller, 1090, batteryfirst_data.size(), batteryfirst_data);
             esphome::modbus_controller::ModbusCommandItem set_loadfirst_command =
                 esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller, 1110, loadfirst_data.size(), loadfirst_data);
            
             // Submit the command to the send queue
             controller->queue_command(set_gridfirst_command);
             controller->queue_command(set_batfirst_command);
             controller->queue_command(set_loadfirst_command);
             ESP_LOGI("Lambda","Setting Grid/Battery/Load");

Whats wrong? Is this YAML not compatible with MIN?

I have the same problem with MOD10ktl3-XH-BP.
With the original WiFi-X stick I can read/write the Register (3049) via the website.
With the custom flashed WiFi-X stick → ESP-Home → Home-Assistant I can’t read or write the register.