Modbus TCP tooling for Sungrow SH-RS / SH-RT hybrid inverters (built and tested against an SH5.0RS with a WiNet-S2 module), covering PV production, self-consumption, and battery telemetry - with a separate, guarded path for EMS/battery control writes.
Register addresses and behaviour are taken from Sungrow's official "Communication Protocol of Residential Hybrid Inverter" V1.1.5 document (supplied via Sungrow support). This isn't an official Sungrow project and isn't affiliated with or endorsed by Sungrow - see Disclaimer below.
| Inverter model | SH5.0RS |
| Comms module | WiNet-S2 |
| Connection | Modbus TCP, port 502, via the WiNet-S2 (not the inverter's internal LAN port) |
| Protocol doc version | V1.1.5 (dated 2024/9/24) |
The register catalog (src/registers.py) also lists addresses for
other SH-RS/RT/T models per the official doc's device type table, but
those are untested against real hardware - see
Verification status below. If you're on a
different model, this is a good starting point, not a guarantee.
The WiNet-S2 module's Modbus TCP server is known to become unresponsive under sustained or aggressive polling, sometimes requiring a hard reboot of the module to recover. This project is deliberately built around single-shot, on-demand reads rather than a built-in polling loop, so you can validate values and build your own polling cadence (with whatever interval and backoff suits your setup) on top of it, rather than inheriting an aggressive default.
src/registers.py- the register catalog: addresses, scaling, units, and categories, taken from the official protocol doc. Includes lookup tables for running state, power flow status bits, and device type codes.src/modbus_client.py- a small Modbus TCP client wrapper that handles the address offset, 32-bit word order, and pymodbus's shiftingunit=/slave=/device_id=keyword argument across versions.src/read_sh5_0rs.py- read-only CLI. Single connect -> read -> disconnect, with--categoryfiltering (production, battery, self_consumption, grid, backup, diagnostics, status, device_info).src/control_sh5_0rs.py- write CLI for EMS mode and battery charge/discharge control. Requires an explicit--i-understand-the-riskflag and an interactive confirmation before it writes anything - read the warnings in that file before using it.
| Category | Examples |
|---|---|
production |
Total DC power, MPPT voltage/current, daily/lifetime PV generation |
self_consumption |
Load power, export power, direct PV consumption, self-consumption % |
battery |
Voltage, current, power, SOC, SOH, temperature, daily/lifetime discharge |
grid |
Grid frequency, power factor, meter phase power, import/export energy |
backup |
Backup port power/voltage/frequency (off-grid) |
status |
Running state, power flow status (decoded from bitmasks) |
device_info |
Device type code (auto-decoded to model name), nominal power |
control (holding, write-only path) |
EMS mode, forced charge/discharge, SOC limits, export limit |
Some registers only return real data if the inverter has its own CT/meter
attached - these are noted individually in registers.py. If you're
reading grid import/export from a separate/independent meter (as this
project originally was), expect those specific registers to read zero
or be unreliable, and use your own meter's readings instead.
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt# Read everything
python3 src/read_sh5_0rs.py 192.168.x.x
# Read just one category
python3 src/read_sh5_0rs.py 192.168.x.x --category battery
# Optional flags
python3 src/read_sh5_0rs.py 192.168.x.x --port 502 --unit 1Control (writes - use deliberately, not experimentally):
python3 src/control_sh5_0rs.py 192.168.x.x self-consumption-mode --i-understand-the-risk
python3 src/control_sh5_0rs.py 192.168.x.x force-charge --power 2000 --i-understand-the-risk
python3 src/control_sh5_0rs.py 192.168.x.x stop-forced-charge-discharge --i-understand-the-riskOpen the folder in VS Code with the Python extension installed. It
should auto-detect .venv (see .vscode/settings.json); if not, run
Python: Select Interpreter and pick .venv/bin/python. Two debug
configs are provided (F5 / Run and Debug panel): one for reading, one
for the control script.
- Addressing: the register addresses in
registers.pyare the "protocol addresses" from Sungrow's doc. The client subtracts 1 automatically per Sungrow's own note ("communication address = protocol address - 1") - you don't need to do this yourself. - 32-bit word order: Sungrow's doc specifies low word first for
values spanning two registers; this is handled in
modbus_client.py. - Not every documented register is reachable over the WiNet-S2. Some throw "Illegal Data Address" even though they're in the protocol doc - likely gateway-side restrictions rather than addressing mistakes. A few register ranges (e.g. 6100-6826) are explicitly RS485-only per the doc and are excluded from this catalog for that reason.
- Stability: keep polling intervals conservative (seconds, not milliseconds) and avoid multiple tools/connections hitting the WiNet-S2 at once.
Registers are transcribed correctly from the official doc, but
"correctly transcribed" isn't the same as "confirmed against real
hardware behaviour" - the WiNet-S2 gateway doesn't expose every
documented register, and some values only populate under certain
conditions (e.g. an attached meter). Each Register in
registers.py carries a verified flag (default False); flip it
to True in a PR once you've confirmed a register returns a sane,
expected value on real hardware, ideally noting the model/gateway you
tested it on in the note field.
See CONTRIBUTING.md - corrections and additions from owners of other SH-RS/RT/T models are especially welcome.
This project is unofficial, not affiliated with or endorsed by
Sungrow, and provided as-is with no warranty (see LICENSE).
Writing to holding registers changes real inverter/battery behaviour;
you use control_sh5_0rs.py at your own risk. Register behaviour may
vary by firmware version and model even within the SH-RS/RT family.
MIT - see LICENSE.