IoT Automation

Solar Inverter Monitoring over MQTT with SunSpec Modbus and Python

Read live production data from your PV inverter over SunSpec Modbus TCP or a local API, publish power, daily energy and status to MQTT, and make solar data available to every dashboard and automation in the house.

Rooftop solar panels and a wall-mounted inverter streaming data to a cloud dashboard for solar inverter monitoring
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

Solar inverter monitoring over MQTT frees your production data from the vendor app and makes it available to dashboards, home automation and load-shifting logic in real time. In this tutorial you will read power, energy and status from an inverter using the SunSpec Modbus TCP standard with Python, publish clean JSON to a broker every few seconds, and understand the pitfalls that trip up most first attempts. Alternative routes through local HTTP APIs and RS485 are covered too.

Three Ways to Get Data out of an Inverter

Vendor portals are fine for a monthly glance, but they usually update slowly and depend on the internet. Local access is faster, works offline and is what you want for automations such as running a heat pump on surplus.

Method Typical inverters Pros Cons
SunSpec Modbus TCP Many string inverters from major brands Standardized register map, LAN only, fast Must often be enabled in installer settings
Vendor local HTTP API Some brands expose JSON on the LAN Easy to read with any HTTP client Different per vendor, may need tokens
Modbus RTU over RS485 Many hybrid and budget inverters Works without a network port Needs an RS485 adapter and the vendor register map

SunSpec is the best starting point when your inverter supports it, because the same code works across brands. The SunSpec Alliance defines "models": blocks of registers with a fixed layout. The inverter models 101 (single phase), 102 (split phase) and 103 (three phase) share the same field layout, which is what the code below relies on.

Enabling Modbus TCP on the Inverter

Every brand hides this setting differently, but the pattern is the same:

  1. Give the inverter a fixed IP address with a DHCP reservation.
  2. In the installer or commissioning menu, enable Modbus TCP (sometimes called "SunSpec" or "Modbus slave").
  3. Note the port (almost always 502) and the unit ID (often 1, sometimes 126 or another value).
  4. Choose SunSpec / float or int+SF register format if the inverter asks; this guide uses the integer-plus-scale-factor format, which is the most widely supported.
  5. If your inverter has a "read only" option for Modbus, enable it. You only need to read.

Some inverters accept only one Modbus TCP client at a time. If an energy manager or wallbox controller is already connected, your script may be refused or may knock the other client off, so plan which system owns the connection.

How SunSpec Registers Work

A SunSpec device starts with the marker SunS at a base address, usually 40000. After the marker comes a chain of models, each beginning with a model ID and a length. You walk the chain until you find model 101, 102 or 103, then read fields at fixed offsets:

Offset in model Field Type Meaning
2 A uint16 AC current, scaled by A_SF (offset 6)
10 PhVphA uint16 Phase A voltage, scaled by V_SF (offset 13)
14 W int16 AC power, scaled by W_SF (offset 15)
16 Hz uint16 Frequency, scaled by Hz_SF (offset 17)
24–25 WH acc32 Lifetime energy in Wh, scaled by WH_SF (offset 26)
31 DCW int16 DC power, scaled by DCW_SF (offset 32)
33 TmpCab int16 Cabinet temperature, scaled by Tmp_SF (offset 37)
38 St enum16 Operating state (4 = MPPT, producing)

Offsets count from the model ID register. Each value is multiplied by 10 raised to its signed scale factor, so a raw W of 51234 with W_SF of -1 means 5123.4 W. Unimplemented registers return 0x8000 (signed) or 0xFFFF (unsigned) and must be ignored.

The Python Publisher

Install the libraries:

pip install "paho-mqtt>=2.0" "pymodbus>=3.7"

The script finds the SunSpec base, walks the models, reads the inverter block and publishes JSON. It uses the free public test broker first, with a random prefix because that area is readable by everyone.

import json
import time
import uuid
import paho.mqtt.client as mqtt
from pymodbus.client import ModbusTcpClient

INVERTER_IP = "192.168.1.60"
INVERTER_PORT = 502
POLL_SECONDS = 10

PREFIX = f"connectmq/demo/{uuid.uuid4().hex[:8]}/solar/inverter1"
STATE_NAMES = {1: "off", 2: "sleeping", 3: "starting", 4: "mppt",
               5: "throttled", 6: "shutting_down", 7: "fault", 8: "standby"}


def s16(v):
    return None if v == 0x8000 else (v - 0x10000 if v & 0x8000 else v)


def u16(v):
    return None if v == 0xFFFF else v


def scaled(value, sf):
    if value is None or sf is None:
        return None
    return round(value * (10 ** sf), 3)


def kwh(wh_raw, sf):
    if not wh_raw or sf is None:      # acc32 value 0 means not implemented
        return None
    return round(wh_raw * (10 ** sf) / 1000, 3)


def find_inverter_model(client):
    for base in (40000, 39999, 50000, 0):
        rr = client.read_holding_registers(base, count=2)
        if rr.isError() or rr.registers != [0x5375, 0x6E53]:   # "SunS"
            continue
        addr = base + 2
        for _ in range(30):
            rr = client.read_holding_registers(addr, count=2)
            if rr.isError():
                break
            model_id, length = rr.registers
            if model_id == 0xFFFF:
                break
            if model_id in (101, 102, 103):
                return addr, length, model_id
            addr += 2 + length
    raise RuntimeError("No SunSpec inverter model found - check Modbus settings and unit ID")


