berndverhofstadt/sungrow-poc

★ 0Forks 0PythonGitHub ↗Compare

README

sungrow-sh5.0rs-modbus

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.

Tested configuration

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.

Why this exists

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.

What's included

  • 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 shifting unit=/slave=/device_id= keyword argument across versions.
  • src/read_sh5_0rs.py - read-only CLI. Single connect -> read -> disconnect, with --category filtering (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-risk flag and an interactive confirmation before it writes anything - read the warnings in that file before using it.

Register coverage

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.

Setup

python3 -m venv .venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate
pip install -r requirements.txt

Usage

# 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 1

Control (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-risk

VS Code

Open 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.

Known limitations / gotchas

  • Addressing: the register addresses in registers.py are 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.

Verification status

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.

Contributing

See CONTRIBUTING.md - corrections and additions from owners of other SH-RS/RT/T models are especially welcome.

Disclaimer

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.

License

MIT - see LICENSE.

Contributors

berndverhofstadt

Issues