Project Spec Sheet & Overview

When makers search for cool things to do with a Raspberry Pi, they often find superficial media center builds or basic web servers. But the Pi's true power lies in bridging physical hardware with high-level Python automation. This project builds an I2C Climate Controller: a system that reads precise temperature and humidity data from a BME280 sensor and triggers a 5V relay to control a desk fan, space heater, or grow tent exhaust.

ParameterSpecification
Difficulty RatingIntermediate (Requires I2C bus config & mains-adjacent relay safety)
Estimated Build Time90 - 120 minutes
Target Board VariantRaspberry Pi 4 Model B (4GB) or Raspberry Pi 5 (4GB/8GB)
OS RequirementRaspberry Pi OS (Bookworm or newer, 64-bit)
Estimated Cost$65 - $85 (excluding Pi and power supply)
⚠️ Safety Callout: The relay module in this build is capable of switching 120V/240V AC mains voltage. This guide covers the low-voltage DC control side (GPIO to Relay). If you connect the relay's COM/NO/NC terminals to mains voltage, you must de-energize the circuit, verify it is dead with a CAT III multimeter, and ensure all AC connections are housed in a proper junction box. Local electrical codes may require a licensed electrician for permanent mains wiring.

Hardware Bill of Materials & Pin Mapping

To ensure the code compiles and runs without modification, use these exact component variants. Substituting a DHT11 for the BME280 will break the I2C implementation, as the DHT series uses a proprietary single-bus protocol.

Exact Parts List

  • Microcontroller: Raspberry Pi 4 Model B (4GB RAM) or Raspberry Pi 5
  • Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID: 2652)
  • Actuator: 2-Channel 5V Opto-Isolated Relay Module (SRD-05VDC-SL-C)
  • Power: 5V 3A USB-C Power Supply (Official Raspberry Pi equivalent)
  • Wiring: Female-to-Female and Male-to-Female Dupont jumper wires

GPIO & I2C Pin Mapping Table

The Raspberry Pi's I2C bus operates at 3.3V logic. The BME280 is natively 3.3V tolerant, but the 5V relay module requires a 5V power rail while accepting 3.3V logic triggers on its optocoupler inputs.

Pi Physical PinBCM GPIO / FunctionTarget ModuleModule Pin
Pin 13.3V PowerBME280 BreakoutVIN / 3Vo
Pin 3GPIO 2 (SDA.1)BME280 BreakoutSDA
Pin 5GPIO 3 (SCL.1)BME280 BreakoutSCL
Pin 6GroundBME280 BreakoutGND
Pin 25V PowerRelay ModuleVCC (JD-VCC jumpered)
Pin 16GPIO 23Relay ModuleIN1
Pin 9GroundRelay ModuleGND

Step-by-Step Wiring & Software Setup

  1. Enable I2C Interface: Boot your Pi, open a terminal, and run sudo raspi-config. Navigate to Interface Options > I2C and enable it. Reboot the Pi.
  2. Verify Sensor Address: With the BME280 wired to Pins 1, 3, 5, and 6, run i2cdetect -y 1. You should see 77 in the grid. (If you see 76, your specific breakout has the address pad bridged; update the Python code variable accordingly).
  3. Install Python Dependencies: We use smbus2 for raw I2C communication and gpiozero for modern, safe GPIO control. Run:
    sudo apt update && sudo apt install python3-smbus python3-gpiozero i2c-tools
    pip3 install RPi.bme280
  4. Wire the Relay Opto-Isolator: Ensure the JD-VCC jumper on the relay module is intact. Connect Pi Pin 2 (5V) to the relay VCC, and Pi Pin 9 (GND) to relay GND. This powers the relay coil independently of the Pi's 3.3V logic rail, preventing brownouts when the coil energizes.

Complete Python Control Code

This script targets the Raspberry Pi 4 and 5 running Raspberry Pi OS. It utilizes the gpiozero library (the current standard over the deprecated RPi.GPIO) and includes robust exception handling for I2C bus failures.

import time
import smbus2
import bme280
from gpiozero import OutputDevice
from signal import pause

# --- Pin & Hardware Definitions ---
RELAY_PIN = 23          # BCM GPIO 23 (Physical Pin 16)
I2C_BUS_ID = 1          # I2C bus 1 (Pins 3 & 5)
BME_ADDRESS = 0x77      # Default Adafruit BME280 address

# Temperature thresholds (Celsius) with 2-degree hysteresis to prevent relay chatter
TEMP_HIGH = 28.0
TEMP_LOW = 26.0

# Initialize Hardware
# active_high=False because most 5V relay modules trigger on LOW (sink current)
relay = OutputDevice(RELAY_PIN, active_high=False)
bus = smbus2.SMBus(I2C_BUS_ID)

try:
    calibration_params = bme280.load_calibration_params(bus, BME_ADDRESS)
except OSError:
    print('Failed to load BME280 calibration. Check I2C wiring.')
    exit(1)

