IoT Automation

Smart Plug MQTT Energy Monitoring: Collect Power Data and Graph Daily Consumption

Collect power and energy readings from MQTT-capable smart plugs, store them in SQLite with a small Python subscriber, and turn them into daily kWh charts per appliance that survive plug reboots and counter resets.

Smart plugs on kitchen appliances feeding a daily consumption bar chart in a smart plug mqtt project
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

Smart plug MQTT monitoring is the cheapest way to find out what each appliance in your home really costs to run. In this tutorial you will connect Tasmota and Shelly plugs to a broker, normalize their different payloads, store readings in SQLite with a Python subscriber, and produce a daily kWh chart per appliance. The approach handles reboots and counter resets, which is where most homemade energy loggers quietly go wrong.

Which Smart Plugs Speak MQTT

You need a plug that can publish to a broker you choose, not only to a vendor cloud. Three families cover most of the market for this purpose:

Plug family How MQTT is enabled Energy fields Notes
Tasmota-flashed plugs (many ESP-based models) Built into Tasmota firmware ENERGY.Power (W), ENERGY.Total (kWh), Today, Yesterday Some models ship with Tasmota; others need flashing
Shelly Plug S Gen3 / Plus Plug Local web UI, Settings > MQTT apower (W), aenergy.total (Wh) Gen2/Gen3 JSON-RPC API
ESPHome-based plugs mqtt: block in ESPHome YAML One topic per sensor Flexible but per-entity topics

Check that the plug has power metering before you buy; many cheap plugs only switch. Also check the rated current against the appliance, since kettles, heaters and dryers are near the limit of most plugs.

Connecting a Tasmota Plug

Open the Tasmota web UI, then Configuration > Configure MQTT, or use the console for a faster setup. The Backlog command applies several settings in one go and restarts the plug:

Backlog MqttHost broker.connectmq.com; MqttPort 1883; MqttClient plug-kitchen-kettle-9f2; Topic kettle; FullTopic connectmq/demo/p4w8/%prefix%/%topic%/; TelePeriod 60

This makes the plug publish its telemetry every 60 seconds to connectmq/demo/p4w8/tele/kettle/SENSOR, with a payload like:

{
  "Time": "2026-10-16T07:31:12",
  "ENERGY": {
    "TotalStartTime": "2025-11-02T18:04:51",
    "Total": 48.215,
    "Yesterday": 0.412,
    "Today": 0.197,
    "Period": 3,
    "Power": 0,
    "ApparentPower": 0,
    "ReactivePower": 0,
    "Factor": 0.00,
    "Voltage": 231,
    "Current": 0.000
  }
}

Total is the lifetime energy in kWh, Power is the instantaneous active power in watts. Use a random path segment such as p4w8 because the public test broker is shared and readable by everyone. The Tasmota-specific details, including TLS builds, are covered in the Tasmota MQTT guide.

Connecting a Shelly Plug

On a Shelly Plus Plug or Plug S Gen3, open the local web UI, go to Settings > MQTT, enable it, set the server to broker.connectmq.com:1883, set the prefix to connectmq/demo/p4w8/shelly/fridge, and enable Generic status update over MQTT. The plug then publishes complete snapshots on connectmq/demo/p4w8/shelly/fridge/status/switch:0, where apower is watts and aenergy.total is the lifetime energy in watt-hours, not kWh. Mixing up those units is the most common bug when combining brands. The Shelly MQTT setup guide explains the full topic and RPC structure.

Designing the Smart Plug MQTT Data Pipeline

Every plug already publishes to the broker; the missing piece is something that listens and keeps history. Brokers deliver live messages and retained values but are not databases, so the logger is a separate MQTT client. The plan:

  1. Subscribe to both plug formats with wildcards.
  2. Normalize each message into (time, plug, power_w, total_kwh).
  3. Store every sample in SQLite.
  4. Compute daily energy from the difference between consecutive lifetime totals, ignoring negative jumps caused by counter resets.
  5. Plot one bar series per plug per day.

