Skip to content

Local Modbus (WiNet-S)

Besides the cloud, the integration can read your inverter directly over your local network using the WiNet-S dongle's Modbus TCP interface. Local reads are fast, don't count against any API quota, and keep working even when the internet (or iSolarCloud) is down.

Cloud and local are separate config entries. They never mash values onto the same sensors. If both exist for the same inverter serial, the local device is nested under the cloud plant in the device registry (via_device_id) — a soft “related to” link only.

What's supported today

Local Modbus is read-only and maps:

  • SG-RS / SG-RT string inverters (single- and three-phase low-block layout)
  • SH-RS / SH-RT hybrid inverters (battery SOC/power, house load, meter points)

Family is auto-detected from register 5000 (device-type code) or the configured model string. Local control (writes) is tracked in #220; remaining map gaps in #219.

What you need

  • A WiNet-S communication dongle or the inverter's internal LAN port on the same LAN as Home Assistant. The internal LAN port, where present, is generally the more reliable path — it may need to be enabled from the iSolarCloud app first (see Troubleshooting → The Ethernet port on the inverter appears dead).
  • The device reachable on TCP port 502 (Modbus TCP) from Home Assistant — the default Modbus port and unit ID (1) are used automatically. A different port can be entered when you connect through a Modbus proxy (see Using a Modbus proxy).
  • No iSolarCloud account for a Modbus-only setup. (A cloud entry is independent and still needs credentials for its own sensors and dispatch.)
  • A supported inverter family — SG string or SH hybrid (see Model support).

mDNS discovery must be able to reach Home Assistant

The dongle is found via mDNS/zeroconf, which doesn't cross subnets or VLANs by default. If Home Assistant and the WiNet-S are on different network segments, discovery won't fire — add the local entry manually when that path is available, or ensure mDNS can cross the boundary.

Flaky WiNet-S connection?

The WiNet-S dongle sometimes drops idle Modbus TCP sessions. If reads flap in and out, a stateful proxy like Akulatraxas/ha-modbusproxy in front of the dongle is the community-standard workaround — see Troubleshooting → WiNet-S drops connections.

Transport modes

Mode Data source Cloud account Control (dispatch) Best for
Cloud (Developer Account via Official OpenAPI - Cloud Polling) iSolarCloud OpenAPI Developer app (App Key/Secret/ID) ✅ Yes Full plant sensors and battery/dispatch controls.
Cloud (User Account via Unofficial API - Cloud Polling) iSolarCloud app/web API Just email + password ✅ Yes (experimental) No developer app — sensors + dispatch. See caveats below.
Modbus (Local Polling) Local Modbus only Not needed ✅ Active power limit (SG string) Fast local metrics + limited local control; offline / privacy-first.
Both (two entries) Each entry its own source Cloud entry only Full dispatch via cloud; local active-power on local entry Compare or use cloud + local side by side.

Cloud user account (unofficial)

If you don't have (or don't want to register) an iSolarCloud developer application, you can connect with the normal email + password you use in the iSolarCloud app/web portal. Choose Cloud (User Account via Unofficial API - Cloud Polling) as the transport and enter your email, password and region.

Unofficial and experimental

This uses Sungrow's undocumented app/web API, not the official OpenAPI. It may change or stop working without notice, and its use may be subject to Sungrow's terms of service. Your password is stored in the Home Assistant config entry and is never logged. Prefer the developer Cloud-only transport if you can.

Current limitations:

  • Plant-level realtime from getPsDetail (power, yield, alarm/fault counts, …). Device discovery is used for dispatch targeting; per-device sensor parity with OAuth may still differ by model.
  • Dispatch uses the same number/select entities and safety rails as the developer Cloud transport (battery gating, forced-mode verification, EMS heartbeat). Some EMS modes (e.g. Energy Management Mode on PV-only plants) may report “template not configured” — that is a plant capability limit, not a missing feature.
  • Prefer the developer Cloud-only transport when you need the official OpenAPI path or full OAuth device metrics.
  • If login fails with "account or password incorrect", it's most often the wrong region — pick the region your account actually uses.
flowchart LR
    subgraph HA["🏠 Home Assistant"]
        direction TB
        CO["Cloud entry"]
        LO["Local entry"]
    end
    CO -->|"poll"| API["☁️ iSolarCloud OpenAPI"]
    LO -->|"poll"| WN["🔌 WiNet-S<br/>Modbus TCP :502"]
    API --> INV["Inverter"]
    WN --> INV
    LO -.->|"via_device_id when serial matches"| CO

