Home Posts Notes Now

Growatt messages

Growatt inverters have a modbus interface to expose a lot of data and to allow users to remotely control them. Growatt offers a remote monitoring solution that uses a Wi-Fi stick (ShineWifi) to interact with their server (ShineServer).

ShineWifi can be configured to talk to different servers, which opens up interesting possibilities as long as we know how to interpret the messages. This approach is used by a few projects that act as full fledged servers or just act as a man-in-the-middle to collect data.

Connection

ShineWifi opens a TCP connection and exchange binary messages with the server. The protocol used is a variant of Modbus.

Framing

Each message has a MBAP header followed by a single byte describing the function code, a variable amount of data and a 2 bytes CRC.

MBAP header
  Transaction identifier (2 bytes)
  Protocol identifier (2 bytes)
  Length (2 bytes)
  Unit identifier (1 bytes)
Function code (1 byte)
Data (length specified in the header - 2 bytes)
CRC (2 bytes)

Obfuscation

Data is obfuscated by applying a XOR of each byte and the corresponding byte in a string that keeps repeating the word Growatt. This is a bit annoying and it doesn’t really add any level of security but at least it’s trivial to get around it.

At this stage we are ready to dive into the content of each specific message. There are two distinct categories of messages: datalogger messages and inverter messages. The former concern the communication between the datalogger and the server and the configuration of the datalogger. The latter are used to communicate all the inverter data and to configure it.

Datalogger messages

The payload of a datalogger message starts with a 30 bytes preamble encoding the datalogger ID. It then continues with the actual payload of the request.

Ping (function code 0x16)

Datalogger ID (30 bytes)

Ping contains no further data other than the datalogger ID. It’s sent by the client and the server replies with the exact same message.

Ping is the first message after the connection has been established. Then the server requests inverter configuration and the client starts publishing its own data.

Read datalogger holding variable (0x18)

Request:

Datalogger ID (30 bytes)
Start address (2 bytes)
End address (2 bytes)

The server sends a message asking the datalogger for its configuration. It's payload is the datalogger ID followed by two 16 bit value corresponding to the first and the last configuration IDs requested. This is the best reference I've found on the meaning of each configuration ID.

Interestingly, the server seems to initially request a single value but the inverter replies with more values.

Response:

Datalogger ID (30 bytes)
Address (2 bytes)
Lenght (2 bytes)
ASCII-encoded string (length specified in the previous field)

Responses from the inverter include the datalogger ID, a 16 bit value corresponding to the ID of the value in the message, another 16 bit value encoding the lenght of the value and the value encoded as a variable-lengh ASCII string.

Set datalogger holding variable (0x19)

This is a very similar message to the previous one. The server request is:

Datalogger ID (30 bytes)
Address (2 bytes)
Lenght (2 bytes)
ASCII-encoded string (length specified in the previous field)

The inverter responds with an ACK:

Datalogger ID (30 bytes)
Response (8 bytes) - ACK is 0x00

Inverter messages

For the meaning of each field please refer to Growatt's Modbus RTU documentation.

Read holding registers (0x05)

The request is same as we've seen in the Datalogger config:

Datalogger ID (30 bytes)
Start address (2 bytes)
End address (2 bytes)

The response is a "register sequence":

Datalogger ID (30 bytes)
Start address (2 bytes)
End address (2 bytes)
Data encoding the contiguous set of registers in the range (lenght: end_address - start_address )

Write single holding register (0x06)

Request:

Datalogger ID (30 bytes)
Address (2 bytes)
Value (2 bytes)

Response:

Datalogger ID (30 bytes)
Address (2 bytes)
Response (2 bytes) - Ack is 0x00

Write multiple holding registers (0x10)

Request:

Datalogger ID (30 bytes)
Start address (2 bytes)
End address (2 bytes)
Values

Response:

Datalogger ID (30 bytes)
Start address (2 bytes)
End address (2 bytes)

Data emitted by the inverter

The payload of a datalogger message starts with a 30 bytes preamble encoding the datalogger ID followed by other 30 bytes encoding the inverter ID and finally by 6 bytes encoding a date followed by one or more registers sequences. Every registers block contains 125 contiguous values.

I haven't managed to map app of them, this is what I've found so far:

The server doesn't respons to these messages.

Datalogger ID (30 bytes)
Inverter ID (30 bytes)
Date (6 bytes)
Register block (possible multiple of them)
  Start address (2 bytes)
  End address (2 bytes)
  Values (125 * 2 bytes)

Thanks for reading. Feel free to reach out for any comment or question.

Backlinks