When makers ask, "what can I do with a Raspberry Pi?", the standard answers are usually a retro game console, a Pi-hole DNS sinkhole, or a basic media center. But if you want to leverage the Pi as a true embedded Linux controller for industrial or home-automation telemetry, you need to look at its GPIO and I2C capabilities. In this guide, we answer that question by building a robust, MQTT-publishing environmental and power monitor node using the Raspberry Pi 5.

This project reads temperature, humidity, and barometric pressure from a BME280 sensor, alongside real-time voltage and current draw from an INA219 sensor, and publishes the payload to an MQTT broker. It is a foundational build for server-room monitoring, solar shed telemetry, or smart-greenhouse control.

Raspberry Pi Hardware Selection for Embedded IoT

Before wiring anything, you need to pick the right board. The Raspberry Pi 5 is the current 2026 standard for desktop-replacement and high-throughput tasks, but for a simple I2C sensor node, it might be overkill compared to the Zero 2 W. Here is a data-dense comparison to help you choose the right variant for your embedded node.

Board Variant I2C Default Clock Max GPIO Current (Total) Idle Power Draw 2026 Street Price (Approx) Best Use Case
Raspberry Pi 5 (4GB) 100kHz (supports 1MHz+) 50mA (across all pins) 2.8W $60 Edge computing, local ML, high-speed logging
Raspberry Pi 4 Model B (2GB) 100kHz (up to 400kHz) 50mA 3.0W $55 Standard Home Assistant nodes, Docker hosts
Raspberry Pi Zero 2 W 100kHz 50mA 1.2W $15 Battery-backed remote sensors, space-constrained nodes
Raspberry Pi 3B+ 100kHz 50mA 2.5W $45 (Refurb) Legacy replacements, basic serial/I2C gateways

Note: The 50mA total GPIO current limit is a hard silicon constraint on the Pi's 3.3V regulator. If your sensor breakboards draw more than this combined, you must power them from the 5V rail and use a logic level shifter for the I2C data lines. For this build, we are targeting the Raspberry Pi 5 (4GB) running Raspberry Pi OS (Bookworm) due to its improved PCIe lanes and faster I2C bus capacitance handling.

Parts List and Pin Mapping

To replicate this exact build, source the following specific modules. Generic clone sensors often lack the necessary pull-up resistors on the I2C lines, which causes bus failures on the Pi 5.

  • Microcontroller: Raspberry Pi 5 (4GB or 8GB variant)
  • Environment Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID: 2652)
  • Power Sensor: Adafruit INA219 High Side DC Current Sensor (Product ID: 904)
  • Power Supply: Official Raspberry Pi 27W USB-C PD Power Supply
  • Wiring: 24 AWG solid core jumper wires, half-size breadboard

Pin Mapping Table

The Raspberry Pi 5 maintains the standard 40-pin header layout. We are using the primary I2C bus (I2C1). Both the BME280 and INA219 support I2C address changes, but we will use their default addresses to keep the wiring simple.

Pi 5 Pin # GPIO / Function BME280 Pin INA219 Pin
1 3V3 Power VIN VCC
6 GND GND GND
3 GPIO 2 (SDA1) SDI/SDA SDA
5 GPIO 3 (SCL1) SCK/SCL SCL
Callout Tip: The INA219 measures load voltage and current. To do this, you must wire your target load (e.g., a 12V fan or a 5V LED strip) through the INA219's VIN+ and VIN- screw terminals. The I2C pins only carry data; they do not power the load.

Wiring and Assembly Steps

  1. De-energize the Pi: Unplug the USB-C power supply before touching the GPIO header. The Pi 5 has no reverse-polarity protection on the 5V pins.
  2. Wire the Power Rails: Connect Pi Pin 1 (3.3V) to the breadboard's red rail, and Pi Pin 6 (GND) to the blue rail.
  3. Connect I2C Data Lines: Run a wire from Pi Pin 3 (SDA) to the SDA pins of both sensors. Run a wire from Pi Pin 5 (SCL) to the SCL pins of both sensors.
  4. Wire the Sensors: Plug the BME280 and INA219 into the breadboard and connect their VCC and GND pins to the 3.3V and GND rails, respectively.
  5. Enable I2C in OS: Boot the Pi, open a terminal, and run sudo raspi-config. Navigate to Interface Options -> I2C and enable it. Reboot the Pi.
  6. Verify Hardware: Run i2cdetect -y 1. You should see 40 (INA219) and 77 (BME280) in the grid output.

Complete Python Telemetry Code

This code targets the Raspberry Pi 5 running Python 3.11+ on Bookworm. It uses the Adafruit Blinka/CircuitPython ecosystem for hardware abstraction and Eclipse Paho for MQTT. Install the dependencies first:

pip3 install adafruit-circuitpython-bme280 adafruit-circuitpython-ina219 paho-mqtt
import time
import json
import board
import busio
import adafruit_bme280
import adafruit_ina219
import paho.mqtt.client as mqtt

# --- Pin and Configuration Definitions ---
I2C_SDA = board.SDA
I2C_SCL = board.SCL
MQTT_BROKER = '192.168.1.50'
MQTT_PORT = 1883
MQTT_TOPIC = 'telemetry/pi5_node_01'
POLL_INTERVAL_SEC = 10

