fronius-modbus
Async Python library for the Modbus TCP (SunSpec) interface of Fronius inverters, built on modbus-connection.
[!WARNING] Alpha version — mostly untested. So far this has been verified against a single Symo GEN24 10.0 Plus (firmware 1.40.9-1) and a SnapINverter Symo behind a Datamanager 2.0. Write support (power limiting, battery controls) is only tested against mocks. Expect breaking API changes. Use at your own risk — especially the write operations, which change inverter behavior. Feedback and test reports are very welcome!
Supports both Fronius device generations:
- GEN24 / Tauro — the inverter itself serves Modbus TCP (unit ID 1)
- SnapINverter via Datamanager 2.0 — the Datamanager gateways all inverters of a
Fronius Solar Net ring (unit ID = inverter number,
00→100)
The SunSpec register map of Fronius devices is dynamic: model addresses shift with the configured data type (float vs int + SF) and differ between generations. This library discovers the model chain at runtime, so no register configuration is needed — and the data type setting is detected automatically.
Reading
- SunSpec Common Model (1): manufacturer, model, software version and serial number.
- SunSpec inverter models (101-103 / 111-113) in both encodings: AC power, frequency, lifetime energy, per-phase currents and voltages, apparent/reactive power, power factor, DC totals, operating state and vendor state, event flags.
- SunSpec Multiple MPPT Inverter Extension Model (160): DC current, voltage, power and lifetime energy per MPP tracker, with module role classification (PV string / storage charge / storage discharge) and derived totals (PV-only energy, battery charging/discharging energy).
- SunSpec Basic Settings Model (121): the inverter's nominal maximum power output, which the output power limit is a percentage of.
- SunSpec Basic Storage Control Model (124): state of charge, battery status, charge and discharge limits with their enable flags, minimum reserve and grid charging.
Each model is refreshed in as few pooled block requests as possible, and values are read together with their scale factors so the two can never disagree.
[!NOTE] With the device set to float, the inverter model's
energy_totalis an IEEE 754 single, which represents integers exactly only up to 2^24 = 16.78 MWh. Past that it quantises — 4 Wh steps at 48 MWh, coarser as the counter grows. The MPPT model counts inacc32with a scale factor, sopv_energy_totaland the battery totals keep full resolution at any reading. Prefer them for lifetime energy, or set the device to int + SF.
Writing
Write commands require inverter control via Modbus to be enabled on the device
web interface; Controls.probe_write_access() reports whether the device accepts
writes at all.
- Immediate Controls (123):
set_power_limit()andclear_power_limit()to limit the inverter output power. - Basic Storage Control (124):
set_limits()for the battery charge and discharge rates (including forced charging), plusset_minimum_reserve()andset_grid_charging().
The limit setters take a revert_seconds auto-revert, so a controller that dies
cannot leave the inverter constrained.
Usage
The library only consumes a ModbusUnit — connection lifecycle stays with the
caller (or with Home Assistant's modbus_connection integration):
import asyncio
from fronius_modbus import FroniusModbusInverter, GEN24_UNIT_ID
from modbus_connection.tmodbus import connect_tcp
async def main() -> None:
connection = await connect_tcp("192.168.1.50", port=502)
inverter = FroniusModbusInverter(connection.for_unit(GEN24_UNIT_ID))
await inverter.discover()
print("Data type:", "float" if inverter.float_mode else "int+SF")
print("Storage:", inverter.has_storage) # auto-detected during discovery
# one pooled read refreshes every discovered model
await inverter.async_update()
if (identity := inverter.common) is not None:
print(identity.manufacturer, identity.model, identity.serial_number)
if (ac_dc := inverter.inverter) is not None:
print("AC power:", ac_dc.ac_power, "state:", ac_dc.operating_state)
if (mppt := inverter.mppt) is not None:
for module, role in zip(mppt.modules, mppt.module_roles, strict=True):
print(module.id_str, role, module.power)
print("PV energy total:", mppt.pv_energy_total)
print("Battery charged:", mppt.storage_charge_energy_total)
print("Battery discharged:", mppt.storage_discharge_energy_total)
if (storage := inverter.storage) is not None:
print("SoC:", storage.state_of_charge, "state:", storage.state)
await storage.set_limits(charge=50.0, revert_seconds=60)
await connection.close()
asyncio.run(main())
Modbus must be enabled on the inverter web interface (GEN24: Communication → Modbus → Slave as Modbus TCP; Datamanager: Settings → Modbus → Data output via Modbus → TCP).
Testing on real hardware
Two scripts in scripts/ help test the library against an inverter (clone the
repo and run them with uv):
read_inverter.py — a one-shot dump of everything the library reads, plus
optional write commands. Good for quick checks and scripting:
uv run scripts/read_inverter.py <inverter-ip>
uv run scripts/read_inverter.py <inverter-ip> --set-power-limit 80 --revert 60
uv run scripts/read_inverter.py <inverter-ip> --probe-write
monitor.py — an interactive terminal UI that polls live at a configurable
interval. It shows the data panels and a status log, a live power graph with a
selectable series list (AC power, per-MPPT and battery charge/discharge), and a
write-command dialog (press s) for running every write command with immediate
status feedback:
uv run scripts/monitor.py <inverter-ip> --interval 2
Write commands require "inverter control via Modbus" to be enabled on the device web interface. Limit writes default to a 60 second auto-revert as a safety net.
Testing support
fronius_modbus.testing provides build_sunspec_map() to build SunSpec register
maps for modbus_connection.mock.MockModbusUnit:
from fronius_modbus.testing import MpptModuleSpec, build_sunspec_map
from modbus_connection.mock import MockModbusConnection
connection = MockModbusConnection()
connection.for_unit(1).holding.update(
build_sunspec_map(
[MpptModuleSpec(id_str="String 1", current=82, voltage=4021, power=3300)],
float_mode=True,
)
)
License
GPL-3.0-or-later, see LICENSE.
Disclaimer
This is an unofficial library, not affiliated with Fronius International GmbH. Based on the public Fronius Modbus TCP & RTU operating instructions and the Fronius SunSpec register maps (fronius.com/QR-link/0006).
Metadata
Release files for fronius-modbus 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fronius_modbus-0.3.0.tar.gz | 25.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fronius_modbus-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 56.1 kB
Release files / fronius_modbus-0.3.0.tar.gz
| Download URL | fronius_modbus-0.3.0.tar.gz |
|---|---|
| Size | 25.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
887ee14c4379090483a80f27819b3add89cdefb5215e632a329e0658c299c110
|
|
BLAKE2b-256 checksum How to use checksums |
f2d5bfb44bd5bca15412efd81a34f380e95e4a74fcb27f9b8df4b3cbd56e441f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.
Transparency logRelease files / fronius_modbus-0.3.0-py3-none-any.whl
| Download URL | fronius_modbus-0.3.0-py3-none-any.whl |
|---|---|
| Size | 30.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5cb749725beb0b2379b17dafdedb8c0e629962186c31981131ab3ae2b46cb486
|
|
BLAKE2b-256 checksum How to use checksums |
02d442989632dfc3b42eabb66a8468d3f25e673f0c02c1d1b552bcdbfe14a32e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.
Transparency log