We recently had a Growatt MIN 11400TL-XH-US inverter and associated battery system installed as part of our solar power system. It all seems quite reasonable, except that its IoT presence is aggressively tied to the cloud. I’d like it not to be. There’s https://github.com/johanmeijer/grott, for an off-the-shelf approach to this problem, but I was not sufficiently enamored of its implementation to want to run it myself. Nevertheless, it has been a useful resource for cross-checking my understanding of Growatt’s protocols.
Please understand that there may be mistakes in the following and it is still mostly the result of educated guesses from packet dumps. There are no warranties associated with this text, not even implied ones.
Connectivity¶
The inverter’s datalogger – apparently fully integrated, in the case of MIN devices – can be programmed to connect to one of four endpoints, by DNS:
server.growatt.com
server-us.growatt.com
server-cn.growatt.com
server.smten.com
It does so on ports 5279 and 5280. I do not know what transpires on port 5280.
Modified Modbus Header¶
The TCP stream established to port 5279 is reminiscent of “Modbus over TCP/IP”. Every message, in each direction, begins with an 8-byte header and ends with a 2-byte checksum. Multi-byte fields in the header are MSB-first (that is, network-endian).
A 2-byte “transaction identifier” (message sequence number)
A 2-byte “protocol identifier”. While Modbus sets this to 0, Growatt uses their own ontology here.
A 2-byte data length, which counts all bytes after the header, including the aforementioned 2-byte checksum.
A 1-byte unit identifier, should there be multiple devices on the bus.
A 1-byte function code.
The checksum is performed with the standard Modbus CRC-16 polynomial, but is, unlike in Modbus RTU serial streams, here transmitted MSB-first.
Protocols¶
Growatt uses protocol identifiers 6 (and 5, apparently?) to indicate a… let’s say lightly obfuscated payload. Specifically, all post-header non-checksum bytes are xor’d with the mask “Growatt” (in ASCII). The checksum is computed using the obfuscated contents, not their “plaintext”.
Function Codes¶
0x04: “Input Registers”¶
The datalogger periodically generates, without in-band stimulus, function code
0x04
messages. The payload of these messages appear to consist of a 67-byte
header followed by an array of register report structures. The initial header
contains…
A 30-byte, NUL-terminated, right NUL-padded ASCII string of the datalogger’s serial number.
A 30-byte, NUL-terminated, right NUL-padded ASCII string of the inverter’s serial number.
A 6-byte date and timestamp. The format for this is
Y M D H M S
with most fields being straightforward.Y
takes the year2000
as its zero value.M
encodes January as1
and counts from there;D
also counts from1
.A 1-byte count of subsequent register report structures.
A register report structure consists of a 4-byte fixed header followed by an array of 2-byte, MSB-first register values. The header contains two 2-byte, MSB-first fields, the minimum (inclusive) and then maximum (inclusive) register indices to follow. See the document “Growatt PV Inverter Modbus RS485 RTU Protocol v120” for the defined list of registers.
0x16: Ping¶
Ping messages will be echoed exactly by the remote end, including an unchanged transaction identifier.
In practice, it looks like the datalogger sends the same 30-byte string encoding of its serial number followed by two zero bytes.
0x19: Datalogger Identification¶
These short messages consist of the same 30-byte string encoding of the datalogger’s serial number followed by a 4-byte header and a variable length payload. The header is two 2-byte MSB-first fields: a key and the length of the payload string. The known keys are as follows:
Key |
Payload |
4 |
Update interval in minutes, as decimal value (e.g., “5.0”) |
8 |
Datalogger alias (defaults to serial number) |
14 |
Datalogger IP address (dotted quad format) |
16 |
Datalogger MAC address |
18 |
Server TCP port |
19 |
Server DNS name |
21 |
Firmware version string |