Which should I choose?

  • Battery/dispatch control or full plant metrics → Cloud entry from Installation & Setup.
  • Fast local power / yield without API quota → Local via auto-discovery.
  • Both → set up each entry independently. Pick which entities to use in Energy / automations deliberately (e.g. cloud daily vs local derived daily).

Modbus-only: set up from auto-discovery

When a WiNet-S dongle appears on your network, Home Assistant discovers it and offers a standalone local setup.

  1. Go to Settings → Devices & Services. A Discovered card reading Sungrow <model> appears (from the dongle's advertised serial and model).
  2. Click Set up and confirm. Home Assistant creates "Sungrow <model> (local)".
  3. Polling defaults to 30 seconds (no cloud quota; minimum 10 seconds).

If the same inverter is already on a cloud entry (matched by serial), the local inverter device is placed under that cloud plant in the UI. Entities stay separate — nothing is merged.

Modbus-only: guided manual setup

If Home Assistant hasn't (yet) offered a Discovered card — the dongle is on another subnet, mDNS is blocked, or the announcement was missed — you can walk the same setup manually via Add Integration → Sungrow → Modbus (Local Polling). The wizard tries to do as much for you as it can:

  1. Discovery — a short mDNS scan runs. Any WiNet-S dongles that answer are listed by model + serial + IP; pick one to skip straight past IP entry. If nothing is found (or you want a specific host), choose Enter IP manually; Rescan re-runs the scan.
  2. Manual IP (if used) — type the WiNet-S IP or hostname, and optionally a Modbus TCP port (default 502). Home Assistant checks that the port answers before continuing, and the identify step below reads over the same port.
  3. Identify — with a reachable host, Home Assistant reads the inverter model (register 4999) and serial (register 4989) directly over Modbus and shows them for confirmation. No wiring diagrams or portal spelunking needed for the common case.
  4. Confirm — submitting runs a final Modbus read as the create-entry probe: if the inverter is still reachable and the serial matches what identify saw, the entry is created. Otherwise the wizard surfaces the reason and lets you back up without losing what you've entered so far.

If model or serial can't be read (older firmware, non-standard model code), the wizard falls through to a manual details form pre-filled with whatever it did manage to read — you supply the missing pieces and it creates the entry.

Reconfigure / IP change

  • Options on the local entry: poll interval, Modbus TCP port and optional daily-yield register debug.
  • Reconfigure: change the host and/or port yourself, for example to use the inverter's dedicated RJ45 Modbus TCP port instead of the WiNet-S, or to go through a Modbus proxy. The new address must answer on that port before it is saved.
  • DHCP / IP change: an entry created from WiNet-S discovery follows the dongle to a new address automatically when it is re-discovered. A host you set (manual wizard or Reconfigure) is never overwritten by discovery — update it with Reconfigure.

Using a Modbus proxy (custom port)

The WiNet-S serves only a limited number of Modbus TCP clients and errors when several tools poll it at once. A Modbus proxy holds the single connection to the dongle and lets several clients share it — for example evcc's modbusproxy, or Akulatraxas/ha-modbusproxy. The proxy usually listens on a port other than 502.

  • New entry: in the guided setup choose Enter IP manually, type the proxy's host and its port.
  • Existing entry (including one created from discovery): use Reconfigure to set the proxy's host and port. Options also offers the port if only the port changes.

Port resolution is: the port set in Options (only stored when it differs from the entry's own), then the port saved by setup / Reconfigure, then 502. Saving Reconfigure clears an Options override, so the value you last entered is the one used.

Entries remain one per inverter, keyed on its serial, not on host/port: several inverters behind one proxy on different ports each get their own entry, while adding the same inverter again through a proxy updates the existing entry's host and port instead of creating a duplicate. Once an entry points at a proxy (a host you set, or a non-standard port), WiNet-S discovery no longer rewrites its host to the dongle's address. The Modbus unit ID stays at 1.

MPPT trackers

For a model in the built-in catalog the integration reads only the MPPT trackers that model actually has. Each of those reports 0 when it is idle, so a tracker sitting at zero — for example when the integration is set up at night — still creates its voltage and current sensors instead of being left without an entity. Tracker points the model does not have are not read at all; when any are skipped they are listed under dropped_mppt_points in the config-entry diagnostics download. Models that are not in the catalog keep the previous conservative behaviour (zero readings are not published).

Upgrading from hybrid (old “Modbus host on cloud”)

Earlier builds allowed putting a WiNet-S IP on the cloud entry and merging Modbus values over cloud sensors. That mashup is removed:

  1. On load, any leftover modbus_host is stripped from the cloud entry.
  2. When the inverter serial is already in the device registry, a separate local entry is created automatically with that host.
  3. If no serial is known yet, set up local Modbus via discovery once more.

Daily yield (local)

On some SG-RS + WiNet-S firmwares the “daily” register never resets at midnight. The local entry derives calendar-day yield from lifetime total_yield (baseline stored in HA). Cloud daily yield remains whatever iSolarCloud reports — they can differ; that is expected with two independent sources.

Daily grid import/export (local)

The device’s own daily grid registers are firmware-dependent: some SH firmware answers a flat 0 for daily_imported_energy (#401), so the entity either reads a permanent 0 or never appears at all.

When the lifetime counters are available the local entry derives calendar-day import and export from them instead — daily_imported_energy = total_imported_energy − start-of-day, and the same for export (same mechanism as local daily yield, with its own stored baseline per counter). The derived entity carries source: modbus_derived, plus raw_register_value — the device's own reading at the time — so the two figures can be compared straight from the entity's attributes. A flat 0 next to a real derived value is the quickest way to see that the register is the broken side.

The derived value replaces the device register while a lifetime counter exists. Two reasons. It keeps the source — and therefore the entity’s classification — stable, since Home Assistant picks the device/state class once, when the entity is first built. And it makes these the only local daily energy points published as device_class: energy, state_class: total_increasing, so they can be chosen directly as Grid consumption / Return to grid on the Energy dashboard; the raw registers stay plain measurements because we don’t know what a given firmware writes into them (#431).

On the day the derivation starts it is seeded from the device’s own daily figure (total − daily) when that reading is plausible, so taking over doesn’t reset the day to 0. A flat-0 register carries no such information, so that day reads 0 until the next local midnight.

A lifetime counter that jumps by more than the connection could physically carry (measured over the time since the previous poll) is treated as a glitch and held back for that poll: the entity keeps showing the register's own reading rather than a spike, the baseline does not move, and a warning names the counter. That covers a smart meter that has been disconnected or has failed, where the inverter keeps answering with garbage. The check fails open whenever it has no reference — first poll after a restart, or a long gap between polls — so a real catch-up is never rejected.

Both the lifetime counters and the daily registers come from the external grid meter (CT clamp / DTSU666). With no meter fitted there is nothing to derive from: the points are omitted and the entities are absent, so use a cloud entry for grid figures (#387 — check modbus_diagnostics.meter_present in the entry diagnostics).

Daily battery charge/discharge (local)

Some SH firmware doesn't reset the daily battery registers (daily_battery_charge / daily_battery_discharge, registers 13039 / 13025) at local midnight, so the raw values can't be trusted to mean "today" (#431). The lifetime counters (total_battery_charge / total_battery_discharge, registers 13040 / 13026) are monotonic, so the local entry derives the daily figures from them exactly as it does for grid import/export above (#486): same per-counter baseline, same source: modbus_derived and raw_register_value attributes, same takeover seeding, and the same device_class: energy, state_class: total_increasing classification — so they can be selected as the battery's energy going in / energy coming out on the Energy dashboard.

The glitch guard applies here too, judged against a battery ceiling (50 kW, twice the largest battery rating in the integration's model table) rather than the grid one. A family without the lifetime battery counters (the SG string inverters) simply has no derived battery entities.

Local control (active power limit)

On SG string local entries (validated on SG3.6RS), the integration can write WiNet-S holding registers for:

  • Limited power switch (enable/disable active power limiting)
  • Active power limit ratio (% of rated, 0–100%)

These use the same number/select entities as cloud dispatch. Battery EMS (charge/discharge, SOC, heartbeat) is not available over local Modbus on string inverters — use a cloud entry for hybrid/battery control (#220).

Current limitations

Local holding control is family-gated, and which params are writable differs by family:

  • SH-RS / SH-RT hybrids can write the battery dispatch set — charge/discharge power, SOC limits, forced-charge, EMS mode — plus the active-power and export limits. Not yet mapped: the export-limit ratio, the forced-charging target SOCs, and reactive power (q_t / pf).
  • SG-RS / SG-RT string inverters have no battery, so only the active-power and limited-power switches are writable; every battery parameter is cloud-only.
  • The exact per-family gap is declared in CLOUD_ONLY_DISPATCH_NUMBERS (modbus_control.py) and asserted against the dispatch Numbers in CI, so a control added to the cloud surface can't silently do nothing on a local entry.

Local and cloud do not share entities; configure Energy/dashboards explicitly.