Smart Manufacturing

Electrical Sub-Metering IoT: Modbus Energy Meters to MQTT

Connect DIN-rail Modbus energy meters on an RS-485 bus to a gateway, decode the registers in Python and publish per-machine kWh to an MQTT broker so every machine gets its own energy figure.

Electrical sub-metering IoT setup with DIN-rail Modbus energy meters on an RS-485 bus, a gateway and MQTT data flowing to a cloud broker
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

Electrical sub-metering with IoT meters attributes the plant's electricity to individual machines, lines and utilities instead of one main meter. In this tutorial you will connect DIN-rail Modbus energy meters on an RS-485 bus to a small gateway, read voltage, current, power and energy counters with Python, and publish them over MQTT so every machine has its own live and historical energy figure.

Why Sub-Meter at Machine Level

The main meter answers "how much"; sub-meters answer "where" and "when". Once each large machine has its own meter you can see idle consumption between orders, compare identical machines, verify the effect of a new drive or a changed setpoint, and report energy per part. Sub-metering is also the measurement backbone of plant-wide factory energy monitoring and of an ISO 50001 data collection plan.

Choosing Meters and a Gateway

Component Common choice Notes
Three-phase meter DIN-rail multifunction meter with RS-485 Modbus RTU, CT inputs (e.g. Eastron SDM630 MCT, similar models from other vendors) Check the register map is published
Current transformers Split-core CTs sized to the feeder, or Rogowski coils Split-core allows retrofit without disconnecting cables
Bus Twisted-pair RS-485, daisy-chained, 120 ohm termination at both ends Up to 32 unit loads per segment without repeaters
Gateway Raspberry Pi with USB-RS485 adapter, industrial edge PC, or a Modbus-TCP-to-RTU converter Needs a reliable network path to the broker
Software Python with pymodbus and paho-mqtt, or Node-RED Python shown below

Give each meter a unique Modbus address (1, 2, 3 and so on) and the same baud rate and parity, configured from the meter's front panel. The examples use 9600 baud, 8 data bits, no parity, 1 stop bit.

Electrical installation of meters and CTs must be done by a qualified electrician following local regulations. CTs with secondary outputs must never be open-circuited while the primary carries current, unless they are of the safe, voltage-output type.

Register Map

Multifunction meters expose readings as 32-bit IEEE 754 floats spread over two 16-bit input registers. For the SDM630 family, a few useful input registers (function code 04) are:

Value Register (decimal) Unit
Phase 1 voltage 0 V
Phase 1 current 6 A
Total system power 52 W
Total system power factor 62 -
Frequency 70 Hz
Total import active energy 72 kWh

Other meters use different addresses, function codes or word orders. Always work from the manufacturer's register document; a wrong word order produces numbers that look plausible but are wildly off.

Wiring the Gateway

  1. Connect the USB-RS485 adapter's A and B (sometimes D+ and D-) to the first meter's A and B terminals, then daisy-chain to the other meters. Avoid star topologies.
  2. Fit 120 ohm termination resistors at the two physical ends of the bus if the meters or adapter do not have them built in.
  3. Connect the reference ground if the adapter provides one, especially on longer runs.
  4. Plug the adapter into the Raspberry Pi and confirm the device appears:
ls -l /dev/ttyUSB*
sudo usermod -aG dialout $USER     # log out and in again afterwards

python3 -m venv ~/meter-env
source ~/meter-env/bin/activate
pip install "pymodbus>=3.10" paho-mqtt pyserial

The Electrical Sub-Metering IoT Gateway Script

The script polls each meter in turn, decodes the floats, and publishes one JSON message per meter to a topic that names the machine it supplies. It uses the free public test broker for the demo; the production variant is shown afterwards.

#!/usr/bin/env python3
"""Electrical sub-metering gateway: Modbus RTU meters -> MQTT."""
import json
import struct
import time

import paho.mqtt.client as mqtt
from pymodbus.client import ModbusSerialClient

BROKER = "broker.connectmq.com"
PREFIX = "connectmq/demo/subm-4q9z/plant1"
POLL_SECONDS = 10

# Modbus address -> topic path of the machine it measures
METERS = {
    1: "molding/imm-01",
    2: "molding/imm-02",
    3: "utilities/comp-1",
}

REGISTERS = {               # name: input register address (float32, 2 words)
    "v_l1": 0,
    "i_l1": 6,
    "kw": 52,
    "pf": 62,
    "hz": 70,
    "kwh_total": 72,
}


def read_float(modbus, address, unit):
    rr = modbus.read_input_registers(address, count=2, device_id=unit)
    if rr.isError():
        raise IOError(f"Modbus error unit {unit} reg {address}: {rr}")
    return struct.unpack(">f", struct.pack(">HH", *rr.registers))[0]


def read_meter(modbus, unit):
    values = {name: read_float(modbus, reg, unit) for name, reg in REGISTERS.items()}
    values["kw"] = values["kw"] / 1000.0        # meter reports watts
    return {k: round(v, 3) for k, v in values.items()}


def on_connect(client, userdata, flags, reason_code, properties):
    print("MQTT connected:", reason_code)
    client.publish(f"{PREFIX}/gateway/status", "online", qos=1, retain=True)