# --- Hardware Initialization ---
try:
    i2c = busio.I2C(I2C_SCL, I2C_SDA)
    bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77)
    ina219 = adafruit_ina219.INA219(i2c, addr=0x40)
    ina219.bus_adc_resolution = adafruit_ina219.ADCResolution.ADCRES_12BIT_32S
    ina219.shunt_adc_resolution = adafruit_ina219.ADCResolution.ADCRES_12BIT_32S
    print('Sensors initialized successfully.')
except ValueError as e:
    print(f'Hardware Init Failed: Check I2C addresses. Error: {e}')
    exit(1)
except Exception as e:
    print(f'I2C Bus Error: Ensure I2C is enabled in raspi-config. Error: {e}')
    exit(1)

# --- MQTT Callbacks ---
def on_connect(client, userdata, flags, rc, properties=None):
    if rc == 0:
        print('Connected to MQTT Broker')
    else:
        print(f'MQTT Connection failed with code {rc}')

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id='Pi5_Telemetry')
client.on_connect = on_connect

try:
    client.connect(MQTT_BROKER, MQTT_PORT, 60)
    client.loop_start()
except ConnectionRefusedError:
    print(f'MQTT Error: Connection refused at {MQTT_BROKER}:{MQTT_PORT}. Is Mosquitto running?')
    exit(1)

# --- Main Telemetry Loop ---
try:
    while True:
        try:
            # Read Environmental Data
            temp_c = round(bme280.temperature, 2)
            humidity = round(bme280.relative_humidity, 2)
            pressure = round(bme280.pressure, 2)
            
            # Read Power Data
            bus_voltage = round(ina219.bus_voltage, 3)
            current_ma = round(ina219.current, 2)
            
            payload = {
                'temp_c': temp_c,
                'humidity_pct': humidity,
                'pressure_hpa': pressure,
                'load_voltage_v': bus_voltage,
                'load_current_ma': current_ma,
                'timestamp': time.time()
            }
            
            client.publish(MQTT_TOPIC, json.dumps(payload))
            print(f'Published: {payload}')
            
        except OSError as e:
            print(f'I2C Read Error: {e}. Retrying next cycle...')
        
        time.sleep(POLL_INTERVAL_SEC)

except KeyboardInterrupt:
    print('Telemetry stopped by user.')
    client.loop_stop()
    client.disconnect()

Debugging: When the I2C Bus or MQTT Broker Fails

Embedded Linux is notorious for throwing cryptic errors when hardware meets software. If your script crashes, here are the first three things to check:

  1. Run i2cdetect -y 1: If the grid is empty, your I2C interface is disabled in the OS, or your SDA/SCL wires are swapped.
  2. Verify Power Rails: Use a multimeter to check the breadboard rails. You must read 3.2V-3.4V on the red rail. If you read 5V, you wired Pin 2 instead of Pin 1, and you are feeding 5V into the Pi's 3.3V logic pins (which can destroy the Pi 5's SoC).
  3. Check Firewall Rules: If the script hangs on client.connect(), ensure port 1883 is open on your MQTT broker host (sudo ufw allow 1883/tcp).

Ranked Causes for I2C Failures

If your code throws the exact error string: OSError: [Errno 121] Remote I/O error, the Linux kernel is failing to receive an ACKnowledge (ACK) bit from the sensor. Here is the ranked cause list:

  1. Missing Pull-up Resistors (Most Likely): The Pi 5 has internal 1.8kΩ pull-ups, but if your I2C wires are longer than 12 inches or you have more than two devices on the bus, bus capacitance rises and the signal edges degrade. Fix: Solder 4.7kΩ pull-up resistors between SDA/SCL and 3.3V on the breadboard.
  2. I2C Address Collision: Both sensors might be defaulting to the same address, or the BME280 SDO pin is floating. Fix: Tie the BME280 SDO pin to GND to force address 0x76, or to VCC for 0x77.
  3. Loose Jumper Wires: Breadboard contacts wear out. Fix: Swap the jumper wires or move to a different breadboard row.

If you encounter ConnectionRefusedError: [Errno 111] Connection refused on the MQTT side, the broker is actively rejecting the TCP handshake. This is almost always caused by Mosquitto 2.0+ defaulting to local-only listeners. Fix: Add listener 1883 and allow_anonymous true to your mosquitto.conf file and restart the service.

Extending or Simplifying the Build

One of the best answers to "what can I do with a Raspberry Pi" is realizing how modular the ecosystem is. You can easily scale this project up or down based on your deployment environment.

How to Simplify

If you don't need power monitoring and just want a basic weather station, drop the INA219 entirely. Remove the adafruit_ina219 imports and sensor reads from the code. To eliminate the need for a local MQTT broker and network dependency, change the payload output to write directly to a local CSV file using Python's csv module, or push it to a free cloud service like Adafruit IO via their REST API.

How to Extend

To turn this into an off-grid remote telemetry node, add a Dragino LoRa/GPS HAT. The Pi 5's 40-pin header supports SPI communication, allowing you to transmit the sensor payloads over LoRaWAN to a gateway miles away without relying on WiFi. Alternatively, leverage the Pi 5's new PCIe 2.0 interface by attaching an NVMe SSD via the M.2 HAT+. You can then configure the Python script to log high-frequency vibration data from an ADXL345 accelerometer at 100Hz directly to the SSD for predictive maintenance analysis, a task the Pi Zero 2 W would completely choke on.

For deeper reading on I2C bus capacitance limits and clock stretching, refer to the official Raspberry Pi GPIO and I2C configuration documentation. For MQTT payload structuring, the Eclipse Paho Python Client docs provide excellent examples on handling QoS levels and retained messages.