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)
Callout Tip: I2C Bus Capacitance
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 1 in 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.
The First Three Things to Check When It Fails
  1. Run i2cdetect -y 1: If the table shows dashes instead of 40 and 76, your issue is purely physical wiring or power. Do not debug the Python code until the sensors show up here.
  2. 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.
  3. 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.

WARNING: Mains Voltage Hazard
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.