When makers search for things you can do with a raspberry pi, they are usually staring at a bare green board and wondering how to bridge the gap between a Linux computer and the physical world. While media centers and ad-blockers are fine, the true power of the Pi lies in its GPIO header. In this guide, we are bypassing the toy projects and building a production-grade IoT node: an MQTT Environment and Power Monitor. This node reads ambient temperature, humidity, and barometric pressure, while simultaneously monitoring the voltage and current draw of a 12V solar battery bank, publishing all telemetry to a local broker.

The Decision Path: Which Raspberry Pi Project Should You Build?

Not every project requires a full single-board computer. Before we wire up the GPIO, use this decision matrix to confirm that a Raspberry Pi is the right tool for your specific use case, rather than a simpler microcontroller.

If your goal is... And you need... Then choose...
Simple 5V logic switching or basic sensor polling Low power, instant boot, bare-metal C/MicroPython ESP32 or Arduino Nano
Network-wide DNS sinkhole or Pi-hole Headless Linux, Docker, minimal GPIO usage Raspberry Pi Zero 2 W
Local media playback or retro emulation HDMI out, USB controllers, heavy GPU rendering Raspberry Pi 5 (8GB)
Bridging I2C/SPI sensors to cloud MQTT with local logging Full Linux networking, Python ecosystem, robust I2C Raspberry Pi 5 (4GB) [OUR PICK]
Default Recommendation: If you want to process sensor data locally using Python, run a local MQTT broker, and push to Home Assistant or Node-RED, the Raspberry Pi 5 4GB is the definitive pick for 2026. It offers the PCIe lane for future NVMe storage logging and enough RAM to run Docker containers alongside your sensor scripts.

Hardware Spec Sheet & Pin Mapping

This build targets the Raspberry Pi 5 (4GB variant) running Raspberry Pi OS Lite (64-bit, Bookworm). The Pi 5 operates its I2C bus at 3.3V logic, which perfectly matches our chosen sensor breakouts without needing a logic level converter.

Parts List

  • Compute: Raspberry Pi 5 (4GB) with official 27W USB-C PD power supply.
  • Environment Sensor: Adafruit BME280 I2C Breakout (Product ID: 2652). Measures temp, humidity, pressure.
  • Power Monitor: Adafruit INA219 I2C High-Side DC Current Sensor Breakout (Product ID: 904). Measures bus voltage and shunt current up to 26V / 3.2A.
  • Wiring: 4-pin JST-SH STEMMA QT cables or standard 28 AWG Dupont jumper wires.
  • Storage: 32GB SanDisk Extreme microSD (A1 rated minimum for OS stability).

Pin Mapping Table (I2C Bus 1)

Both sensors share the same I2C bus. The Pi's internal pull-ups are sufficient for short runs (< 30cm), but if you extend the wires, add external 4.7kΩ pull-up resistors to the 3.3V line.

Pi 5 GPIO Pin Physical Pin # Function BME280 Pad INA219 Pad
GPIO 2 3 I2C1 SDA SDI/SDA SDA
GPIO 3 5 I2C1 SCL SCK/SCL SCL
3V3 Power 1 VCC (3.3V) 3Vo VCC
Ground 6 GND GND GND

Note: The INA219 also has Vin+ and Vin- screw terminals. Connect these in series with the positive lead of the 12V battery bank you wish to monitor.

Step-by-Step Build & Software Configuration

