If you want to install Home Assistant on Raspberry Pi hardware while retaining direct access to the 40-pin GPIO header for local sensors, the standard Home Assistant OS (HAOS) image will block your path. HAOS locks down the host OS, making raw I2C/SPI access difficult without complex add-ons. The professional bench-standard for 2026 is to install Raspberry Pi OS Lite 64-bit and run Home Assistant Container via Docker. This gives you a rock-solid smart home hub and native Python access to the GPIO pins for local environmental monitoring.

This guide targets the Raspberry Pi 5 (8GB variant) running Raspberry Pi OS Lite 64-bit (Bookworm). We will pair it with an NVMe SSD for database longevity and wire a BME280 I2C sensor to push local climate data to Home Assistant via MQTT.

The Decision Path: Choosing Your Home Assistant Hardware

Before flashing an SD card, you need to select the right compute and storage tier. MicroSD cards will destroy themselves within a year under Home Assistant's constant database write load. Use this decision matrix to select your hardware.

Criteria Budget / Low Load Standard / Medium Load Pro / High Load (2026 Standard)
Compute Board Raspberry Pi 4 (4GB) Raspberry Pi 5 (4GB) Raspberry Pi 5 (8GB)
Storage High-Endurance microSD USB 3.0 SATA SSD NVMe M.2 via PCIe HAT
Zigbee/Matter USB Dongle (Direct) USB Dongle (Direct) USB Dongle + Powered Hub
Use Case < 20 devices, basic automations 20-100 devices, standard dashboards 100+ devices, local LLMs, heavy logging
The Concrete Pick: For 95% of users building a new system today, terminate your decision here: Buy the Raspberry Pi 5 (8GB) paired with an Argon ONE V3 M.2 NVMe case and a 256GB WD Blue SN580 NVMe SSD. This eliminates USB bottlenecks, provides passive cooling, and guarantees the I/O endurance required for the Home Assistant MariaDB/SQLite write cycles.

Exact Parts List and GPIO Pin Mapping

Here is the exact bill of materials and the physical pin mapping required for this build. We are integrating a Bosch BME280 sensor to monitor the server closet or living room environment directly from the Pi.

Bill of Materials

  • Compute: Raspberry Pi 5 (8GB RAM) - ~$80 USD
  • Case/Storage: Argon ONE V3 M.2 NVMe Raspberry Pi 5 Case - ~$45 USD
  • Drive: WD Blue SN580 256GB NVMe M.2 2242 SSD - ~$35 USD
  • Power: Official Raspberry Pi 27W USB-C Power Supply (Critical for Pi 5 PCIe stability) - ~$12 USD
  • Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID: 2652) - ~$15 USD
  • Radios: Sonoff Zigbee 3.0 USB Dongle Plus (P-Version, CC2652P) - ~$25 USD

BME280 to Raspberry Pi 5 Pin Mapping

The Raspberry Pi 5 maintains the standard 40-pin layout. We are using the primary I2C bus (i2c-1). Ensure your BME280 breakout has the I2C pull-up resistors enabled (most Adafruit/SparkFun boards do by default).

BME280 Pin Pi 5 GPIO / Function Physical Pin # Wire Color (Standard)
VIN / VCC 3.3V Power Pin 1 Red
GND Ground Pin 6 Black
SDA GPIO 2 (I2C1 SDA) Pin 3 Blue
SCL GPIO 3 (I2C1 SCL) Pin 5 Yellow

Step-by-Step: Install Home Assistant on Raspberry Pi

