This guide builds an automated basement exhaust fan controller targeting the Raspberry Pi 5 (8GB variant). It reads humidity via a BME280 I2C sensor on GPIO 2 (SDA) and GPIO 3 (SCL), triggering a 5V relay on GPIO 17 when moisture exceeds 60%. Below is the complete hardware spec sheet, pin mapping, production-ready Python code, and a decision tree for debugging the infamous I2C Remote I/O error.

Project Overview & Difficulty Rating

Difficulty: Intermediate (Requires I2C configuration and mains-adjacent relay wiring)
Time to Build: 45 minutes
Target Board: Raspberry Pi 5 (8GB) running Raspberry Pi OS (Bookworm, 64-bit)

Exact Parts List

Do not substitute the BME280 with a BMP280; the BMP variant lacks the humidity sensor required for this logic. Ensure your Pi 5 power supply can deliver the required 27W via USB-C PD to prevent brownouts when the relay switches.

  • Microcontroller: Raspberry Pi 5 (8GB variant, SKU SC1113) - ~$80
  • Power Supply: Official Raspberry Pi 27W USB-C PD Power Supply - ~$12
  • Sensor: BME280 Breakout Board (Bosch sensor, 3.3V logic, I2C interface) - ~$6
  • Actuator: 5V Single-Channel Relay Module with optocoupler (Songle SRD-05VDC-SL-C) - ~$4
  • Wiring: 22 AWG stranded hookup wire, female-to-female Dupont jumpers

Hardware Wiring & Pin Mapping

The Raspberry Pi 5 operates strictly at 3.3V logic on its GPIO header. Feeding 5V back into any GPIO pin (including SDA/SCL) will permanently destroy the SoC. The relay module’s optocoupler isolates the Pi from the relay coil's back-EMF, but the control signal must still be 3.3V compatible.

Table 1: BME280 & Relay Pin Mapping (BCM Numbering)
ComponentComponent PinPi 5 GPIO (BCM)Pi 5 Physical PinWire Color (Std)
BME280VIN / VCC3.3V Power1Red
BME280GNDGround6Black
BME280SCLGPIO 3 (SCL)5Yellow
BME280SDAGPIO 2 (SDA)3Blue
Relay ModuleVCC5V Power2Red
Relay ModuleGNDGround9Black
Relay ModuleIN (Signal)GPIO 1711Orange
Bench Tip: Many cheap BME280 breakout boards from online marketplaces lack onboard I2C pull-up resistors. The Pi 5's internal pull-ups are often too weak for long wire runs. If your wire run exceeds 12 inches, solder 4.7kΩ pull-up resistors between SDA/VCC and SCL/VCC on the sensor board.

Python Control Code for Raspberry Pi 5

This script uses smbus2 for raw I2C communication, the lightweight RPi.bme280 package for sensor calibration math, and gpiozero for safe relay state management. It includes hysteresis to prevent the relay from rapidly clicking on and off when humidity hovers exactly at the threshold.

Prerequisites: Install the required libraries via terminal:
sudo apt update && sudo apt install python3-smbus python3-gpiozero -y
pip3 install RPi.bme280 --break-system-packages (Note: Use a virtual environment in production; --break-system-packages is for quick bench testing on Bookworm).

import time
import smbus2
import bme280
from gpiozero import OutputDevice
import logging

# --- PIN & ADDRESS DEFINITIONS ---
I2C_PORT = 1
BME280_ADDRESS = 0x76  # Verify with `i2cdetect -y 1` (can be 0x77)
RELAY_GPIO_PIN = 17    # BCM GPIO 17 (Physical Pin 11)

# --- THRESHOLDS ---
HUMIDITY_THRESHOLD = 60.0  # Trigger relay above 60% RH
HYSTERESIS = 5.0           # Prevent rapid relay clicking

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')

def main():
    # Initialize I2C Bus
    try:
        bus = smbus2.SMBus(I2C_PORT)
        calibration_params = bme280.load_calibration_params(bus, BME280_ADDRESS)
        logging.info(f"BME280 initialized at address {hex(BME280_ADDRESS)}")
    except OSError as e:
        logging.critical(f"Failed to initialize BME280: {e}")
        return
    except Exception as e:
        logging.critical(f"Unexpected sensor error: {e}")
        return

    # Initialize Relay (Active Low for most optocoupler modules)
    relay = OutputDevice(RELAY_GPIO_PIN, active_high=False, initial_value=False)
    
    try:
        while True:
            data = bme280.sample(bus, BME280_ADDRESS, calibration_params)
            humidity = data.humidity
            temp_c = data.temperature
            
            logging.info(f"Temp: {temp_c:.1f}C | Humidity: {humidity:.1f}%")
            
            # Hysteresis logic
            if humidity > HUMIDITY_THRESHOLD and not relay.is_active:
                logging.warning("Humidity high! Triggering exhaust fan.")
                relay.on()
            elif humidity < (HUMIDITY_THRESHOLD - HYSTERESIS) and relay.is_active:
                logging.info("Humidity normalized. Turning off exhaust fan.")
                relay.off()
                
            time.sleep(10)
            
    except KeyboardInterrupt:
        logging.info("Shutting down safely...")
    finally:
        relay.off()
        relay.close()
        bus.close()

