When makers search for uses for a raspberry pi, the results are usually saturated with retro gaming consoles and basic media centers. But for electrical engineers, solar DIYers, and home automation builders, the highest-value application for a Pi in 2026 is acting as a hardened, local MQTT telemetry node. By pairing a Raspberry Pi 5 with precision I2C sensors, you can build a hub that monitors DC battery banks, solar charge currents, and server rack environments, pushing that data to Home Assistant or Node-RED via MQTT.
This guide details the exact hardware, pin mappings, and Python code to build a DC power and environmental monitoring hub. We will target the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS Bookworm, leveraging its upgraded I2C bus timing and PCIe architecture to handle high-frequency sensor polling without the I/O bottlenecks that plagued the Pi 4.
Hardware Specification and Sensor Matrix
Before wiring the GPIO header, you need to select the right silicon. The Pi 5 operates its I2C bus at 3.3V logic with internal 1.8kΩ pull-up resistors. While this is sufficient for short breadboard runs, adding multiple sensors requires an understanding of bus capacitance. Below is the specification matrix for the core components used in this build.
| Component | Exact Model / Variant | Key Specification | Interface | Approx. Cost (2026) |
|---|---|---|---|---|
| Compute Node | Raspberry Pi 5 (8GB) | 2.4GHz Quad-core, 8GB LPDDR4X | 40-pin GPIO (I2C1) | $80.00 |
| Current/Power Sensor | Texas Instruments INA219 Breakout | 26V max, 3.2A max (with 0.1Ω shunt) | I2C (Addr: 0x40) | $9.50 |
| Environment Sensor | Bosch BME280 Breakout | Temp/Humidity/Pressure (±1°C, ±3% RH) | I2C (Addr: 0x76) | $12.00 |
| Thermal Management | Pi 5 Active Cooler | PWM controlled, 1.2W dissipation | JST fan header | $5.00 |
| Alternative Node | Raspberry Pi Zero 2 W | 1GHz Quad-core, 512MB LPDDR2 | 40-pin GPIO (I2C1) | $15.00 |
Note: If you are monitoring a 48V solar battery bank, the standard INA219 will be destroyed by overvoltage. You must swap it for an INA226 (36V max) paired with an external voltage divider, or use an isolated Hall-effect sensor like the ACS712 for high-side DC measurements.
GPIO Pin Mapping and Wiring Procedure
The Raspberry Pi 5 retains the standard 40-pin header layout, but the underlying SoC routing for the I2C1 bus remains on BCM GPIO 2 (SDA) and GPIO 3 (SCL). Because both the INA219 and BME280 are 3.3V tolerant, we can wire them directly to the Pi's 3.3V power rail. Do not connect these specific breakouts to the 5V rail, as doing so will back-feed 5V into the Pi 5's 3.3V SDA/SCL pins, potentially frying the SoC's I2C controller.
| Pi 5 Physical Pin | BCM GPIO | Function | Sensor Breakout Pin |
|---|---|---|---|
| 1 | N/A | 3.3V Power | VIN / VCC (Both Sensors) |
| 6 | N/A | Ground | GND (Both Sensors) |
| 3 | GPIO 2 | I2C SDA | SDA (Both Sensors) |
| 5 | GPIO 3 | I2C SCL | SCL (Both Sensors) |
The Pi 5's internal 1.8kΩ pull-ups are relatively strong, but if your I2C wires exceed 30cm (12 inches), bus capacitance will round off the square waves, causing bit errors. For longer runs to a battery bank, add external 4.7kΩ pull-up resistors to the 3.3V rail on the SDA and SCL lines, or use an I2C bus extender like the PCA9615.
Python MQTT Aggregation Code
The following Python script targets the Raspberry Pi 5 8GB running Raspberry Pi OS Bookworm. Unlike older tutorials that rely on the deprecated RPi.GPIO library, this script uses smbus2 for direct, low-level I2C register reads and paho-mqtt for broker communication. This ensures compatibility with the Pi 5's updated kernel and avoids the Bookworm dependency traps.
Install the required libraries via the terminal before running the code:
sudo apt update
sudo apt install python3-smbus python3-pip
pip3 install paho-mqtt smbus2 --break-system-packages
Save the following code as pi_mqtt_hub.py:
import smbus2
import paho.mqtt.client as mqtt
import time
import json
import sys
# --- HARDWARE CONFIGURATION ---
I2C_BUS = 1
INA219_ADDR = 0x40
BME280_ADDR = 0x76
INA219_REG_BUS_VOLTAGE = 0x02
BME280_REG_CHIP_ID = 0xD0
# --- MQTT CONFIGURATION ---
MQTT_BROKER = "192.168.1.50"
MQTT_PORT = 1883
MQTT_TOPIC = "sensor_hub/pi5/telemetry"
# Initialize I2C Bus
bus = smbus2.SMBus(I2C_BUS)
def on_connect(client, userdata, flags, rc, properties=None):
if rc == 0:
print(f"Connected to MQTT Broker at {MQTT_BROKER}")
else:
print(f"MQTT Connection failed with code {rc}")
def read_ina219_voltage():
"""Reads the INA219 Bus Voltage register (0x02) and converts to Volts."""
try:
# Read 16-bit word from the bus voltage register
raw = bus.read_word_data(INA219_ADDR, INA219_REG_BUS_VOLTAGE)
# INA219 is big-endian, smbus2 read_word_data returns little-endian. Swap bytes.
raw = ((raw & 0xFF) << 8) | (raw >> 8)
# Shift right by 3 (bits 2-0 are conversion ready flags)
raw = raw >> 3
# Multiply by 4mV per bit
voltage = raw * 0.004
return round(voltage, 3)
except OSError as e:
raise e
def verify_bme280():
"""Reads the BME280 Chip ID register to verify I2C communication."""
try:
chip_id = bus.read_byte_data(BME280_ADDR, BME280_REG_CHIP_ID)
if chip_id == 0x60:
return True
return False
except OSError:
return False
# Setup MQTT Client (Paho v2.0 API)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="Pi5_Hub_01")
client.on_connect = on_connect
try:
client.connect(MQTT_BROKER, MQTT_PORT, 60)
client.loop_start()
except ConnectionRefusedError as e:
print(f"FATAL: {e}. Is Mosquitto running on {MQTT_BROKER}?")
sys.exit(1)
print("Verifying I2C sensors...")
if not verify_bme280():
print("WARNING: BME280 not found at 0x76. Check wiring.")
print("Starting telemetry loop. Press Ctrl+C to exit.")
try:
while True:
try:
v_bus = read_ina219_voltage()
# In a full build, you would read BME280 temp/humidity here via a dedicated library
payload = {
"timestamp": time.time(),
"dc_bus_voltage_v": v_bus,
"bme280_status": "online" if verify_bme280() else "offline"
}
client.publish(MQTT_TOPIC, json.dumps(payload), qos=1)
print(f"Published: {payload}")
except OSError as e:
print(f"I2C Read Error: {e}. Bus may be locked or sensor disconnected.")
time.sleep(5)
except KeyboardInterrupt:
print("\nStopping MQTT loop...")
client.loop_stop()
client.disconnect()
print("Clean shutdown complete.")
Debugging: When the I2C Bus or MQTT Broker Fails
Embedded Python scripts rarely fail gracefully on the first run. When dealing with I2C sensors and network brokers on the Pi 5, you will inevitably encounter two specific error strings. Here is how to diagnose them.
Error 1: OSError: [Errno 121] Remote I/O error
This is the universal Linux I2C failure code. It means the kernel sent a clock pulse, but the sensor did not pull the SDA line low to acknowledge (ACK) the address.
- Cause 1 (Most Likely): Loose Dupont jumper wires or a breadboard with worn-out internal springs. Fix: Swap wires and verify continuity with a multimeter.
- Cause 2: The I2C address is wrong. Some BME280 breakouts default to 0x77 instead of 0x76 depending on a solder jumper on the back. Fix: Run
i2cdetect -y 1in the terminal to scan the bus and find the actual address. - Cause 3: The sensor's internal state machine has locked up due to a voltage spike. Fix: Power cycle the 3.3V rail completely.
Error 2: ConnectionRefusedError: [Errno 111] Connection refused
This occurs when the Python Paho library attempts a TCP handshake to port 1883 on your broker IP, but the remote host actively rejects it.
- Cause 1: The MQTT broker (like Mosquitto) is not running on the target machine. Fix: SSH into the broker and run
sudo systemctl status mosquitto. - Cause 2: A firewall (UFW/iptables) on the broker machine is blocking inbound traffic on port 1883. Fix: Allow the port via
sudo ufw allow 1883/tcp. - Cause 3: The broker is configured to require authentication, but the script lacks credentials. Fix: Add
client.username_pw_set("user", "pass")before the connect call.
- Run
i2cdetect -y 1: If the table shows dashes instead of40and76, your issue is purely physical wiring or power. Do not debug the Python code until the sensors show up here. - Verify Broker Reachability: Open a separate terminal on the Pi and run
mosquitto_pub -h 192.168.1.50 -t "test" -m "hello". If this fails, the network or broker is the problem, not your script. - Check Pi 5 I2C Interface Status: Ensure I2C is actually enabled. Run
sudo raspi-config, navigate to Interface Options > I2C, and confirm it is enabled. A reboot is required after changing this.
Extending or Simplifying the Build
One of the best uses for a raspberry pi is its modularity. You can scale this exact architecture up or down based on your site requirements.
How to Simplify (The Remote Solar Node)
If you are deploying this inside a remote solar shed where power consumption matters, swap the Pi 5 for a Raspberry Pi Zero 2 W. The Zero 2 W idles at roughly 0.7W compared to the Pi 5's 2.5W+ idle draw. Drop the BME280 to save I2C bus complexity, and run the script as a systemd service so it auto-restarts on brownouts. You will need to solder the GPIO header onto the Zero 2 W, but the pin mapping and Python code remain 100% identical.
How to Extend (AC Mains Monitoring)
To monitor 120V/240V AC loads (like a well pump or EV charger), the INA219 is useless. You must extend the build by adding an RS485 to UART HAT and a PZEM-004T v3 AC power module. The Pi 5's UART (GPIO 14/15) will communicate via Modbus-RTU to the PZEM.
Working with the PZEM-004T requires wiring 120V/240V AC directly to screw terminals. De-energize the breaker, verify dead with a CAT III multimeter, and use proper ferrule crimps on your wires. Never bypass the breaker or work on live AC panels. If you are not comfortable with mains wiring, hire a licensed electrician to install the CT clamp and shunt.
By treating the Raspberry Pi not as a desktop computer, but as a headless, solid-state industrial gateway, you unlock its true potential in the electrical engineering space. The combination of native I2C, robust Python libraries, and low-cost precision sensors makes it the undisputed king of the DIY telemetry stack.