We bypass the standard Home Assistant OS installation to keep host-level I2C access. Follow these steps precisely.

  1. Flash the Base OS: Use Raspberry Pi Imager to flash Raspberry Pi OS Lite (64-bit) to your NVMe drive (via a USB NVMe enclosure) or a temporary microSD card. In the Imager's OS Customization settings, enable SSH, set your hostname to ha-pi5, and configure your WiFi/Ethernet.
  2. Boot and Enable PCIe/I2C: Boot the Pi. If using the Argon ONE V3, you must enable the PCIe interface. Edit the boot config: sudo nano /boot/firmware/config.txt. Add dtparam=pciex1 and dtparam=pciex1_gen=3 to the bottom. Also ensure dtparam=i2c_arm=on is present. Reboot.
  3. Verify I2C Hardware: Run sudo i2cdetect -y 1. You should see 76 or 77 in the grid, confirming the BME280 is physically detected on the I2C bus.
  4. Install Docker and Dependencies: Run the official Docker convenience script:
    curl -fsSL https://get.docker.com | sh
    Then install required packages: sudo apt-get install -y jq apparmor-utils.
  5. Pull Home Assistant Container: Create your config directory and launch the container using the official Raspberry Pi 5 compatible image:
    mkdir -p /opt/homeassistant/config
    sudo docker run -d \
      --name homeassistant \
      --privileged \
      --restart=unless-stopped \
      -e TZ=America/New_York \
      -v /opt/homeassistant/config:/config \
      -v /etc/localtime:/etc/localtime:ro \
      --net=host \
      ghcr.io/home-assistant/home-assistant:stable
  6. Install Mosquitto MQTT Broker: Home Assistant needs a broker to receive the sensor data. Install it via Docker:
    mkdir -p /opt/mosquitto/config /opt/mosquitto/data /opt/mosquitto/log
    sudo docker run -d --name mosquitto --restart=unless-stopped -p 1883:1883 -v /opt/mosquitto/config:/mosquitto/config -v /opt/mosquitto/data:/mosquitto/data -v /opt/mosquitto/log:/mosquitto/log eclipse-mosquitto
    Note: Add listener 1883 and allow_anonymous true to your mosquitto.conf file for local testing, then restart the container.
  7. Onboard HA: Navigate to http://ha-pi5.local:8123. Create your admin account. Go to Settings > Devices & Services > Add Integration > MQTT. Point it to 127.0.0.1 on port 1883.

Extending the Build: I2C Sensor to MQTT Integration

With the host OS exposed, we can write a Python script to read the BME280 and publish the payload to Home Assistant. This script targets the Raspberry Pi 5 (8GB) running the 64-bit Bookworm architecture.

First, install the Python dependencies on the host OS:
sudo apt install python3-pip python3-smbus2
pip3 install paho-mqtt bme280

Create the file /opt/homeassistant/bme280_publisher.py and paste the following complete, compilable code:

#!/usr/bin/env python3
"""
BME280 to Home Assistant MQTT Publisher
Target Board: Raspberry Pi 5 (8GB) running Pi OS Lite 64-bit
Pin Definitions: SDA=GPIO2 (Pin 3), SCL=GPIO3 (Pin 5)
"""

import smbus2
import bme280
import paho.mqtt.client as mqtt
import time
import json
import sys

# --- Configuration & Pin Definitions ---
I2C_BUS = 1
BME280_I2C_ADDRESS = 0x76  # Use 0x77 if your breakout board has the alternate address
MQTT_BROKER_IP = "127.0.0.1"
MQTT_PORT = 1883
MQTT_TOPIC = "homeassistant/sensor/pi5_closet/state"
POLL_INTERVAL_SEC = 60

# --- MQTT Callbacks ---
def on_connect(client, userdata, flags, rc, properties=None):
    if rc == 0:
        print(f"[INFO] Connected to MQTT Broker at {MQTT_BROKER_IP}")
    else:
        print(f"[ERROR] MQTT Connection failed with code {rc}")

def on_publish(client, userdata, mid, rc, properties=None):
    pass # Silent success

# --- Main Execution Loop ---
def main():
    # Initialize I2C Bus and Sensor Calibration
    try:
        bus = smbus2.SMBus(I2C_BUS)
        calibration_params = bme280.load_calibration_params(bus, BME280_I2C_ADDRESS)
        print("[INFO] BME280 sensor calibrated successfully via I2C.")
    except Exception as e:
        print(f"[FATAL] I2C Initialization failed: {e}")
        sys.exit(1)

    # Initialize MQTT Client (Paho v2.0+ API)
    client = mqtt.Client(client_id="pi5_bme280_publisher", protocol=mqtt.MQTTv5)
    client.on_connect = on_connect
    client.on_publish = on_publish
    
    try:
        client.connect(MQTT_BROKER_IP, MQTT_PORT, keepalive=120)
        client.loop_start()
    except Exception as e:
        print(f"[FATAL] MQTT Connection failed: {e}")
        sys.exit(1)

    # Polling Loop
    try:
        while True:
            try:
                # Read sensor data
                data = bme280.sample(bus, BME280_I2C_ADDRESS, calibration_params)
                
                # Build HA-compatible JSON payload
                payload = {
                    "temperature": round(data.temperature, 2),
                    "humidity": round(data.humidity, 2),
                    "pressure": round(data.pressure, 2)
                }
                
                # Publish to MQTT
                result = client.publish(MQTT_TOPIC, json.dumps(payload), qos=1)
                result.wait_for_publish()
                print(f"[DATA] Published: {payload}")
                
            except OSError as io_err:
                print(f"[ERROR] I2C Read Failure: {io_err}. Check physical wiring.")
            except Exception as loop_err:
                print(f"[ERROR] Unexpected loop error: {loop_err}")
                
            time.sleep(POLL_INTERVAL_SEC)
            
    except KeyboardInterrupt:
        print("\n[INFO] Stopping publisher...")
        client.loop_stop()
        client.disconnect()
        bus.close()