Why compute from lifetime totals instead of integrating power? Because power is sampled once a minute, and a kettle that runs for 90 seconds between two samples may appear as zero. The plug's own energy counter measures continuously, so deltas of the total are accurate even with sparse sampling. If a message is lost, the next delta simply covers a longer period.

The Python Logger

pip install "paho-mqtt>=2.0" matplotlib
import json
import sqlite3
import sys
from datetime import datetime
import paho.mqtt.client as mqtt

PREFIX = "connectmq/demo/p4w8"
DB = sqlite3.connect("plugs.db", check_same_thread=False)
DB.execute("""CREATE TABLE IF NOT EXISTS samples (
    ts TEXT, plug TEXT, power_w REAL, total_kwh REAL)""")
DB.commit()


def normalize(topic, payload):
    parts = topic.split("/")
    data = json.loads(payload)
    if "/tele/" in topic and topic.endswith("/SENSOR"):
        e = data.get("ENERGY")
        if not e:
            return None
        return parts[-2], float(e["Power"]), float(e["Total"])
    if "/shelly/" in topic and topic.endswith("/status/switch:0"):
        if "aenergy" not in data:
            return None
        plug = parts[parts.index("shelly") + 1]
        return plug, float(data.get("apower", 0)), float(data["aenergy"]["total"]) / 1000.0
    return None


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    client.subscribe(f"{PREFIX}/tele/+/SENSOR", qos=1)
    client.subscribe(f"{PREFIX}/shelly/+/status/switch:0", qos=1)


def on_message(client, userdata, msg):
    try:
        row = normalize(msg.topic, msg.payload)
    except (ValueError, KeyError) as exc:
        print("Skipping", msg.topic, exc)
        return
    if row is None:
        return
    plug, power, total = row
    DB.execute("INSERT INTO samples VALUES (?, ?, ?, ?)",
               (datetime.now().isoformat(timespec="seconds"), plug, power, total))
    DB.commit()
    print(f"{plug:12s} {power:8.1f} W  {total:10.3f} kWh")


def daily_kwh():
    rows = DB.execute("SELECT ts, plug, total_kwh FROM samples ORDER BY plug, ts").fetchall()
    result, last = {}, {}
    for ts, plug, total in rows:
        day = ts[:10]
        prev = last.get(plug)
        last[plug] = total
        if prev is None:
            continue
        delta = total - prev
        if delta < 0:          # counter reset or plug replaced: skip this interval
            continue
        result.setdefault(plug, {}).setdefault(day, 0.0)
        result[plug][day] += delta
    return result


def plot():
    import matplotlib.pyplot as plt
    data = daily_kwh()
    days = sorted({d for per in data.values() for d in per})
    width = 0.8 / max(len(data), 1)
    fig, ax = plt.subplots(figsize=(10, 5))
    for i, (plug, per_day) in enumerate(sorted(data.items())):
        xs = [n + i * width for n in range(len(days))]
        ax.bar(xs, [per_day.get(d, 0) for d in days], width, label=plug)
    ax.set_xticks([n + 0.4 - width / 2 for n in range(len(days))])
    ax.set_xticklabels(days, rotation=45, ha="right")
    ax.set_ylabel("kWh per day")
    ax.legend()
    fig.tight_layout()
    fig.savefig("daily_kwh.png", dpi=120)
    for plug, per_day in sorted(data.items()):
        print(plug, {d: round(v, 3) for d, v in sorted(per_day.items())})


if __name__ == "__main__":
    if sys.argv[1:] == ["plot"]:
        plot()
    else:
        client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="plug-logger-3391")
        client.on_connect = on_connect
        client.on_message = on_message
        client.connect("broker.connectmq.com", 1883, keepalive=60)
        client.loop_forever()

Leave python plug_logger.py running, and after a day or two run python plug_logger.py plot to produce daily_kwh.png and print the numbers. Days are bucketed by the logger's local time, which is usually what you want for household reporting.