def main():
    modbus = ModbusSerialClient(
        port="/dev/ttyUSB0", baudrate=9600, bytesize=8, parity="N", stopbits=1, timeout=1
    )
    if not modbus.connect():
        raise SystemExit("Cannot open serial port")

    mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="subm-gw-4q9z")
    mqttc.will_set(f"{PREFIX}/gateway/status", "offline", qos=1, retain=True)
    mqttc.on_connect = on_connect
    mqttc.connect(BROKER, 1883, keepalive=60)
    mqttc.loop_start()

    while True:
        cycle_start = time.time()
        for unit, path in METERS.items():
            topic = f"{PREFIX}/{path}/energy"
            try:
                payload = {"ts": int(time.time()), "meter": unit, **read_meter(modbus, unit)}
                mqttc.publish(topic, json.dumps(payload), qos=1)
                print(topic, payload)
            except IOError as err:
                mqttc.publish(f"{topic}/error", str(err), qos=1)
                print(err)
            time.sleep(0.05)                         # short gap between meters on the bus
        time.sleep(max(0, POLL_SECONDS - (time.time() - cycle_start)))


if __name__ == "__main__":
    main()

Watch the output from any machine with the Mosquitto client:

mosquitto_sub -h broker.connectmq.com -p 1883 -i subm-viewer-$RANDOM \
  -t "connectmq/demo/subm-4q9z/plant1/#" -v

The public MQTT test broker is ideal for this first test, but it is readable by anyone, so switch to a private broker before connecting real production equipment.

Going to Production

Move the gateway to authenticated TLS by replacing the connection lines:

mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
mqttc.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
mqttc.tls_set()
mqttc.will_set(f"{PREFIX}/gateway/status", "offline", qos=1, retain=True)
mqttc.connect("broker.connectmq.com", 8883, keepalive=60)

Change PREFIX to your own namespace, for example connectmq/plant1, to follow the ISA-95 hierarchy. ConnectMQ recognises this kind of structure and lists devices by site, area and line in its portal, which makes a few dozen meters easy to navigate. Run the script as a systemd service so it restarts on failure and on boot.

Attributing Energy to Machines

With one topic per machine, attribution is straightforward:

  1. Energy per period: subtract the kwh_total counter at the start of the shift or day from the value at the end.
  2. Idle energy: combine power with machine state. Energy consumed while the machine is idle or stopped, using the states from machine status monitoring, is the most actionable number for many plants.
  3. Unmetered remainder: main meter minus the sum of sub-meters. A large or growing remainder points to unmetered loads or a meter configuration error.
  4. Energy per part: divide machine kWh by good parts for the same period.

Store the stream with Telegraf or a small subscriber in a time-series database, and calculate these figures there; the broker carries live data and retained last values, not history.

Planning a Sub-Metering Rollout

Meter the largest consumers first. A short walk with a clamp meter, or a look at motor and heater nameplates, usually shows that a handful of machines and utilities account for a large part of the load. Put those on the first bus, prove the data path end to end, and only then extend to smaller loads.

Keep a meter register from the beginning: Modbus address, physical location, feeder, CT ratio, installation date and the topic it publishes to. This list is what lets someone else maintain the system, and it is exactly the kind of documented information an energy audit asks for. When a meter is replaced, record the final counter of the old meter and the initial counter of the new one so period calculations stay correct.

Finally, decide polling intervals per purpose. Ten seconds is plenty for live dashboards and idle-energy detection; billing-style reports only need the counters, so a slower interval on a long bus is acceptable.

Troubleshooting Sub-Meters

Symptom Likely cause Fix
Timeouts on every meter A and B swapped, wrong baud rate or parity Swap A/B, match serial settings on all devices
One meter times out Duplicate or wrong Modbus address Check the address on the meter display
Values like 1.4e-38 or huge numbers Wrong word order or register offset Verify the register map; try swapping the two registers
Power negative CT mounted backwards or phase mismatch Reverse the CT or fix phase assignment
Power too high by a constant factor Wrong CT ratio configured in the meter Set the correct primary/secondary ratio
Intermittent errors on long buses Missing termination or no ground reference Terminate both ends, connect reference ground

Beyond kWh

Many multifunction meters also expose total harmonic distortion, per-phase values and demand registers. Those feed directly into power quality monitoring, which helps explain nuisance trips and overheating transformers. For a generic look at bridging any Modbus RTU device, see Modbus RTU to MQTT. The smart manufacturing guide places sub-metering in the wider roadmap of connected production.

Connect Your Meters to ConnectMQ

ConnectMQ is a hosted MQTT broker with TLS, MQTT 5, a live topic explorer, an ISA-95 devices view and threshold alerts on fields such as kw or pf. Create a free Serverless broker with no credit card, paste in the generated credentials, and your sub-meters are online.

Frequently Asked Questions

How many energy meters can share one RS-485 bus?

A standard RS-485 segment supports up to 32 unit loads, and many modern transceivers present fractional loads so more fit. In practice, polling time sets the limit: more meters per bus means a longer cycle for each meter.

Can I use Modbus TCP meters instead?

Yes. Replace ModbusSerialClient with ModbusTcpClient and the meter's IP address; the register decoding and MQTT publishing stay the same.

Should the gateway compute kWh from power readings?

No. Publish the meter's own cumulative energy register. It is accurate between polls and survives gateway restarts and lost messages.

Is electrical sub-metering IoT data secure over the internet?

Use TLS on port 8883 with credentials issued for each gateway. On ConnectMQ, client IDs are bound to their credentials and repeated failed logins lead to IP blocking.

Related articles

OEE vs TEEP vs OOE: Which Metric Should You Use?

OEE, OOE and TEEP share the same performance and quality factors but measure against different time bases. This comparison works through one example and lists the MQTT signals each metric needs.

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.