When evaluating projects for the raspberry pi 3, the temptation is often to push it into heavy desktop-replacement or media-center roles. But the Raspberry Pi 3 Model B+ actually shines brightest in 2026 as a headless, low-power IoT edge node. Its native Ethernet port (via USB 2.0 bridge) and full-size USB ports eliminate the dongle fatigue common with the Pi Zero series, while drawing significantly less idle current than a Pi 4 or Pi 5.
This guide walks through building a robust, headless MQTT Environmental and Power Monitor. We will read temperature, humidity, and barometric pressure from a BME280, alongside real-time DC voltage and current draw from an INA219, publishing the telemetry to an MQTT broker. This guide specifically targets the Raspberry Pi 3 Model B+ running Raspberry Pi OS Lite (64-bit, Bookworm).
Decision Tree: Is the Pi 3 B+ the Right Board for This Project?
Before wiring anything, let's confirm the hardware. The embedded ecosystem has shifted, and choosing the right board prevents mid-build pivots. Use this decision matrix to finalize your pick.
| Criteria | Raspberry Pi 3 Model B+ | Raspberry Pi Zero 2 W | Raspberry Pi 4 Model B |
|---|---|---|---|
| Idle Power Draw | ~2.5W | ~1.2W | ~3.5W+ |
| Native Ethernet | Yes (10/100/1000 over USB2) | No (Requires USB OTG adapter) | Yes (True Gigabit) |
| USB Ports | 4x Full-size USB 2.0 | 1x Micro USB OTG | 2x USB 3.0, 2x USB 2.0 |
| 2026 Used Market Cost | $25 - $35 USD | $15 - $20 USD (if in stock) | $45 - $60 USD |
Hardware Spec Sheet and Pin Mapping
Sensor selection matters. Generic clone sensors often lack proper I2C pull-up resistors or voltage regulation, leading to bus lockups. We are using specific Adafruit variants for their onboard 3.3V LDOs and 10kΩ pull-ups.
Parts List
- Compute: Raspberry Pi 3 Model B+ (1GB RAM)
- Environmental Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID: 2652)
- Power Monitor: Adafruit INA219 DC Current Sensor Breakout (Product ID: 904)
- Wiring: 22 AWG silicone stranded wire or standard female-to-female jumper dupont cables
- Power Supply: 5.1V / 2.5A Micro-USB official Raspberry Pi power supply
GPIO Pin Mapping Table
The Pi 3 exposes multiple I2C buses, but Bus 1 is the default hardware I2C bus with built-in 1.8kΩ pull-up resistors on the board itself. We will wire both sensors in parallel on this bus.
| Pi 3 GPIO (Physical Pin) | Function | BME280 Pin | INA219 Pin |
|---|---|---|---|
| Pin 1 (3V3) | Power (3.3V) | VIN | VCC |
| Pin 6 (GND) | Ground | GND | GND |
| Pin 3 (GPIO 2) | I2C1 SDA | SDI | SDA |
| Pin 5 (GPIO 3) | I2C1 SCL | SCK | SCL |
Note: The INA219 also has Vin- and Vin+ screw terminals. These are for the load you are measuring (e.g., a 12V LED strip or a 5V fan) and do NOT connect to the Pi's GPIO. Ensure the load ground is tied to the Pi ground for a common reference.
Step-by-Step Wiring and OS Configuration
- De-energize the Pi: Unplug the micro-USB power cable before touching the GPIO header.
- Wire the I2C Bus: Connect Pin 1 to both sensor VIN/VCC pins. Connect Pin 6 to both GND pins. Connect Pin 3 to both SDA pins. Connect Pin 5 to both SCL pins.
- Boot and SSH: Power on the Pi 3. Flash Raspberry Pi OS Lite (64-bit) beforehand and enable SSH via the Raspberry Pi Imager settings. SSH into the Pi.
- Enable I2C: Run
sudo raspi-config, navigate to Interface Options > I2C, and enable it. Reboot the Pi. - Verify Hardware Addresses: Run
i2cdetect -y 1. You should see40(INA219) and77(BME280) in the grid. If the BME280 shows as76, it's a generic clone; note this for the code block below. - Install Python Dependencies: Create a virtual environment and install the required libraries.
python3 -m venv ~/mqtt_env source ~/mqtt_env/bin/activate pip install paho-mqtt pimoroni-bme280 pi-ina219
The Python MQTT Publisher Code
This script reads both sensors, formats the data into a JSON payload, and publishes it to an MQTT broker. It includes robust error handling for I2C bus lockups and network drops, which are the most common failure points in headless Pi deployments.
import time
import json
import paho.mqtt.client as mqtt
from bme280 import BME280
from ina219 import INA219
from smbus2 import SMBus
# --- PIN & ADDRESS DEFINITIONS ---
I2C_BUS_ID = 1 # Pi 3 uses I2C bus 1 (GPIO 2 / GPIO 3)
BME280_ADDR = 0x77 # Default for Adafruit BME280 (Use 0x76 for generic clones)
INA219_ADDR = 0x40 # Default for INA219
SHUNT_OHMS = 0.1 # INA219 onboard shunt resistor value
MAX_AMPS = 3.2 # Expected max current for INA219 scaling
# --- MQTT CONFIGURATION ---
BROKER_IP = "192.168.1.50"
BROKER_PORT = 1883
MQTT_TOPIC = "sensors/pi3/node01/telemetry"
PUBLISH_INTERVAL = 10 # Seconds
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT Broker!")
else:
print(f"Failed to connect, return code {rc}")
# Initialize I2C Bus
bus = SMBus(I2C_BUS_ID)
# Initialize Sensors
try:
bme280 = BME280(i2c_dev=bus, i2c_addr=BME280_ADDR)
ina219 = INA219(SHUNT_OHMS, max_expected_amps=MAX_AMPS, address=INA219_ADDR, busnum=I2C_BUS_ID)
ina219.configure()
print("Sensors initialized successfully.")
except Exception as e:
print(f"Fatal: Sensor initialization failed. Check wiring. Error: {e}")
exit(1)
# Initialize MQTT Client
client = mqtt.Client(client_id="Pi3_Node01", protocol=mqtt.MQTTv311)
client.on_connect = on_connect
try:
client.connect(BROKER_IP, BROKER_PORT, 60)
client.loop_start()
except Exception as e:
print(f"Fatal: Could not connect to MQTT broker at {BROKER_IP}. Error: {e}")
exit(1)
# Main Loop
try:
while True:
try:
# Read BME280
temp_c = round(bme280.get_temperature(), 2)
humidity = round(bme280.get_humidity(), 2)
pressure = round(bme280.get_pressure(), 2)
# Read INA219
bus_voltage = round(ina219.voltage(), 3)
current_ma = round(ina219.current(), 2)
power_mw = round(ina219.power(), 2)
payload = {
"timestamp": int(time.time()),
"bme280": {"temp_c": temp_c, "humidity": humidity, "pressure_hpa": pressure},
"ina219": {"load_v": bus_voltage, "current_ma": current_ma, "power_mw": power_mw}
}
client.publish(MQTT_TOPIC, json.dumps(payload))
print(f"Published: {payload}")
except OSError as e:
print(f"I2C Read Error: {e}. Bus may be locked. Retrying in 5s...")
time.sleep(5)
continue
time.sleep(PUBLISH_INTERVAL)
except KeyboardInterrupt:
print("\nStopping script...")
client.loop_stop()
client.disconnect()
bus.close()
Debugging: First 3 Checks and Exact Error Strings
When deploying headless nodes, you don't have a monitor attached. You will rely on SSH logs. If the script crashes or hangs, run through these checks immediately.
The First 3 Things to Check When It Fails
- Run
i2cdetect -y 1: If the grid is entirely blank or showsUUon the expected addresses, your physical wiring is flawed, or a previous crashed Python process is holding the I2C bus hostage. Reboot the Pi to clear bus locks. - Verify MQTT Broker Port and Firewall: Run
nc -zv 192.168.1.50 1883from the Pi terminal. If it times out, your broker's host firewall (like UFW on Ubuntu) is blocking inbound TCP 1883. - Check for I2C Pull-Up Resistors: If you swapped the Adafruit boards for cheap Amazon/eBay clones, they often omit the 10kΩ pull-up resistors. The Pi 3's internal pull-ups are weak (1.8kΩ) and sometimes insufficient for long wires. Add external 4.7kΩ resistors between SDA/SCL and 3.3V.
Exact Error Strings and Ranked Causes
OSError: [Errno 121] Remote I/O error
This is the classic I2C communication failure. The Pi sent a clock signal, but the sensor did not acknowledge (NACK).
- Cause 1 (Most Likely): Loose Dupont jumper wire on the SDA or SCL line. Vibration from nearby machinery or fans backs these out over time. Solder headers or use JST connectors for permanent deployments.
- Cause 2: Wrong I2C address in the code. If your BME280 is a generic clone, change
BME280_ADDR = 0x77to0x76in the script. - Cause 3: Voltage sag. If the Pi 3 power supply is failing under load, the 3.3V rail drops below 3.1V, causing the sensors to brownout and drop off the bus.
ConnectionRefusedError: [Errno 111] Connection refused
The Pi reached the target IP address, but the operating system at that IP actively rejected the TCP connection on port 1883.
- Cause 1 (Most Likely): The MQTT broker service (e.g., Mosquitto) is not running on the target machine. SSH into the broker and run
sudo systemctl status mosquitto. - Cause 2: Mosquitto is configured to only accept localhost connections. Check your
mosquitto.confand ensurelistener 1883 0.0.0.0is defined, followed byallow_anonymous true(if not using TLS/Auth). - Cause 3: You hardcoded the wrong
BROKER_IPin the Python script.
How to Extend or Simplify the Build
Not every deployment requires full telemetry. Here is how to adapt this architecture based on your constraints.
Simplifying the Build
If you only need ambient environmental data and want to reduce the BOM cost and wiring complexity, drop the INA219 entirely. Remove the pi-ina219 import and the associated dictionary keys from the JSON payload. The BME280 alone draws less than 1mA during active sampling, making it ideal for nodes powered by small 5V USB power banks.
Extending the Build
If you need to monitor multiple DC loads (e.g., a solar charge controller output and a battery bank simultaneously), you will run into an I2C address collision, as the INA219 only supports a limited number of address jumpers.
The Solution: Add a TCA9548A I2C Multiplexer (Adafruit 2717). Wire the TCA9548A to the Pi's primary I2C bus, then plug up to 8 INA219 boards into the multiplexer's downstream channels. You will need to update the Python script to send a hex byte to the TCA9548A to switch channels before querying each INA219. For a deeper dive into I2C multiplexing logic, refer to the Adafruit TCA9548A guide.
Building headless projects for the raspberry pi 3 remains one of the most cost-effective ways to bridge physical sensors to networked MQTT dashboards like Node-RED or Home Assistant. By sticking to verified hardware variants, handling I2C exceptions gracefully in your Python code, and understanding the exact failure modes of the bus, you can deploy a node that runs for years without requiring a physical reboot.