if __name__ == "__main__":
    main()

Run the script using python3 /opt/homeassistant/bme280_publisher.py. In Home Assistant, use the MQTT integration to listen to homeassistant/sensor/pi5_closet/state and configure your dashboard cards.

Troubleshooting: Boot and MQTT Failures

When working with bare-metal Pi OS and Docker, you will encounter specific errors. Here are the exact error strings and their ranked causes.

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

This occurs when the Python script attempts to read the BME280 via smbus2 but the hardware fails to acknowledge.

  1. Cause 1 (Most Likely): Loose Dupont wires on the physical Pin 3/Pin 5 header. I2C is highly sensitive to capacitance and poor contacts. Solder the header or use high-quality crimped connectors.
  2. Cause 2: Wrong I2C address. Run sudo i2cdetect -y 1. If the sensor shows at 0x77 instead of 0x76, update the BME280_I2C_ADDRESS variable in the Python script.
  3. Cause 3: I2C bus speed too high for long wire runs. Add dtparam=i2c_arm_baudrate=10000 to /boot/firmware/config.txt to slow the bus down to 10kHz.

Error 2: ConnectionRefusedError: [Errno 111] Connection refused

This occurs when the Python script tries to connect to the Mosquitto MQTT broker on localhost.

  1. Cause 1 (Most Likely): Mosquitto v2.0+ defaults to blocking external/unauthenticated connections. Ensure your mosquitto.conf explicitly contains listener 1883 and allow_anonymous true (for local testing only), and that you restarted the Docker container.
  2. Cause 2: The Docker container crashed. Run sudo docker ps to verify Mosquitto is running. If not, check logs via sudo docker logs mosquitto.
  3. Cause 3: Host firewall (UFW) blocking port 1883. Run sudo ufw allow 1883/tcp.
The First 3 Things to Check When It Fails:
1. Run sudo i2cdetect -y 1 to verify the kernel sees the sensor at the hardware level.
2. Run sudo docker ps to ensure both the homeassistant and mosquitto containers are in the Up state.
3. Verify your power supply. The Pi 5 will throttle the PCIe bus and USB ports if it detects a power supply under 27W, causing NVMe and Zigbee dongle dropouts. Check vcgencmd get_throttled.

How to Extend or Simplify the Build

Depending on your long-term maintenance goals, you should make a deliberate choice on how to manage this stack moving forward.

To Simplify: Switch to ESPHome

Running a custom Python script on the host OS means you must manage systemd services, Python virtual environments, and OS updates manually. If you want a "set it and forget it" environment, drop the BME280 Python script entirely. Instead, wire the BME280 to an ESP32-WROOM-32 dev board and flash it with ESPHome. ESPHome integrates natively into the Home Assistant UI, handles WiFi reconnections, and pushes data via the native API without requiring a local MQTT broker or host-level Python scripts.

To Extend: Add a Thread/Matter Border Router

The Raspberry Pi 5 has the compute headroom to run local AI and advanced radio stacks. To extend this build into a 2026 Matter-ready hub, purchase a Sonoff Zigbee 3.0 USB Dongle Plus (P-Version) and flash it with the OpenThread Border Router firmware. Plug it into the Pi 5's USB 3.0 port (via a powered hub to prevent 2.4GHz USB interference) and install the Silicon Labs Multiprotocol add-on in Home Assistant. This allows your Pi to natively route Matter-over-Thread devices directly into your local network without relying on cloud hubs.