ESPHome modbus Growatt ShineWiFi-S

IMHO the best and easiest solution is to make a separate MODBUS interface consisting of a ESP board like a D1 Mini and a UART to RS485 converter. Connect the device tot the MODBUS connector on your growatt inverter (pin layout is in the Growatt manual). The modbus register lay-out for Growatt can be found on the internet. ESPHOME YAML scripts can be found on this forum. The dutch Tweakers forum also has a few threads on this. Once I had the hardware I had it up & running in one evening. I also have Shinelink running because it came with the inverter. The two do not interfere. I do not use Shinelink, it’s only there so the vendor can do monitoring (I don’t believe they monitor anything, but that is a separate topic). You can do pretty advanced stuff with MODBUS and HA. I have programmed automated curtailment for example.

If you own a growatt battery do check the warranty conditions, shinelink monitoring is a prerequisite to benefit from the 10y/6000 cycles condition. If not for that i would have disabled it.

Bit of a resurrection this :slight_smile: I’ve been fighting this with my SPH-6k and it always ignores my Inverter Priority changes. Could you post your complete yaml file please? I suspect it might be something around the 1090+ registers overriding the override.

You might need to change the grid first and battery first time slots. If at the time you change the priority there is no active time slot it will default to load first.

Solution is to have a full day time slot for both grid and battery first.

Suggest you try (through either shinelink or the display) to set the following: 00:00 - 23:59

Do the same for discharging and you are done (be sure to check that only one is enabled otherwise you will get a crude error message)

Thanks, I’m stumbling around that area, doing it the hard way with setting modbus registers directly :roll_eyes: In your screenshot you have empty slots enabled the same as the proper 00:00-23:59 one?

Quick question, I managed to flash my ShineWifi-S, it powers up and WiFi/MQTT works perfectly when powering up via the serial programer.

The problem is when I plug it into the inverter (SPA) it powers up but all LEDs stay on and it does not want to boot up.

I have tried plugging the ESP board via separate cables on the inverter 9-pin D-Sub using standard D-Sub pinout to get 3.3v, GND, RXT, and TX, but no joy.

Can anyone throw some light on this? Is the SPA pinout different from the standard serial pinout?

TIA

The inverter puts circa 12v into the d-sub, not 3.3! If you’re putting that in, you’re probably not getting enough power for the ESP

Using this lambda I seem to be able to switch between Load First and Battery First by twiddling Inverter Priority, but if I select Grid First it sets itself to Battery First. I can live with that as if I have excess power it’s going into the car, not the grid.

  - id: setAllTimes
    then:
    - lambda: |-
        esphome::modbus_controller::ModbusController *controller = id(growatt);
        std::vector<uint16_t> disable_data={0,0,0};
        std::vector<uint16_t> enable_data= {0,23*256+59,1};
        std::vector<uint16_t> window_data= {0,23*256+59,0};
        int size = window_data.size();

        ESP_LOGI("ModbusLambda","Enqueue Writes");
        
        //GF1
        controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1080,size,window_data));
        //GF2
        controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1083,size,disable_data));
        //GF3
        controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1086,size,disable_data));

        //BF1
        controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1100,size,enable_data));
        //BF2
        controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1103,size,disable_data));
        //BF3
        controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1106,size,disable_data));

        //LF1
        controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1110,size,window_data));
        //LF2
        controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1113,size,disable_data));
        //LF3
        controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1116,size,disable_data));

        ESP_LOGI("ModbusLambda","Writes");

Ouch! Thanks for letting me know, so will an external 3.3v source be required?

If you’re feeding into the d-sub on the stick, you probably need to put 9-12v onto pin 9 so the stick can convert it down to what the ESP needs. However double and triple check what I’m saying before you risk frying it.

Thanks @scudderfish, time to get the multitester out.

By the way, what’s the usual method everyone is using to connect the boards to the inverter (RS232)?

I genuinely thought it was going to work plugging it via D-Sub (noob here).

Thats probably because the battery first time window is not properly setup. Have not gone through the trouble of setting this up through modbus.

@scudderfish - this is the advice i followed and it works for me. Off loaded all planning for charge and discharge to home assistant by calling grid or Bat first.

I’ve done that, but I ditched the shine stick and just use an ESP32. I tap the power from the dsub into an adjustable voltage regulator. There might me a photo of it somewhere around post 200 ish :slight_smile:

1 Like

Thanks. I’m trying to do this in a reproducible way that doesn’t rely on doing something manually with their app/website first. However I feel like I’m poking an undocumented state machine and weird things happen. Could I ask a favour please? Could you add the following text sensor to your config and paste what the output is? It will dump the values of all the timing blocks so I can decode them.

text_sensor:
  - name: "dump"
    platform: modbus_controller
    address: 1080
    register_count: 49
    raw_encode: HEXBYTES
    register_type: "holding"

My current values start

0000173b00000000000000000000000000000000006400640001000000000000000000000000

Growatt MOD10KTL3-XH Mode selection - I finally got this working!

I am not a programmer, and I have really stretched my capabilities here! But it works. In my knowledge there was no other way to change the mode selection besides display and growatts server / shinephone app.

My needs were only to change the modes, as this it not supported by Solar Assistant yet. But reading sensors and changing holding registers with selection works fine, so it should not be a bit problem to set up.

Why is the selection of modes complicated on the TL3-XH models? Growatt have chosen to merge mode selection with time slots. My workaround is to disable all timeslots but no. 1. Further more is timeslots defined by two holding registers.

