When building reliable network Raspberry Pi projects for a home lab or small office rack, the Raspberry Pi 5’s upgraded I/O and PCIe lane make it a viable replacement for entry-level x86 micro-servers. However, moving from a simple desktop toy to a headless, always-on network hub requires rigorous power management, robust sensor integration, and bulletproof telemetry scripts.
This guide walks through building a rack-mounted MQTT broker and environmental monitor. We will wire a BME280 sensor to the Pi 5’s hardware I2C bus, configure Mosquitto MQTT with modern security defaults, and write a fault-tolerant Python script to publish telemetry. The code and configurations specifically target the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS Bookworm 64-bit.
Project Overview and Hardware Specifications
A common failure point in embedded network hubs is power loss corrupting the SD card, or thermal throttling dropping network packets. This build solves both by integrating a UPS HAT and moving the OS to an NVMe drive via the Pi 5's PCIe 2.0 interface. The BME280 provides critical temperature, humidity, and barometric pressure data to ensure your server rack isn't quietly baking your equipment.
Estimated Build Time: 2 hours hardware, 1 hour software
Hardware Bill of Materials (2026 Pricing)
| Component | Exact Model / Variant | Interface | Est. Price |
|---|---|---|---|
| Compute Board | Raspberry Pi 5 (8GB RAM) | PCIe / 40-pin Header | $80.00 |
| Power Backup | Waveshare UPS HAT (E) for Pi 5 | I2C / pogo pins | $38.00 |
| Env. Sensor | Adafruit BME280 Breakout | I2C (Hardware) | $19.50 |
| Storage HAT | Geekworm X1001 NVMe Shield | PCIe 2.0 x1 | $25.00 |
| Storage Drive | WD Blue SN580 1TB NVMe (2242) | M.2 M-Key | $65.00 |
Pin Mapping and Physical Wiring
The Raspberry Pi 5 routes its primary I2C bus through the new RP1 southbridge chip. Unlike the Pi 4, the Pi 5 includes onboard 1.8kΩ pull-up resistors for the primary I2C1 bus, meaning you generally do not need external pull-ups for short wire runs. However, you must ensure your sensor breakout is configured for I2C and not SPI.
BME280 to Pi 5 40-Pin Header Mapping
| BME280 Pin | Pi 5 Physical Pin | BCM GPIO / Function | Wire Color (Standard) |
|---|---|---|---|
| VIN (3-5V) | Pin 1 | 3.3V Power | Red |
| GND | Pin 6 | Ground | Black |
| SCK (SCL) | Pin 5 | GPIO 3 (SCL1) | Yellow |
| SDI (SDA) | Pin 3 | GPIO 2 (SDA1) | Blue |
| CSB | Not Connected | Floating (Sets I2C Addr 0x77) | N/A |
| SDO | Not Connected | Floating | N/A |
0x77. If you are using a generic Amazon/eBay breakout board, the address is often 0x76. You will need to adjust the BME280_ADDR variable in the Python script below accordingly. Always verify with i2cdetect -y 1 before running your code.
Software Stack and Configuration Steps
Before writing code, the underlying OS and broker must be configured. The biggest stumbling block in modern network Raspberry Pi projects is the Mosquitto 2.0 update, which changed default listener behavior to enhance security, breaking countless legacy tutorials.
- Flash and Boot: Flash Raspberry Pi OS Bookworm 64-bit (Lite) to your NVMe drive using Raspberry Pi Imager. Ensure you enable SSH and set a strong password in the Imager settings.
- Enable I2C: Run
sudo raspi-config, navigate to Interface Options > I2C, and enable it. Reboot. - Install Dependencies: Update your package manager and install the required Python and MQTT tools:
sudo apt update && sudo apt install -y python3-pip python3-venv mosquitto mosquitto-clients i2c-tools python3 -m venv ~/hub-env source ~/hub-env/bin/activate pip install paho-mqtt smbus2 bme280 - Configure Mosquitto 2.0+ Listener: By default, Mosquitto 2.0+ only binds to localhost and requires authentication. For a local lab hub, we explicitly define the listener. Create a config file:
Paste the following:sudo nano /etc/mosquitto/conf.d/local_hub.conf
Save, exit, and restart the service:listener 1883 0.0.0.0 allow_anonymous truesudo systemctl restart mosquitto.
For deeper configuration options on ACLs (Access Control Lists) and TLS encryption for production environments, refer to the official Mosquitto documentation.
Complete Python Telemetry Script
This script reads the BME280 sensor via the smbus2 and bme280 libraries, formats the data as a JSON payload, and publishes it to the local MQTT broker. It includes robust error handling for both I2C bus dropouts and network disconnects.
import time
import json
import paho.mqtt.client as mqtt
import smbus2
import bme280
import sys
# --- PIN & CONFIG DEFINITIONS ---
# BME280 is wired to Pi 5 Hardware I2C1 (Pin 3 = SDA1, Pin 5 = SCL1)
I2C_BUS_ID = 1
# Use 0x77 for Adafruit, 0x76 for most generic breakouts
BME280_I2C_ADDR = 0x77
MQTT_BROKER_IP = "127.0.0.1"
MQTT_PORT = 1883
MQTT_TOPIC = "home_lab/rack/environment"
QOS_LEVEL = 1 # QoS 1 ensures at-least-once delivery
POLL_INTERVAL_SEC = 15
def on_connect(client, userdata, flags, rc, properties=None):
if rc == 0:
print(f"[MQTT] Connected to broker at {MQTT_BROKER_IP}")
else:
print(f"[MQTT] Connection failed with result code {rc}")
def main():
# Initialize I2C Bus and Sensor Calibration
try:
bus = smbus2.SMBus(I2C_BUS_ID)
calibration_params = bme280.load_calibration_params(bus, BME280_I2C_ADDR)
print(f"[I2C] BME280 initialized at address {hex(BME280_I2C_ADDR)}")
except Exception as e:
print(f"[FATAL] I2C Initialization failed: {e}")
sys.exit(1)
# Initialize MQTT Client (Paho v2.0 API format)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="pi5_rack_monitor")
client.on_connect = on_connect
try:
client.connect(MQTT_BROKER_IP, MQTT_PORT, keepalive=60)
client.loop_start()
except Exception as e:
print(f"[FATAL] MQTT Broker connection failed: {e}")
sys.exit(1)
print(f"[MAIN] Publishing to {MQTT_TOPIC} every {POLL_INTERVAL_SEC}s. Press Ctrl+C to exit.")
try:
while True:
try:
# Read Sensor Data
data = bme280.sample(bus, BME280_I2C_ADDR, calibration_params)
payload = {
"temp_c": round(data.temperature, 2),
"humidity": round(data.humidity, 2),
"pressure_hpa": round(data.pressure, 2),
"timestamp": int(time.time())
}
# Publish to MQTT
result = client.publish(MQTT_TOPIC, json.dumps(payload), qos=QOS_LEVEL)
result.wait_for_publish()
print(f"[TX] {payload}")
except OSError as ioe:
print(f"[ERROR] I2C Read Fault: {ioe}. Check wiring.")
except ValueError as ve:
print(f"[ERROR] Sensor Data Corruption: {ve}.")
except Exception as tx_err:
print(f"[ERROR] MQTT Publish Fault: {tx_err}")
time.sleep(POLL_INTERVAL_SEC)
except KeyboardInterrupt:
print("\n[MAIN] Shutting down gracefully.")
client.loop_stop()
client.disconnect()
bus.close()
if __name__ == "__main__":
main()
Debugging I2C and Network Failures
When working with embedded network hubs, hardware and software faults often mask each other. Below are the exact error strings Python will throw, ranked by their most likely root causes on the Pi 5 platform.
Error: OSError: [Errno 121] Remote I/O error
This is the most common I2C failure. It means the Pi's RP1 southbridge attempted to clock data, but the sensor did not acknowledge (NACK) the request.
- Cause 1 (Most Likely): The BME280 I2C address is wrong. You are polling
0x77but the board is hardcoded to0x76. - Cause 2: Loose Dupont jumper wires on the SDA/SCL pins. The Pi 5 header pins can be slightly shorter than Pi 4; ensure a firm push.
- Cause 3: The sensor breakout is in SPI mode. If the CSB (Chip Select) pin on your specific breakout is tied to GND instead of floating or pulled high, it disables the I2C interface.
Error: ConnectionRefusedError: [Errno 111] Connection refused
The Python script cannot reach the Mosquitto broker on port 1883.
- Cause 1 (Most Likely): You skipped the Mosquitto 2.0+
listenerconfiguration step, and the broker is silently ignoring external or unauthenticated requests. - Cause 2: The Mosquitto service crashed or isn't running. Check with
systemctl status mosquitto. - Cause 3: A local firewall (like
ufw) is blocking port 1883.
The First 3 Things to Check When It Fails
Before rewriting code or swapping parts, run this exact diagnostic sequence on the Pi 5 CLI:
- Verify I2C Hardware: Run
i2cdetect -y 1. You must see a76or77in the grid. If the grid is empty, your wiring or sensor is dead. If you seeUU, another driver has claimed the bus. - Verify I2C Overlay: Run
cat /boot/firmware/config.txt | grep i2c. Ensuredtparam=i2c_arm=onis present and uncommented. - Verify MQTT Listener: Run
mosquitto_sub -h 127.0.0.1 -t "#" -vin a separate terminal. If it immediately exits with a connection error, your Mosquitto config file is malformed or not loaded.
For a deeper understanding of the Pi 5's RP1 chip I/O architecture and edge cases with clock stretching, consult the official Raspberry Pi hardware documentation.
Extending or Simplifying the Build
Not every network Raspberry Pi project needs to be a rack-mounted enterprise clone. Here is how to scale this build up or down based on your actual requirements.
How to Simplify (The Desktop Node)
If you just need a remote room monitor and don't care about NVMe speeds or power outages:
- Drop the NVMe and UPS HAT: Boot directly from a high-endurance microSD card (e.g., SanDisk High Endurance 64GB). This cuts the hardware cost by over 50%.
- Use a Pi Zero 2 W: The code provided above is entirely compatible with the Pi Zero 2 W running Bookworm Lite. Just ensure you solder the 40-pin header to the Zero.
How to Extend (The Fleet Manager)
If you are building a fleet of network nodes across a large property:
- Add Prometheus Exporter: Instead of just pushing to MQTT, wrap the Python script in a lightweight Flask/FastAPI web server to expose a
/metricsendpoint. This allows Prometheus to scrape the Pi 5 natively for Grafana dashboards. - Home Assistant Integration: Modify the MQTT payload to include Home Assistant Discovery topics. By publishing a specific JSON config payload to
homeassistant/sensor/pi5_rack/config, Home Assistant will automatically detect and create the temperature and humidity entities without manual YAML configuration. - Add a UPS Serial Monitor: The Waveshare UPS HAT (E) exposes battery telemetry via I2C address
0x2D. You can expand the Pythonpayloaddictionary to read registers0x01(Voltage) and0x0A(Capacity), giving you remote visibility into your rack's battery backup health.






