Skip to content

Latest commit

 

History

History
114 lines (79 loc) · 3.39 KB

File metadata and controls

114 lines (79 loc) · 3.39 KB

sma-modbus

Async Python library for the Modbus TCP interface of SMA devices, built on modbus-connection.

Warning

Developer Preview

Working, but do not use in production. Please report any issues you come across.

Note

Based on a partial agentic port of fronius-modbus. 🫶

Supports the following SMA devices (so far):

  • Sunny Home Manager 2.0
  • Sunny Boy Smart Energy 3.6-6.0
  • Sunny Boy 3.0-6.0
  • Sunny Tripower 3.0-6.0 untested

Not all Modbus parameters have been added, yet. Support for read/write registers maybe later.

The SMA register map is mostly fixed, but it has been slightly modified with firmware updates in the past.

Reading

The library consumes a ModbusConnection and manages its own unit handles internally. Use discover() to auto-detect the device type and serial number from the Type Label (probes unit ID 1, then unit ID 3):

import asyncio

from modbus_connection.tmodbus import connect_tcp

from sma_modbus import DEVICE_CLASSES, discover


async def main() -> None:
    connection = await connect_tcp("192.168.1.50", port=502)
    info = await discover(connection)
    inverter = DEVICE_CLASSES[info.device_type](connection, info.unit_id)

    # one pooled read refreshes the whole device, block by block
    await inverter.async_update()

    print("PV power:", inverter.pv_power, "W")
    print("PV energy:", inverter.pv_energy_total, "Wh")
    print("Battery SoC:", inverter.battery_state_of_charge, "%")
    print("DC string 1:", inverter.dc_voltage_1, "V", inverter.dc_power_1, "W")

    await connection.close()


asyncio.run(main())

Pass unit_id= to discover() to read the Type Label from a specific unit ID and use it for measurements. This covers inverters that have been reconfigured to a non-default unit ID. Without unit_id, discovery probes unit IDs 1 and 3 and uses the device type's standard default (3 for inverters, 2 for the Sunny Home Manager):

info = await discover(connection, unit_id=5)

A field reads as None when the device reports its not-a-value sentinel, so a powered-down or unsupported measurement is distinct from a real zero.

Testing on real hardware

scripts/read_device.py is a one-shot dump of everything the library reads:

uv run scripts/read_device.py <host> [--port 502] [--unit <id>]

The device type is auto-detected. Use --unit to override the measurement unit ID if the device has been reconfigured.

Modbus must be enabled on the device.

Testing support

sma_modbus.testing provides set_input_registers() to load a modbus_connection.mock.MockModbusConnection with raw register words for a component:

from modbus_connection.mock import MockModbusConnection
from sma_modbus import SunnyHomeManager
from sma_modbus.testing import set_input_registers

connection = MockModbusConnection()
device = SunnyHomeManager(connection)
set_input_registers(
    connection,
    device,
    {"grid_import_energy": 123456, "grid_export_power": 750},
)
await device.async_update()
assert device.grid_import_energy == 123456

The mock_modbus_connection fixture (shipped by modbus_connection's pytest plugin) hands a ready-to-configure connection to each test.

Disclaimer

This is an unofficial library and in no way affiliated with SMA Solar Technology AG.