A lot of energy hardware already has a local data interface that often goes unused: DIN-rail energy meters, solar inverters, heat pumps and ventilation units often speak Modbus. The protocol comes from industrial controllers and needs no cloud or account. Once it works, you can poll voltage, power and energy counters as often as the device allows.
Getting the first correct number out of a device is the hard part: the wiring has rules that are easy to break, registers are numbered in two different ways, and a 32-bit value turns into nonsense if you read its halves in the wrong order. This guide goes through those problems in the order you hit them, using an Eastron SDM120 single-phase meter and a Fronius GEN24 inverter as examples because both publish their register maps.
RTU or TCP: find out what your device has
Modbus comes in two common flavours for home use.
Modbus TCP runs over Ethernet or Wi-Fi. The standard port is 502, although some devices let you pick another one (Fronius GEN24 offers 502 or 1502). The Home Assistant Modbus documentation points out that many devices ship with Modbus TCP switched off, so check the device's web interface or installer menu first. On a GEN24 the setting lives under Communication > Modbus.
Modbus RTU runs over a serial line, almost always RS-485 in this context: a two-wire differential bus plus a common conductor. To talk to it from a computer you need a USB to RS-485 adapter, or a gateway that connects the bus to your network. Gateways work in one of two ways: some translate RTU to Modbus TCP, others pass the RTU frames through unchanged over TCP (often called transparent mode or RTU over TCP). Your software has to know which one it is talking to.
One rule from the serial line specification shapes everything else: an RS-485 Modbus bus has exactly one master (client) at a time, and every other device only answers. If the meter's RS-485 port is already polled by something else, such as a manufacturer's logger or the inverter itself, you cannot simply clip a second master onto the same pair. Fronius repeats this in its GEN24 manual for RTU. In that situation, look for a TCP interface on the device that already does the polling.
Wiring an RS-485 bus that works
Most "Modbus doesn't work" problems on RTU are wiring problems. The relevant rules come from the Modbus serial line specification (V1.02) and vendor application notes.
- Daisy chain, not star. Run one trunk cable from device to device. The specification allows short derivations (stubs) of up to 20 m, and HMS Networks advises against star, tree and ring layouts.
- Terminate both ends, and only the ends. The specification describes a line termination at each end of the trunk: 150 ohm (0.5 W), or 120 ohm (0.25 W) in series with a 1 nF capacitor, which the specification calls the better choice when the line is polarised. Never more than two terminations, and never on a stub. Many adapters and meters have a built-in termination resistor behind a switch or jumper, so check that it is enabled only on the two end devices.
- Run the common wire. The specification calls for a common conductor connecting all devices, grounded at a single point. Shielded twisted pair is the usual cable; Fronius recommends it for GEN24 RTU wiring together with single-point grounding.
- Polarise once. If the bus needs biasing resistors, they belong at one location only, with a value between 450 and 650 ohm according to the specification. Many USB adapters already include them.
- Distance and device count. The specification gives a maximum of 1000 m at 9600 baud or slower with AWG26 or thicker cable, and up to 32 devices without a repeater.
The A and B labels deserve a warning of their own. The Modbus specification names the lines D0 (A) and D1 (B), and in the RS-485 standard A is the negative line and B the positive one. Manufacturers do not follow this consistently: HMS Networks notes that devices are labelled +/-, D+/D-, or A/B with the meaning reversed. If everything else is right and nothing answers, swapping the two data wires is the first thing to try.
Serial settings and addresses
Each device on an RTU bus has an address, also called slave ID or unit ID, from 1 to 247. Address 0 is a broadcast for write commands, and devices do not reply to it. Addresses must be unique, and this is the classic trap when you buy two identical meters: both come with address 1. Connect them one at a time and give the first one a new address before adding the second.
The serial settings must match exactly on both sides: baud rate, data bits, parity and stop bits. The specification makes 19200 baud with even parity the default and requires 9600 and 19200 to be supported, but real devices differ. The SDM120 manual lists 2400 baud, 8 data bits, no parity and one stop bit as the factory setting, with address 1.
A device that receives a frame with a parity or CRC error does not answer at all. From your side, a wrong baud rate, a wrong parity, a wrong address, swapped wires and a missing device all look the same: a timeout. Change one thing at a time.
On the SDM120, address, baud rate and parity are holding registers (40021, 40029 and 40019), written as floating point values, and the manual says the meter needs a restart before the new serial settings apply. Other meters do it differently, so read the manual before writing anything.
Registers, function codes and the off-by-one problem
The Modbus application protocol has four data tables:
- coils (single bits, read and write);
- discrete inputs (single bits, read only);
- input registers (16-bit, read only);
- holding registers (16-bit, read and write).
You read input registers with function code 04 and holding registers with function code 03, up to 125 registers per request. Writes use function code 06 (one register) or 16 (several).
Now the confusing part. The specification says data elements are numbered from 1 to n but addressed from 0 to n-1 on the wire. Many manuals use a "register number" with a prefix digit for the table: 3xxxx for input registers, 4xxxx for holding registers. In the SDM120 manual, voltage is register 30001, which means input register number 1, which is address 0 in the request. Active power is 30013 (address 12, or 0x000C), and total active energy is 30343 (address 342, or 0x0156).
Software disagrees about which form it expects. The Home Assistant Modbus integration wants the zero-based address, so 40001 becomes 0. The mbpoll command-line tool takes the one-based reference by default and switches to zero-based addressing with the -0 option. If your values look plausible but belong to the neighbouring register, you are probably off by one.
Reading 32-bit values
Registers are 16 bits wide and Modbus transmits them big-endian. Anything larger, such as a 32-bit float or a 32-bit energy counter, spans two registers, and the protocol does not define which of the two comes first.
The SDM120 stores its readings as 32-bit IEEE 754 floats in two consecutive input registers, with the high word first. Its manual also says the start address and the number of registers must both be even, otherwise the request lands in the middle of a value and the meter returns an error.
If a float comes back as a tiny number with a huge negative exponent, or as an enormous one, try the opposite word order before suspecting the wiring. A wiring fault gives you a timeout, not a wrong number.
First reading from the command line
mbpoll is a small command-line Modbus master that works over RTU and TCP. Based on its manual page, this reads the SDM120 voltage once over a USB adapter, using the meter's default serial settings:
mbpoll -m rtu -a 1 -b 2400 -P none -t 3:float -B -r 1 -c 1 -1 /dev/ttyUSB0
What each option does:
-m rtuselects Modbus RTU (TCP is the default);-a 1is the device address;-b 2400 -P nonesets baud rate and parity (mbpoll defaults to 19200 and even parity);-t 3:floatreads input registers as 32-bit floats;-Buses big-endian word order for 32-bit values;-r 1is the one-based start reference, so register 30001;-c 1reads one value and-1polls once instead of repeating.
Change -r 1 to -r 13 for active power. For a Modbus TCP device, drop -m rtu and the serial options and give the IP address instead of the port, adding -p if the device does not use port 502.
Reading the same value from Python
pymodbus is the usual choice in Python. This snippet reads the voltage register over Modbus TCP, for example through a gateway that translates RTU to TCP. It was checked against the pymodbus simulator with pymodbus 3.16.1. Releases before 3.10 used slave= instead of device_id=.
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient("192.168.1.50", port=502)
client.connect()
rr = client.read_input_registers(0, count=2, device_id=1)
if rr.isError():
print("Modbus error:", rr)
else:
v = client.convert_from_registers(rr.registers, data_type=client.DATATYPE.FLOAT32)
print(f"Voltage: {v:.1f} V")
client.close()
For a USB adapter, replace the client with ModbusSerialClient(port="/dev/ttyUSB0", baudrate=2400, parity="N", stopbits=1, bytesize=8, timeout=1) and import it from pymodbus.client as well. Note the 0 in read_input_registers(0, ...): that is the zero-based address of register 30001.
Inverters and SunSpec
Many solar inverters, including Fronius and SolarEdge models, expose their data through SunSpec, a standard set of register maps on top of Modbus. Two details matter in practice.
First, the maps are found by searching rather than at a fixed address. A SunSpec device places its maps starting at address 0, 40000 or 50000 and marks the start with the registers 0x5375 0x6E53, which spell "SunS" in ASCII. The Common Model with ID 1 comes next and describes the device. Fronius states in its GEN24 manual that register addresses are dynamic and that the start of each model should be searched for rather than hard-coded.
Second, integer maps use scale factors. A value is stored as an integer and a separate scale factor register gives the power of ten: the real value is value × 10^SF. Fronius lets you choose between float and integer plus scale factor maps. Values that a device does not implement come back as special markers such as 0x8000, 0xFFFF or NaN, depending on the data type, and your software should treat them as missing rather than as a reading.
The Fronius manual adds some practical limits: a timeout of at least one second, requests sent one after another rather than in parallel, and on Modbus TCP an inverter unit ID of 1. Writes to read-only registers are silently ignored, so a missing error does not mean a write worked.
Treat Modbus TCP as an open door
Modbus TCP has no authentication and no encryption. The Home Assistant documentation puts it bluntly: any device that can reach the port can read and write registers. On an inverter or heat pump, holding registers can include settings that change how the device runs.
Some practical consequences:
- never forward port 502 from your router to the internet;
- put Modbus devices in a separate network segment and allow only the machine that polls them, as described in our article on IoT VLANs;
- use device-side restrictions where they exist: the GEN24 can restrict control to specific IP addresses, and control can stay switched off if you only read data.
The Modbus Organization has published a secure variant, Modbus/TCP Security, which wraps the protocol in TLS with mutual X.509 certificate authentication on port 802. Support for it in home energy devices is rare, so plan your network as if it did not exist.
Where to go next
Once a register reads correctly from the command line, a permanent setup is mostly configuration. Home Assistant has device-specific integrations for some hardware (Fronius, SolarEdge and others) and a generic Modbus integration, configured in YAML with the addresses, data types and word order you have just worked out. ESPHome's Modbus Controller component lets an ESP32 with an RS-485 transceiver poll a meter on its own. Either way, keep requests modest and sequential.