Making Sense of the Numbers

Once you have a week of data, a few patterns usually stand out:

  • Standby loads. Televisions, consoles and network gear that never drop below a few watts add up over a year. Look at the minimum power_w per plug, not just the daily total.
  • Fridges and freezers show a regular duty cycle. A rising daily total at the same room temperature can indicate a failing seal or dirty condenser.
  • Short high loads such as kettles and coffee machines barely register in power samples, but the energy counter captures them, which validates the delta method.
  • Annual cost is daily kWh multiplied by 365 and your tariff. Calculate it per plug so you know which change is worth making.

For a whole-house view to compare against, see the home energy monitor with MQTT tutorial.

Turning Smart Plug MQTT Data into Alerts

History tells you what happened; alerts tell you while it is still happening. Because every reading already flows through the broker, you can add alerting without touching the plugs:

  • Freezer stopped. A freezer plug reporting under a few watts for more than an hour usually means a tripped thermostat or a door left open. The ConnectMQ portal's threshold alert rules can watch a numeric field on a topic, and a Python or Node-RED subscriber can add the "for more than an hour" condition.
  • Forgotten appliance. An iron, heater or hair straightener drawing power for longer than usual is a classic smart plug MQTT automation: publish off to the plug's command topic after a timeout.
  • Washing machine finished. Power dropping from hundreds of watts to near zero and staying there for a few minutes is a reliable "cycle done" signal for a phone notification.

Keep the logic in one subscriber rather than spreading it across devices, so you can change thresholds without reflashing anything.

Troubleshooting Checklist

Problem Cause Fix
Daily totals are 1000 times too large Shelly Wh treated as kWh Divide aenergy.total by 1000
No Tasmota data for minutes TelePeriod too long Set TelePeriod 60 or lower (minimum 10)
Plugs disconnect each other Duplicate MQTT client IDs Unique MqttClient per plug
Negative daily values Counter reset not handled Skip negative deltas as above
Power readings look wrong Uncalibrated plug Calibrate with PowerSet, VoltageSet, CurrentSet in Tasmota

Moving to a Private Broker

The public broker is perfect for this experiment, but energy data reveals when you are home, and anyone who guesses your prefix can also switch your plugs. Before leaving the setup running, move to a cloud MQTT broker with credentials and TLS. With ConnectMQ, create a broker in the portal, then give each plug its own username, password and client ID. In Tasmota that means a TLS-enabled build and MqttPort 8883; in Shelly, enable SSL with the built-in CA bundle. In the logger, add:

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set()
client.connect("broker.connectmq.com", 8883, keepalive=60)

The free Serverless plan's 25 connections are enough for a dozen plugs, the logger and a dashboard. A fully managed MQTT service also means the broker is reachable from your phone without opening ports on your router. The IoT automation guide shows how plug data can trigger automations as well.

Start Monitoring Your Plugs with ConnectMQ

ConnectMQ gives your smart plugs, loggers and dashboards a private, TLS-secured broker with a live topic explorer and threshold alert rules, for example to warn you when a freezer plug reports zero watts for too long. Create a free Serverless broker at app.connectmq.com, no credit card required.

Frequently Asked Questions

Can any smart plug send MQTT data?

No. Only plugs with MQTT in their firmware, such as Tasmota, Shelly Gen2/Gen3 or ESPHome devices, can publish to a broker you choose. Cloud-only plugs need a vendor integration instead.

How accurate are smart plug energy readings?

They are usually good enough for comparing appliances and spotting waste, but they are not billing-grade meters. Calibrating against a known resistive load improves accuracy noticeably on Tasmota plugs.

Why use the energy total instead of the power value?

The plug integrates energy continuously, while power is only sampled when a message is sent. Differences between lifetime totals capture short loads that power samples miss.

How often should a smart plug publish?

Every 30 to 60 seconds is a good balance for history and dashboards. Shorter intervals add traffic without improving daily totals, because those come from the energy counter.

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.