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
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.
| Component | Component Pin | Pi 5 GPIO (BCM) | Pi 5 Physical Pin | Wire Color (Std) |
|---|---|---|---|---|
| BME280 | VIN / VCC | 3.3V Power | 1 | Red |
| BME280 | GND | Ground | 6 | Black |
| BME280 | SCL | GPIO 3 (SCL) | 5 | Yellow |
| BME280 | SDA | GPIO 2 (SDA) | 3 | Blue |
| Relay Module | VCC | 5V Power | 2 | Red |
| Relay Module | GND | Ground | 9 | Black |
| Relay Module | IN (Signal) | GPIO 17 | 11 | Orange |
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:
- I2C is not enabled in the bootloader config. In Raspberry Pi OS Bookworm, the legacy
raspi-configoverlay sometimes fails to persist on headless Pi 5 setups. Open/boot/firmware/config.txtvia terminal (sudo nano /boot/firmware/config.txt) and ensure the linedtparam=i2c_arm=onis present and uncommented. Reboot. - Incorrect I2C Address. The BME280 defaults to
0x76if the SDO pin is tied to GND, but many breakout boards tie SDO to VCC, shifting the address to0x77. Runi2cdetect -y 1in the terminal. If you see77in the grid, updateBME280_ADDRESS = 0x77in the Python script. - 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.