Follow these numbered steps to prepare the OS and install the required Python libraries. We use adafruit-blinka to provide CircuitPython hardware API compatibility on standard Raspberry Pi OS.

  1. Flash the OS: Use Raspberry Pi Imager to flash Raspberry Pi OS Lite (64-bit) to your microSD card. Enable SSH and configure your Wi-Fi in the advanced settings (gear icon).
  2. Enable I2C: SSH into the Pi and run sudo raspi-config. Navigate to Interface Options > I2C and enable it. Reboot the Pi.
  3. Verify Hardware: After reboot, install I2C tools and scan the bus:
    sudo apt update && sudo apt install i2c-tools -y
    i2cdetect -y 1
    You should see 40 (INA219) and 77 (BME280 default) in the grid output. If you don't, check your wiring before proceeding.
  4. Set up Python Environment: Create a virtual environment to keep dependencies clean.
    sudo apt install python3-venv python3-pip -y
    mkdir ~/env_monitor && cd ~/env_monitor
    python3 -m venv venv
    source venv/bin/activate
  5. Install Libraries: Install the Blinka compatibility layer, sensor drivers, and the Eclipse Paho MQTT client (Eclipse Mosquitto is the industry standard broker).
    pip3 install adafruit-blinka adafruit-circuitpython-bme280 adafruit-circuitpython-ina219 paho-mqtt
Safety Callout: When wiring the INA219 to a 12V or 24V battery bank, ensure the system is de-energized. A short across the Vin+ and Vin- screw terminals while under load can arc and damage the shunt resistor. Always use appropriately sized ring terminals and torque them securely.

Complete Python MQTT Monitor Code

This script initializes the I2C bus, configures the sensors, and publishes a JSON payload to an MQTT broker every 10 seconds. It includes robust error handling to prevent I2C bus lockups from crashing the daemon.

import time
import json
import board
import busio
import adafruit_bme280
from adafruit_ina219 import INA219, Range, Gain
import paho.mqtt.client as mqtt

# --- PIN & ADDRESS DEFINITIONS ---
I2C_SDA = board.SDA
I2C_SCL = board.SCL
BME_ADDR = 0x77  # Default for Adafruit BME280
INA_ADDR = 0x40  # Default for INA219

# --- MQTT CONFIGURATION ---
MQTT_BROKER = "192.168.1.100"
MQTT_PORT = 1883
MQTT_TOPIC = "pi5/sensors/env_power"

# Initialize I2C Bus
i2c = busio.I2C(I2C_SCL, I2C_SDA)

# Initialize Sensors
try:
    bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=BME_ADDR)
    bme280.sea_level_pressure = 1013.25
    print("BME280 initialized successfully.")
except ValueError as e:
    print(f"Failed to find BME280 at 0x{BME_ADDR:02X}: {e}")
    exit(1)

try:
    ina219 = INA219(i2c, address=INA_ADDR)
    ina219.configure(Range.VOLT_16, Gain.AMP_400)
    print("INA219 initialized successfully.")
except ValueError as e:
    print(f"Failed to find INA219 at 0x{INA_ADDR:02X}: {e}")
    exit(1)

# MQTT Callbacks
def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code == 0:
        print(f"Connected to MQTT Broker at {MQTT_BROKER}")
    else:
        print(f"MQTT Connection failed with code: {reason_code}")

def on_disconnect(client, userdata, flags, reason_code, properties):
    print(f"MQTT Disconnected (Code: {reason_code}). Attempting auto-reconnect...")

# Setup MQTT Client (Paho v2.0 API)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="Pi5_EnvNode")
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.connect(MQTT_BROKER, MQTT_PORT, 60)
client.loop_start()

print("Starting telemetry loop...")

try:
    while True:
        payload = {}
        
        # Read BME280 with error handling for I2C NACKs
        try:
            payload["temperature_c"] = round(bme280.temperature, 2)
            payload["humidity"] = round(bme280.relative_humidity, 2)
            payload["pressure_hpa"] = round(bme280.pressure, 2)
        except OSError as e:
            print(f"BME280 Read Error: {e}")
            payload["bme280_error"] = str(e)

        # Read INA219 with error handling
        try:
            payload["bus_voltage_v"] = round(ina219.bus_voltage, 2)
            payload["current_ma"] = round(ina219.current, 2)
            payload["power_mw"] = round(ina219.power, 2)
        except OSError as e:
            print(f"INA219 Read Error: {e}")
            payload["ina219_error"] = str(e)

        # Publish JSON payload
        json_payload = json.dumps(payload)
        client.publish(MQTT_TOPIC, json_payload)
        
        time.sleep(10)

