When makers search for "how to make raspberry pi," they are rarely asking about semiconductor fabrication. They are looking for a complete, ground-up guide to turning the bare Raspberry Pi PCB into a functional embedded node. In this guide, we are building a headless MQTT Environmental Sensor Hub. This project reads temperature, humidity, and barometric pressure from a Bosch BME280 sensor and publishes the telemetry to a local Mosquitto MQTT broker, ready for ingestion by Home Assistant, Node-RED, or custom dashboards.
This guide targets the Raspberry Pi 5 (4GB or 8GB variant) running Raspberry Pi OS Bookworm (64-bit). We will cover the physical I2C wiring, the Mosquitto broker configuration traps introduced in recent OS updates, and provide a fully compilable Python script using the modern Paho MQTT v2.0 API.
Project Spec Sheet & Required Hardware
Before ordering parts, note that the Raspberry Pi 5 features stronger I2C pull-up resistors (1.5kΩ compared to the Pi 4's 1.8kΩ). While this improves signal integrity for single sensors, it means you must keep your I2C bus capacitance low. Avoid running long, unshielded jumper wires if you plan to daisy-chain multiple devices later.
| Component | Exact Variant / Model | Estimated Cost | Notes |
|---|---|---|---|
| Microcontroller | Raspberry Pi 5 (4GB or 8GB) | $60 - $80 | Bookworm 64-bit OS required |
| Cooling | Raspberry Pi Active Cooler | $5 | Mandatory for Pi 5 under continuous load |
| Sensor | BME280 Breakout (3.3V I2C) | $8 - $12 | Ensure it is BME280, not BMP280 (no humidity) |
| Power Supply | 27W USB-C PD Power Supply | $12 | Must support 5V/5A PD for full peripheral power |
| Wiring | F-to-F Jumper Wires (10cm) | $3 | Keep under 15cm to respect I2C capacitance limits |
Pin Mapping & Physical Wiring
The BME280 communicates via I2C. The Raspberry Pi 5 exposes its primary I2C bus on GPIO 2 (SDA) and GPIO 3 (SCL). The BME280 breakout operates natively at 3.3V, meaning you can connect it directly to the Pi's 3.3V power pin without a logic level converter.
| BME280 Pin | Raspberry Pi 5 GPIO (Physical Pin) | Function |
|---|---|---|
| VIN / VCC | 3.3V (Physical Pin 1) | Power Input |
| GND | GND (Physical Pin 6) | Common Ground |
| SDA | GPIO 2 (Physical Pin 3) | I2C Data |
| SCL | GPIO 3 (Physical Pin 5) | I2C Clock |
OS Configuration & Python Environment
Raspberry Pi OS Bookworm shifted away from system-wide pip installs, enforcing PEP 668. You must use a virtual environment for Python dependencies. Furthermore, Mosquitto 2.0+ defaults to a secure, localhost-only posture, which breaks most legacy tutorials.
- Enable I2C: Run
sudo raspi-config, navigate to Interface Options > I2C, and enable it. Reboot the Pi. - Verify the Sensor: Run
sudo i2cdetect -y 1. You should see76or77in the grid. If the grid is empty, check your wiring. - Install Mosquitto Broker: Run
sudo apt update && sudo apt install mosquitto mosquitto-clients -y. - Configure Mosquitto Listener: Create a config file to allow local network connections:
sudo nano /etc/mosquitto/conf.d/default.conf
Add the following lines:listener 1883 allow_anonymous true
Save and restart the service:sudo systemctl restart mosquitto. - Setup Python Virtual Environment:
python3 -m venv ~/sensor_hub_env
source ~/sensor_hub_env/bin/activate
pip install paho-mqtt smbus2 bme280
The Complete MQTT Publisher Script
The following Python script initializes the I2C bus, calibrates the BME280, and publishes JSON-formatted telemetry to the MQTT broker every 10 seconds. It uses the Paho MQTT v2.0 API, which changed the on_connect callback signature to include reason_code and properties.
import time
import json
import smbus2
import bme280
import paho.mqtt.client as mqtt
# --- PIN & HARDWARE DEFINITIONS ---
# I2C Bus 1 is the default primary bus on Raspberry Pi 5
I2C_PORT = 1
# BME280 default I2C address (check i2cdetect if unsure)
BME_ADDRESS = 0x76
# --- MQTT CONFIGURATION ---
MQTT_BROKER_IP = "127.0.0.1" # Change to Pi's LAN IP if running script off-board
MQTT_PORT = 1883
MQTT_TOPIC = "home/environment/living_room"
QOS_LEVEL = 1
# Initialize I2C bus and load sensor calibration parameters
bus = smbus2.SMBus(I2C_PORT)
calibration_params = bme280.load_calibration_params(bus, BME_ADDRESS)
def on_connect(client, userdata, flags, reason_code, properties):
"""Paho MQTT v2.0 connection callback."""
if reason_code.is_failure:
print(f"[MQTT] Failed to connect: {reason_code}. Retrying...")
else:
print(f"[MQTT] Connected successfully to broker. Reason code: {reason_code}")
def on_publish(client, userdata, mid):
"""Callback triggered when a message is successfully handed to the network."""
print(f"[MQTT] Message {mid} published.")
# Configure MQTT Client
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="pi5_env_hub")
client.on_connect = on_connect
client.on_publish = on_publish
try:
client.connect(MQTT_BROKER_IP, MQTT_PORT, keepalive=60)
client.loop_start()
except Exception as e:
print(f"[CRITICAL] Initial MQTT connection failed: {e}")
exit(1)
def read_and_publish():
"""Reads sensor data and publishes to MQTT."""
try:
# Sample the sensor
data = bme280.sample(bus, BME_ADDRESS, calibration_params)
# Build telemetry payload
payload = {
"temperature_c": round(data.temperature, 2),
"humidity_pct": round(data.humidity, 2),
"pressure_hpa": round(data.pressure, 2),
"timestamp": time.time()
}
# Publish JSON string
result = client.publish(MQTT_TOPIC, json.dumps(payload), qos=QOS_LEVEL)
if result.rc != mqtt.MQTT_ERR_SUCCESS:
print(f"[MQTT] Publish failed with code: {result.rc}")
except FileNotFoundError:
print("[ERROR] I2C device not found. Is I2C enabled in raspi-config?")
except Exception as e:
print(f"[ERROR] Unexpected sensor/read error: {e}")
if __name__ == "__main__":
print("Starting Environmental Hub. Press Ctrl+C to stop.")
try:
while True:
read_and_publish()
time.sleep(10)
except KeyboardInterrupt:
print("\nStopping hub...")
client.loop_stop()
client.disconnect()
Debugging: Fixing Connection & I2C Errors
When building embedded nodes, you will inevitably hit environmental or configuration errors. Here is how to diagnose the two most common failure modes for this specific stack.
ConnectionRefusedError: [Errno 111] Connection refused
Ranked Causes & Fixes:
- Mosquitto Listener Not Configured: In Mosquitto 2.0+, if you do not explicitly define a
listenerin/etc/mosquitto/conf.d/, it binds only to localhost and rejects external TCP connections. Verify yourdefault.conffile haslistener 1883. - Service Not Running: The daemon may have crashed or failed to start. Run
sudo systemctl status mosquittoto check for active status. - Firewall Blocking Port: If you have
ufwenabled, port 1883 is blocked by default. Runsudo ufw allow 1883/tcp.
FileNotFoundError: [Errno 2] No such file or directory: '/dev/i2c-1'
Ranked Causes & Fixes:
- I2C Interface Disabled: You skipped the
raspi-configstep. Run it, enable I2C, and reboot. - Missing Kernel Module: Rare on standard Bookworm, but if the I2C kernel module isn't loading, add
dtparam=i2c_arm=onto your/boot/firmware/config.txtand reboot.
Extending or Simplifying Your Hub
To Simplify: If you do not have a home automation server and just want to log data for analysis, strip out the paho-mqtt library entirely. Replace the publish function with standard Python file I/O to append a CSV row to a local file, and use a cron job to rotate the log weekly.
To Extend:
- Add TLS Encryption: Generate self-signed certificates using OpenSSL and configure Mosquitto to require
certfileandkeyfilein the listener block. Update the Python script usingclient.tls_set(). - Integrate Home Assistant: In Home Assistant, add the MQTT integration pointing to your Pi's IP address. The sensor data will automatically be discovered if you format the MQTT payload to match Home Assistant's MQTT Discovery protocol.
- Add a Display: Wire an SSD1306 OLED display to the same I2C bus (it uses address 0x3C, which won't conflict with the BME280) and push local telemetry readings to the screen using the
luma.oledPython library.
Frequently Asked Questions
How to make Raspberry Pi run headless without a monitor?
To boot a Raspberry Pi headless, you must configure WiFi and SSH before the first boot. Flash Raspberry Pi OS using the official Raspberry Pi Imager. Before clicking "Write", click the gear icon (or press Ctrl+Shift+X) to open the Advanced Options. Here, you can set your hostname, inject your local WiFi SSID and password, and enable SSH (choose "Allow public-key authentication only" for security). Once flashed, insert the SD card, apply power, and connect via ssh pi@your-hostname.local.
How to make Raspberry Pi automatically run a Python script on boot?
The most robust method for modern Raspberry Pi OS (Bookworm) is using systemd. Create a service file at /etc/systemd/system/sensorhub.service. Define the [Unit], [Service] (pointing ExecStart to your virtual environment's Python binary and your script path), and [Install] sections. Run sudo systemctl enable sensorhub.service and sudo systemctl start sensorhub.service. Avoid using rc.local or .bashrc, as they are deprecated or unreliable for background daemon tasks.
How to make Raspberry Pi connect to a hidden WiFi network?
NetworkManager handles WiFi in Raspberry Pi OS Bookworm. To connect to a hidden SSID via the command line, use the nmcli tool. Run:
sudo nmcli dev wifi connect "YourHiddenSSID" password "YourPassword" hidden yes.
NetworkManager will automatically add the hidden=true flag to the connection profile, ensuring the Pi actively probes for the network on subsequent reboots rather than waiting for the router to broadcast the beacon.