I am not interested in using the timeslots, as I can do my programming in Home Assistant / Node-red instead. So therefore I just use timeslot 1. Set from 00:00 to 23:59.

This is from the growatt modbus RTU documentation v.1.24.

How to get the MOD inverter to change mode? You need to send a value to both holding register 3038 and 3039. The value you need to send is calculated like this:

I would like to send the following:
Start minutes: 00
Start hour: 00
Mode: 0
Enable / disable: 1

For me binary was quite the learning code, so what I tell here, is in my best knowledge!

We need to “build” a 16 bit long binary number. bit 0 is the bit furthest to the right and bit 15 is the one furthest on the left. I used this converter: Decimal to Binary Converter

First 8 bits (bit 0-7) are the start minutes. We want 00. This is very easy. all zeros
Binary: 00000000
Next 5 bits are start hour (bit 8-12) We want 00. Again all zeroes
Binary: 00000
Next 2 bits are mode. (bit 13-14) We want Load First, again a 0.
Binary: 00
Last bit is enable bit. (bit 15) We want enable, this time 1.
Binary:1

Then we merge the numbers:


Binary number: 1000000000000000
Converted to decimal 32768

I will do one more example for battery first.
First 8 bits (bit 0-7) are the start minutes. We want 00. This is very easy. all zeros
Binary: 00000000
Next 5 bits are start hour (bit 8-12) We want 00. Again all zeroes
Binary: 00000
Next 2 bits are mode. (bit 13-14) We want Battery First = 1
Binary: 01
Last bit is enable bit. (bit 15) We want enable = 1
Binary:1

Numbers merged:


Converted to decimal: 40960

For grid first, we need to write 2 in binary in bit 13-14 = 10, this results in the number: 49152

I my situation, i need the stop time to be 23:59.
First 8 bits (bit 0-7) are the en minutes. We want 59. (The binary number is 111011, but we need 8 bit, so just fill up with two o)
Binary: 00111011
Next 5 bits are start hour (bit 8-12) We want 23. Binary number: 10111
Binary: 10111
Bit 13-15 are reserved and need to be 0.
Binary: 000
Merged together:
Binary: 0001011100111011
Decimal: 5947

Now for the last challenge… to get the inverter to accept the values. Some trial and error, i figured out i need to send to both registers. I “stole” and modified a bit of code from this thread by Scudderfish!

I am not capable of C++ coding, so again a bit of a challenge :slight_smile:

Here is the code. Will break it up a bit after the code:

The basics is that the values calculated earlier has to be written to holding register 3038 and 3039. The lambda function does this when button is pressed in Home Assistant.

I will show load first as an example her. We want: 3038 to have value 32768 and 3039 to have value 5947

First we define the two values in the vector called load_first

Then we use the command: create_write_multiple. Here we ask to start at register 3038, and use the vector load_first. Because the vector has two values, they will be written 3038 and 3039.

For this to work, you will need to disable time slots 2 - 6 via shinephone or on the display.

Hope this is usefull

Regards Tommy

2 Likes

That’s a really well written explanation, and I’m pleased I helped a little :slight_smile:

1 Like

Inspired by your work, this now works for me

button:
  - platform: template
    name: "Battery First"
    on_press:
      then:
          lambda: |-
            esphome::modbus_controller::ModbusController *controller = id(growatt);
            std::vector<uint16_t> on={0,23*256+59,1};
            std::vector<uint16_t> off={0,23*256+59,0};
            int size = on.size();

            ESP_LOGI("ModbusLambda","Enqueue Writes");
            //BF1
            controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1100,size,on));
            //LF1
            controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1110,size,off));
            //GF1
            controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1080,size,off));
            ESP_LOGI("ModbusLambda","Writes");

  - platform: template
    name: "Load First"
    on_press:
      then:
          lambda: |-
            esphome::modbus_controller::ModbusController *controller = id(growatt);
            std::vector<uint16_t> on={0,23*256+59,1};
            std::vector<uint16_t> off={0,23*256+59,0};
            int size = on.size();

            ESP_LOGI("ModbusLambda","Enqueue Writes");
            //BF1
            controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1100,size,off));
            //LF1
            controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1110,size,on));
            //GF1
            controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1080,size,off));
            ESP_LOGI("ModbusLambda","Writes");

  - platform: template
    name: "Grid First"
    on_press:
      then:
          lambda: |-
            esphome::modbus_controller::ModbusController *controller = id(growatt);
            std::vector<uint16_t> on={0,23*256+59,1};
            std::vector<uint16_t> off={0,23*256+59,0};
            int size = on.size();

            ESP_LOGI("ModbusLambda","Enqueue Writes");
            //BF1
            controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1100,size,off));
            //LF1
            controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1110,size,off));
            //GF1
            controller->queue_command(esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller,1080,size,on));
            ESP_LOGI("ModbusLambda","Writes");

The Inverter Priority field then updates as it should to reflect the new status.

1 Like

I cannot make sense of this.

On a MOD 10KTL3-XH looking in register 3144:

When in Load First i get : 0
When in Battery First: i get: 65536
When in Grid First i get: 131072

Tried to make sense of these numbers. I cannot. I can see the values are used ind register 3000 also.

Anybody able to decode the values, to something that makes sense?

regards

If i had to guess there is a rollover happening, likely at 65535. power is reported with 1 decimal (the filter multiply by 0.1).

double check the value type, there is a very slight difference

value_type: U_WORD

value_type: U_DWORD

so word and double word
basically one is a single register and the other is a double register

check all your power, total power, energy today, energy lifetime

1 Like