def read_inverter(client, addr, length):
    rr = client.read_holding_registers(addr, count=length + 2)
    if rr.isError():
        raise IOError(rr)
    r = rr.registers
    wh_raw = (r[24] << 16) | r[25]
    return {
        "ac_power_w": scaled(s16(r[14]), s16(r[15])),
        "ac_current_a": scaled(u16(r[2]), s16(r[6])),
        "ac_voltage_v": scaled(u16(r[10]), s16(r[13])),
        "frequency_hz": scaled(u16(r[16]), s16(r[17])),
        "energy_total_kwh": kwh(wh_raw, s16(r[26])),
        "dc_power_w": scaled(s16(r[31]), s16(r[32])),
        "temp_c": scaled(s16(r[33]), s16(r[37])),
        "state": STATE_NAMES.get(r[38], str(r[38])),
    }


mq = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                 client_id=f"solar-{uuid.uuid4().hex[:6]}")
mq.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
mq.connect("broker.connectmq.com", 1883, keepalive=60)
mq.loop_start()
mq.publish(f"{PREFIX}/status", "online", qos=1, retain=True)
print("Publishing to", PREFIX)

mb = ModbusTcpClient(INVERTER_IP, port=INVERTER_PORT, timeout=3)
model_addr = model_len = None

while True:
    try:
        if not mb.connected:
            mb.connect()
        if model_addr is None:
            model_addr, model_len, model_id = find_inverter_model(mb)
            print(f"Found SunSpec model {model_id} at {model_addr}")
        data = read_inverter(mb, model_addr, model_len)
        data["ts"] = int(time.time())
        mq.publish(f"{PREFIX}/telemetry", json.dumps(data), qos=1, retain=True)
        print(data)
    except Exception as exc:
        print("Read failed:", exc)
        mb.close()
        model_addr = None
    time.sleep(POLL_SECONDS)

If your inverter uses a unit ID other than 1, pass it on each read with the device_id keyword (called slave in older pymodbus releases). Watch the output from another machine:

mosquitto_sub -h broker.connectmq.com -p 1883 -i solar-viewer-2290 -t "connectmq/demo/+/solar/#" -v

A typical message looks like this:

{"ac_power_w": 4183.0, "ac_current_a": 6.05, "ac_voltage_v": 231.4, "frequency_hz": 50.01,
 "energy_total_kwh": 18342.611, "dc_power_w": 4301.0, "temp_c": 41.2, "state": "mppt", "ts": 1760603520}

Solar Inverter Monitoring Pitfalls

Most problems come from a handful of causes:

  • Off-by-one addresses. Documentation mixes 0-based and 1-based register numbering. The script tries both 40000 and 39999 for this reason.
  • Scale factors change. Some inverters change W_SF dynamically. Always read the scale factor in the same request as the value, as the code does by reading the whole block.
  • Night time. Many inverters shut down their communication board at night or report state "sleeping" with empty values. Treat timeouts after sunset as normal and keep the last retained value.
  • Poll rate. Every 5 to 10 seconds is plenty for dashboards and surplus control. Polling several times per second gains nothing and can upset some inverters.
  • Meter versus inverter. The inverter knows production, not consumption or export. For self-consumption logic you also need a grid meter; the home energy monitor guide covers that half.
  • Hybrid inverters with batteries often expose battery data in vendor-specific models rather than standard SunSpec ones. See home battery monitoring for publishing BMS values.

Using the Data

Once production is on MQTT, every consumer can use it without touching the inverter again. Home Assistant can map the JSON to sensors through its MQTT integration, Node-RED can drive a smart plug when production exceeds a threshold, and Telegraf can store everything in a time-series database for monthly yield reports. Because the telemetry message is retained, a dashboard that opens at noon sees current production immediately.

Integrations work this way on purpose: the broker is the hub, and each tool connects to it as an MQTT client. If the idea is new, the overview of what an MQTT broker does explains the publish/subscribe model behind it.

Going to Production with a Private Broker

Solar data is not secret in the way passwords are, but it does reveal occupancy patterns and system details, and the public broker is shared. For a permanent setup, run the publisher on a Raspberry Pi or home server and send data to a cloud MQTT broker over TLS. With ConnectMQ, create a broker in the portal, copy the generated credentials and replace the connection lines:

mq = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
mq.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
mq.tls_set()
mq.will_set("home/solar/inverter1/status", "offline", qos=1, retain=True)
mq.connect("broker.connectmq.com", 8883, keepalive=60)

A cloud broker also lets you view production from your phone without exposing anything on your home network, because the Raspberry Pi only makes an outbound connection. The broader IoT automation guide shows how solar data fits into whole-home energy flows.

Monitor Your Solar Production with ConnectMQ

ConnectMQ is a managed MQTT broker with TLS on port 8883, WebSocket access for browser dashboards, a live topic explorer and threshold alert rules, for example to warn you when an inverter reports a fault state during daylight. Create a free Serverless broker at app.connectmq.com, no credit card required.

Frequently Asked Questions

Does every inverter support SunSpec Modbus?

No. Many established string inverter brands do, often after enabling it in installer settings, while some hybrid and budget inverters only offer vendor-specific Modbus RTU maps or cloud APIs. Check your model's documentation for "SunSpec".

How often should I poll the inverter?

Every 5 to 10 seconds is enough for live dashboards and surplus-based automations. Faster polling rarely adds value and some inverters respond poorly to it.

Why does my script fail at night?

Many inverters power down their communication interface when there is no PV input. Treat night-time timeouts as expected and rely on the last retained MQTT message.

Can I control the inverter over MQTT?

Some inverters accept SunSpec control registers for power limiting, but writing to an inverter can violate grid-connection rules. Keep this tutorial read-only unless your installer confirms what is allowed.

Related articles

Zigbee2MQTT Setup Guide: From Coordinator to Dashboard

Install Zigbee2MQTT with Docker, configure the coordinator and MQTT connection, pair and rename devices, control them over MQTT and view their states on a dashboard, testing first on a free public broker.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.