if __name__ == "__main__":
    main()

Debugging: Fixing I2C and Boot Failures

When working with I2C on the Pi 5, you will inevitably encounter the following exact error string when running the script:

OSError: [Errno 121] Remote I/O error

This is a generic Linux kernel I2C bus failure. It does not mean your sensor is broken; it means the Pi's I2C controller failed to receive an ACKnowledge (ACK) bit from the target address. Here are the first three things to check when it fails, ranked by probability:

  1. I2C is not enabled in the bootloader config. In Raspberry Pi OS Bookworm, the legacy raspi-config overlay sometimes fails to persist on headless Pi 5 setups. Open /boot/firmware/config.txt via terminal (sudo nano /boot/firmware/config.txt) and ensure the line dtparam=i2c_arm=on is present and uncommented. Reboot.
  2. Incorrect I2C Address. The BME280 defaults to 0x76 if the SDO pin is tied to GND, but many breakout boards tie SDO to VCC, shifting the address to 0x77. Run i2cdetect -y 1 in the terminal. If you see 77 in the grid, update BME280_ADDRESS = 0x77 in the Python script.
  3. SDA and SCL are swapped. I2C is not auto-polarizing. If you connect SDA to Pin 5 and SCL to Pin 3, the bus will hang and throw Errno 121. Verify against the pin mapping table above.

For deeper hardware diagnostics, consult the official Raspberry Pi configuration documentation regarding device tree overlays, or review the Bosch BME280 datasheet for I2C timing requirements.

Extending and Simplifying the Build

How to Simplify

If I2C debugging is frustrating your build process, swap the BME280 for a DHT22 (AM2302). The DHT22 uses a single-bus digital protocol requiring only one data pin (plus VCC/GND) and a 10kΩ pull-up resistor. You will lose barometric pressure data and the I2C bus entirely, but you can read it using the adafruit-circuitpython-dht library with zero bus-configuration overhead.

How to Extend

To integrate this into a broader smart home ecosystem, add the paho-mqtt Python library. Wrap the data.humidity and data.temperature variables in a JSON payload and publish them to a local Mosquitto broker. Home Assistant can then ingest the MQTT topics to trigger secondary automations, like turning on a dehumidifier smart plug or sending a push notification if the basement floods while you are away.

Frequently Asked Questions

What are the best home projects for Raspberry Pi 5 in 2026?

The Pi 5’s PCIe 2.0 interface and dual 4K display outputs make it ideal for high-bandwidth home projects. The top tier includes local AI-powered security camera NVRs (using Frigate and a Coral TPU via PCIe), whole-home audio servers (using Volumio), and offline smart home hubs (Home Assistant OS). For simple sensor-logging tasks like this exhaust fan, a Pi Zero 2 W is usually more cost-effective, but the Pi 5 is best if you plan to consolidate multiple Docker containers onto one node.

Can I use a Raspberry Pi Zero 2 W for this home project instead?

Yes. The Python code and wiring are 100% identical for the Pi Zero 2 W. However, the Zero 2 W has a lower thermal ceiling and lacks the USB-C PD power delivery of the Pi 5. If you are switching high-current inductive loads (like a large exhaust fan) via the relay, ensure your Zero 2 W power supply can handle the transient voltage drops on the 5V rail, or the Wi-Fi chip will brownout and drop your MQTT connection.

How do I make my Raspberry Pi home project survive a power outage?

You need two things: hardware backup and filesystem protection. First, wire a 5V UPS HAT (like the PiJuice or Geekworm X735) to keep the board running during blips. Second, SD card corruption is the leading cause of Pi failure after sudden power loss. Edit your /etc/fstab to mount the root filesystem as ro (read-only), or use overlayfs to redirect all write operations to RAM. For a permanent solution, boot the Pi 5 from an NVMe SSD via the PCIe ribbon cable.

Is it safe to leave a Raspberry Pi running a home project 24/7?

Yes, provided you manage thermals and storage wear. The Pi 5 runs hotter than the Pi 4; an active cooler (the official Raspberry Pi Active Cooler) is mandatory for 24/7 operation in an enclosed basement or attic space to prevent thermal throttling at 80°C. Additionally, writing sensor logs to an SD card every 10 seconds will kill the card's NAND flash within months. Always log to a RAM disk (tmpfs) or an external USB SSD if continuous logging is required.