A Raspberry Pi power monitor tells you exactly how much a circuit draws, how full a battery is getting and how much energy a device uses over a day. In this tutorial you will measure DC voltage, current and power with an INA219, integrate them into watt-hours, and publish everything as JSON over MQTT so a dashboard or automation can use it. You will also see how CT clamp HATs extend the same idea to AC mains circuits.
Choosing a Sensor: INA219 or CT Clamp
| INA219 breakout | CT clamp HAT | |
|---|---|---|
| Measures | DC voltage (up to 26 V), current, power | AC current through a clamp; some boards also sense AC voltage |
| Connection | In series with the load, via a shunt resistor | Non-invasive, clamps around one conductor |
| Typical use | Solar panels, 12 V batteries, LED strips, robots, the Pi itself | Household circuits, appliances, machines |
| Interface | I2C | Usually SPI or I2C ADC on the HAT |
| Safety | Low voltage only | Clamp is isolated, but installation near mains needs a qualified person |
This tutorial builds the INA219 version end to end because it is cheap, accurate for low-voltage DC and safe to experiment with. If you need AC, a CT clamp HAT ships with its own Python library; read its values and reuse the MQTT part of the script unchanged. The ESP32 equivalent for mains is covered in the ESP32 SCT-013 current sensor guide.
How the INA219 Works
The INA219 measures the small voltage drop across a 0.1 ohm shunt resistor in series with your load, and the bus voltage on the load side. From those two values it calculates current and power internally. The standard breakout handles up to 26 V on the bus and, with the default calibration, around 3.2 A. Because the shunt sits in series, all load current flows through the breakout, so stay within its ratings and use wires thick enough for the current.
Wiring
| INA219 pin | Connect to |
|---|---|
| VCC | Pi 3.3 V (physical pin 1) |
| GND | Pi GND (physical pin 6) and the load supply ground |
| SDA | GPIO2 (physical pin 3) |
| SCL | GPIO3 (physical pin 5) |
| VIN+ | Positive terminal of the power source |
| VIN- | Positive input of the load |
The ground of the measured circuit must be shared with the Pi's ground, otherwise the bus voltage reading is meaningless. Enable I2C with sudo raspi-config nonint do_i2c 0 and confirm the sensor appears at address 0x40 with i2cdetect -y 1.
Installing the Software
sudo apt update
sudo apt install -y python3-venv i2c-tools
mkdir -p ~/power-monitor && cd ~/power-monitor
python3 -m venv .venv
source .venv/bin/activate
pip install "paho-mqtt>=2.0" adafruit-circuitpython-ina219
Topics and Payload
| Topic | Content | Retained |
|---|---|---|
connectmq/demo/8d20f6/solar/power |
Voltage, current, power every 5 s | No |
connectmq/demo/8d20f6/solar/energy |
Accumulated Wh, every minute | Yes |
connectmq/demo/8d20f6/solar/status |
online / offline |
Yes |
A power message looks like this:
{"voltage": 12.84, "current_ma": 412.6, "power_w": 5.3, "ts": 1792161000}
The energy topic is retained so a dashboard that opens later immediately sees today's total.
The Raspberry Pi Power Monitor Script
The script samples once per second, averages over the publish interval to smooth out noise, and integrates power over time into watt-hours. It saves the energy counter to disk every ten minutes so a restart does not reset it, without writing to the SD card on every sample. Save as power_monitor.py:
#!/usr/bin/env python3
"""INA219 power monitor publishing to MQTT."""
import json
import os
import time
import uuid
from pathlib import Path
import board
import paho.mqtt.client as mqtt
from adafruit_ina219 import INA219
BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"power-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("MQTT_PREFIX", "connectmq/demo/8d20f6/solar")
PUBLISH_EVERY = 5 # seconds
ENERGY_EVERY = 60 # seconds
SAVE_EVERY = 600 # seconds
STATE_FILE = Path(os.getenv("STATE_FILE", "energy_wh.txt"))
ina = INA219(board.I2C()) # default calibration: 32 V, 2 A range
def load_energy():
try:
return float(STATE_FILE.read_text())
except (OSError, ValueError):
return 0.0
def save_energy(wh):
tmp = STATE_FILE.with_suffix(".tmp")
tmp.write_text(f"{wh:.4f}")
tmp.replace(STATE_FILE) # atomic rename avoids half-written files
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Connection refused: {reason_code}")
return
print(f"Connected as {CLIENT_ID}")
client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
client.on_connect = on_connect
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
if USERNAME:
client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
client.tls_set()
client.connect(BROKER, PORT, keepalive=30)
client.loop_start()
energy_wh = load_energy()
samples = []
last_sample = time.monotonic()
last_publish = last_energy = last_save = last_sample
try:
while True:
time.sleep(1)
now = time.monotonic()
voltage = ina.bus_voltage + ina.shunt_voltage # supply-side voltage
current_ma = ina.current
power_w = ina.power
if ina.overflow:
print("Warning: INA219 overflow, current exceeds calibrated range")
energy_wh += power_w * (now - last_sample) / 3600.0
last_sample = now
samples.append((voltage, current_ma, power_w))
if now - last_publish >= PUBLISH_EVERY:
n = len(samples)
payload = {
"voltage": round(sum(s[0] for s in samples) / n, 2),
"current_ma": round(sum(s[1] for s in samples) / n, 1),
"power_w": round(sum(s[2] for s in samples) / n, 2),
"ts": int(time.time()),
}
client.publish(f"{PREFIX}/power", json.dumps(payload), qos=0)
samples.clear()
last_publish = now
if now - last_energy >= ENERGY_EVERY:
energy = {"energy_wh": round(energy_wh, 3), "ts": int(time.time())}
client.publish(f"{PREFIX}/energy", json.dumps(energy), qos=1, retain=True)
last_energy = now
if now - last_save >= SAVE_EVERY:
save_energy(energy_wh)
last_save = now
except KeyboardInterrupt:
pass
finally:
save_energy(energy_wh)
client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True).wait_for_publish(2)
client.loop_stop()
client.disconnect()
Run it with python power_monitor.py and subscribe from another machine:
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-8d20f6 \
-t 'connectmq/demo/8d20f6/solar/#' -v
Turn the load on and off and watch current_ma and power_w respond within five seconds.
Calibration and Accuracy
The default calibration covers 32 V and 2 A with reasonable resolution. If your load is small, such as a microcontroller drawing tens of milliamps, call ina.set_calibration_16V_400mA() after creating the sensor for finer resolution. If you need more than the breakout's range, replace the shunt with a lower value and write your own calibration, or switch to a sensor designed for higher currents.
Verify against a multimeter in series with the load. Small offsets are normal; a consistent percentage error usually points to the shunt tolerance, which you can correct with a scale factor in software. Negative current means VIN+ and VIN- are swapped, which can actually be useful: a battery monitor can use the sign to show charging versus discharging.
Use Cases
The same script fits many projects with only a change of topic prefix and calibration. Because subscribers only see JSON with voltage, current, power and energy fields, you can swap the sensor later, or add a second INA219 at address 0x41 for another circuit, without touching the dashboard or automation that consumes the data.
- Solar and battery systems: track panel output and battery voltage, and publish an alert when the battery drops below a safe level.
- Measuring your own Pi or ESP32 projects: put the INA219 between the supply and a device to see real-world consumption during Wi-Fi bursts and sleep.
- Robots and vehicles: monitor motor current to detect stalls.
- 12 V lighting and pumps: confirm that a load actually switched on, not just that a relay clicked.
For whole-house monitoring with CT clamps and long-term charts, our home energy monitor with MQTT article shows the subscriber side, and the Raspberry Pi Grafana and InfluxDB guide stores the readings for months. On low power budgets, the same script runs well on a Raspberry Pi Zero 2 W MQTT node.
From Bench to Permanent Installation
On the public test broker at broker.connectmq.com anyone can read your topics, which is fine for a bench test but reveals usage patterns in a real home or workshop. Move to a private broker with TLS before installing the monitor permanently. With ConnectMQ you get generated credentials per device; set MQTT_PORT=8883 plus MQTT_USERNAME=YOUR_USERNAME, MQTT_PASSWORD=YOUR_PASSWORD and MQTT_CLIENT_ID=YOUR_CLIENT_ID in a systemd environment file and restart. Then add a threshold alert rule in the ConnectMQ portal, for example voltage below 11.8 V on the power topic, so you get warned before a battery is damaged.
If you are new to what an MQTT broker does or how to run a script as a service, start with the Raspberry Pi MQTT tutorial and come back to this build.
Troubleshooting
ValueError: No I2C device at address: 0x40: check SDA and SCL, enable I2C and runi2cdetect -y 1.- Voltage reads near zero: VIN+ or VIN- is not connected to the measured circuit, or grounds are not shared.
- Current readings are noisy: average over the publish interval as the script does, and keep shunt wires short.
- Energy total resets after reboot: the state file is not writable or the script was killed before saving; check permissions on the working directory.
Get Started with ConnectMQ
Send your power data to a cloud MQTT broker that dashboards and alerts can reach from anywhere. Create your free ConnectMQ broker on the Serverless plan, no credit card required, and connect over TLS with MQTT 3.1.1 or 5.0. The live topic explorer shows every reading as it lands.
Frequently Asked Questions
Can an INA219 measure AC mains power?
No. The INA219 is a DC sensor for up to 26 V. Use a CT clamp HAT or a certified energy meter for AC circuits, and have mains work done by a qualified person.
How accurate is a Raspberry Pi power monitor with an INA219?
Accurate enough for most hobby and monitoring work once checked against a multimeter. Shunt tolerance is the main source of error and can be corrected with a scale factor.
Why publish energy as a retained message?
So any dashboard or subscriber that connects later immediately receives the current total instead of waiting up to a minute for the next update.
How often should I publish power readings?
Every few seconds is enough for dashboards. Sample faster locally, average, and publish the average to keep traffic low.