def monitor_climate():
    print(f'Starting climate monitor. Relay on GPIO {RELAY_PIN}.')
    try:
        while True:
            # Sample sensor data
            data = bme280.sample(bus, BME_ADDRESS, calibration_params)
            temp_c = data.temperature
            humidity = data.humidity
            
            print(f'Temp: {temp_c:.2f}C | Humidity: {humidity:.1f}%')
            
            # Hysteresis control logic
            if temp_c >= TEMP_HIGH:
                if not relay.is_active:
                    relay.on()
                    print(' -> Relay ON: Cooling triggered.')
            elif temp_c <= TEMP_LOW:
                if relay.is_active:
                    relay.off()
                    print(' -> Relay OFF: Target temp reached.')
                    
            time.sleep(5)
            
    except OSError as e:
        # Catch the specific I2C bus error
        if e.errno == 121:
            print('CRITICAL: OSError: [Errno 121] Remote I/O error.')
            print('The I2C bus dropped. Check physical connections and pull-ups.')
        else:
            print(f'Unexpected OSError: {e}')
    except KeyboardInterrupt:
        print('\nUser interrupted. Shutting down safely.')
    finally:
        # Always clean up hardware state on exit
        relay.off()
        bus.close()
        print('Relay deactivated. I2C bus closed.')

if __name__ == '__main__':
    monitor_climate()

Debugging: Fixing the I2C Bus Drop

The most common failure mode when integrating I2C sensors on the Pi is the dreaded bus lockup. If your script crashes or hangs, you will likely see this exact error string in your terminal:

OSError: [Errno 121] Remote I/O error

The First Three Things to Check

When this error strikes, do not immediately rewrite your code. The issue is almost always physical or configuration-level. Check these three items in order:

  1. Run i2cdetect -y 1: If the grid is entirely blank, your SDA/SCL wires are swapped, or the sensor lacks power. If you see UU instead of 77, a kernel driver (like iio_bme280) has already claimed the address. You must blacklist the conflicting driver in /boot/firmware/config.txt.
  2. Verify the 3.3V Rail Stability: The BME280 requires a minimum of 1.71V on its VDD pin to maintain I2C logic levels. If you are drawing heavy current from the Pi's 5V rail (like a relay coil) without proper decoupling, the Pi's onboard 3.3V regulator can sag, causing the I2C transceiver to fail mid-transaction.
  3. Inspect Pull-Up Resistors: The Pi's internal I2C pull-ups are 1.8kΩ. This is sufficient for a sensor plugged directly into the GPIO header. However, if you are using Dupont wires longer than 30cm, parasitic capacitance will round off the I2C square waves, resulting in Errno 121. Solder external 4.7kΩ pull-up resistors between SDA/SCL and 3.3V on the sensor breakout.

Ranked Causes for Errno 121

  • Cause 1 (60%): SDA and SCL pins swapped at the breadboard.
  • Cause 2 (25%): Loose Dupont jumper wire causing intermittent contact.
  • Cause 3 (10%): Missing I2C pull-up resistors on long wire runs.
  • Cause 4 (5%): Address collision with another I2C device on the same bus.

How to Extend or Simplify the Build

Depending on your experience level and end goal, you can modify this architecture.

💡 Tip: Simplifying for Beginners
If raw I2C wiring and smbus2 calibration math feel overwhelming, swap the BME280 breakout for the Pimoroni Enviro+ HAT. It plugs directly over the Pi's GPIO header, eliminating jumper wires entirely. You can then use Pimoroni's pre-packaged Python library, reducing the code to three lines.

Extending for Advanced Makers:
To turn this into a true IoT node, integrate the Eclipse Paho MQTT library. Publish the temp_c and humidity variables to a local Mosquitto broker. From there, Home Assistant can ingest the MQTT topics to build historical dashboards, completely decoupling the Pi from the relay logic and letting your smart home server handle the hysteresis automation.

FAQ: More Cool Things to Do with a Raspberry Pi

What are the coolest things to do with a Raspberry Pi for home automation?

Beyond climate control, the most impactful home automation project is building a local Home Assistant Green equivalent. By installing Home Assistant OS on a Pi 4 or 5, you can integrate Zigbee (via a Sonoff Zigbee 3.0 USB Dongle Plus) and Matter devices. This keeps your smart home data entirely off the cloud, reduces latency to under 50ms, and allows complex automations like triggering motorized blinds based on the sun's azimuth angle.

Are there cool things to do with a Raspberry Pi that don't require coding?

Yes. You can configure a Pi as a network-wide ad blocker using Pi-hole. It requires zero programming—just flashing the OS and running the automated installer script. Once configured as your router's DNS server, it blocks telemetry and ads across every device on your WiFi. Another no-code option is setting up RetroPie to emulate classic consoles using a USB controller and pre-compiled ROM packages.

What cool things can you do with a Raspberry Pi 5 compared to older models?

The Raspberry Pi 5 introduces a dedicated PCIe 2.0 interface, which changes the game for storage-bound projects. The coolest application is building a NAS (Network Attached Storage) using an NVMe M.2 SSD via a PCIe HAT. Unlike the Pi 4, which bottlenecked at USB 3.0 speeds (~300MB/s real-world), the Pi 5 can saturate gigabit Ethernet limits, making it a viable, low-power Plex media server capable of hardware-accelerated 4K transcoding via its upgraded VideoCore VII GPU.