ESPHome modbus Growatt ShineWiFi-S

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

It would make sense that it would be a problem like that. I tried to change some of the power sensors to U-DWORD right now, but got some astronomical values like power export of 210,980,080 kWh. Maybe I would need to change some other settings too? I did leave the filter multiply at “0.1”.

you may need to adjust the address by 1 as well, you were probably just reading the lower byte, now with dword you are reading the lower byte and the next one and joining them together. so for the registers that require dword reduce the address by 1

for my unit, looks to be very similar to yours
total output power address is 35
total energy today is 53
total energy lifetime is 55
perhaps look at some of the configs above, and also see if you can find a modbus register map for your particular model
the multiply by should stay the same

this is the only ones i can see in my config that use the dword, all the others, like current and volts are just word
also have a look at post #55 in this thread

1 Like

Thank you! That was the trick. I had to lower the address by one as you suggested as well as changing to U_DWORD. I guess I did not understand the growatt documentation correctly when first reading it. I did only use the address 36 which was the lower half of the value.

Thank you so much for the help! Now I need to find out how to delete some data from the history to make the power dashboard correct again :stuck_out_tongue:

Developer Tools → Statistics, the ramp icon next to the entity.

Thank you. This is what I found in a different thread too. I am now waiting for a backup to complete to not make the same mistake twice :laughing:

Solved. I did not realise the U_WORD and u_DWORD difference… I remember reading about it, but did not figure it out… LOL Thank you chris.huitema

Good morning Dave, I saw that you have the same inverter as me and I would like to ask you, or someone else if you want to answer. I’m also starting with the esp project and while I was checking the documentation, I noticed a note in which Growatt writes that the rs458-1 input cannot be used if the inverter uses the TA probe connected to the CT input. (This is valid only for the connection of the METER, do you use the TA probe? Do you have all 2 inputs used?)

Good question, I don’t know :sweat_smile: My ESP32 is connected via the RS232 port replacing the Shine stick. I haven’t touched the 485. I have 2 strings of panels and a 6kWh battery if that’s of any help.

Well i just see your message.
i could add a some code to retain the data till 00:00 so when it goes offline it keeps the data.
and probably add a new sensor so it will sy if the device is offline or online…

I can update my code, but however i’m using it from 29 July till today and hve 0 issue’s with it going offline.

Great work,
Was working instantly without any problems.

I just have a question about all the programming Registers you can set with the shine app or using the growatt web interface.
Is there a way to also set the growatt settings?
Like display language, time, type of Inverter and so on or isn’t it possible to add this to the esphome Firmware?