except KeyboardInterrupt:
    print("\nShutting down gracefully...")
    client.loop_stop()
    client.disconnect()

Debugging: First Three Things to Check When It Fails

When moving from a breadboard prototype to a deployed node, I2C and network errors are inevitable. Here are the first three things to check when the script fails, ranked by occurrence frequency.

1. The I2C Bus Lockup

Exact Error String: OSError: [Errno 121] Remote I/O error

Ranked Causes:

  1. Loose Dupont Connections: Vibration causes momentary SDA/SCL disconnects. The Pi's I2C hardware controller does not automatically recover from a mid-byte clock stretch failure. Fix: Solder header pins or use JST connectors.
  2. Missing Pull-up Resistors: The Pi 5 internal pull-ups are around 50kΩ, which is too weak for long wire runs, causing slow rise times that the INA219 misinterprets. Fix: Add 4.7kΩ external pull-ups to 3.3V.
  3. Address Collision: You accidentally wired a second device to 0x77. Fix: Run i2cdetect -y 1 to verify unique addresses.

2. The Missing Interface

Exact Error String: FileNotFoundError: [Errno 2] No such file or directory: '/dev/i2c-1'

Ranked Causes:

  1. I2C Disabled in OS: You forgot to enable it in raspi-config or the dtparam=i2c_arm=on line is missing from /boot/firmware/config.txt.
  2. Wrong Bus Number: The code is hardcoded to bus 1, but you are using a compute module or a Pi variant where the primary header I2C is mapped to bus 3 or 4. Fix: Check ls /dev/i2c* and update the code.

3. The MQTT Broker Rejection

Exact Error String: ConnectionRefusedError: [Errno 111] Connection refused or reason_code == 5 (Connection Refused: not authorised)

Ranked Causes:

  1. Mosquitto Listener Config: Modern Mosquitto (v2.0+) defaults to local-loopback only. You must explicitly define listener 1883 and allow_anonymous true in mosquitto.conf to accept external Pi connections.
  2. Firewall Rules: UFW on the broker host is blocking port 1883. Fix: sudo ufw allow 1883/tcp.
  3. Wrong IP: The DHCP lease for your broker expired and changed. Fix: Assign a static IP reservation in your router for the MQTT server.

Extending and Simplifying the Build

A good embedded design scales to your actual requirements. Here is how to adjust this build based on your deployment constraints.

How to Simplify (The Minimalist Node)

If you only care about server room ambient conditions and don't need power monitoring:

  • Hardware: Drop the INA219 entirely. Switch to a Raspberry Pi Zero 2 W to cut BOM costs by roughly 70% and reduce idle power draw from ~2.5W to ~1.2W.
  • Software: Replace the MQTT library with the requests library. Have the Pi Zero push a simple HTTP POST to a webhook (like a Home Assistant REST API endpoint) every 60 seconds. This eliminates the need to maintain a local MQTT broker entirely.

How to Extend (The Off-Grid Solar Sentinel)

If you are deploying this in a remote shed to monitor a solar setup:

  • Hardware: Add a Pi Camera Module 3. Wire a 12V-to-5V buck converter (like the Pololu D24V50F5) to power the Pi directly from the battery bank you are monitoring.
  • Software: Implement a watchdog trigger in the Python script. If the INA219 reports bus voltage dropping below 11.8V (indicating a deeply discharged lead-acid battery), trigger the Pi Camera to snap a photo of the physical charge controller LCD screen and publish it via MQTT base64 encoding. This gives you visual remote debugging when the telemetry data doesn't match the expected charge profile.
  • Storage: Use the Pi 5's PCIe connector to add an NVMe SSD via the official M.2 HAT+. Log raw CSV data locally using Python's sqlite3 library so you don't lose telemetry when the Wi-Fi link drops during storms.

By treating the Raspberry Pi as a robust edge-compute node rather than just a desktop replacement, you unlock industrial-grade telemetry capabilities right from your workbench. Stick to the I2C pinouts, handle your bus errors gracefully, and your MQTT environment monitor will run for years without intervention.