When exploring python and raspberry pi projects, the leap from blinking an LED to controlling real-world mains or high-current loads requires a firm grasp of both hardware logic levels and software error handling. This guide walks through building an I2C-based environmental monitor that triggers physical relays based on temperature and humidity thresholds.
Target Board Variant: This code and wiring scheme specifically targets the Raspberry Pi 4 Model B (4GB or 8GB variant) running the 64-bit Raspberry Pi OS (Bookworm or newer). While the logic applies to the Pi 5, the Pi 5's shifted I2C bus mappings and strict 3.3V tolerances require slight pin adjustments not covered here.
Project Specification & Parts List
Estimated Build Time: 45 minutes (hardware) + 30 minutes (software setup).
| Component | Exact Variant / Model | Approx. Cost (2026) |
|---|---|---|
| Microcontroller | Raspberry Pi 4 Model B (4GB RAM) | $55.00 |
| Environmental Sensor | Adafruit BME280 I2C/SPI Breakout (Product ID: 2652) | $12.50 |
| Relay Module | HiLetgo 4-Channel 5V Relay Module (SRD-05VDC-SL-C with optocouplers) | $8.00 |
| Wiring & Misc | Female-to-Female jumper wires, 2x 4.7kΩ pull-up resistors (optional, Adafruit board has internal pull-ups) | $5.00 |
Hardware Wiring & Pin Mapping
The most common failure point in python and raspberry pi projects involving relays is frying the Pi's BCM2711 SoC by feeding 5V logic back into a 3.3V GPIO pin. Standard 5V relay modules use optocouplers, but they often ship with a jumper connecting VCC and JDVCC. You must remove this jumper.
By separating VCC (logic side) and JDVCC (relay coil side), we can safely drive the optocoupler LEDs using the Pi's 3.3V pins while powering the actual relay coils with the Pi's 5V rail.
| Pi 4 Pin (Physical) | BCM GPIO | Function | Destination Module & Pin |
|---|---|---|---|
| Pin 1 | 3.3V Power | Logic VCC | BME280 VIN AND Relay Module VCC (Remove JDVCC jumper!) |
| Pin 2 | 5V Power | Coil Power | Relay Module JDVCC |
| Pin 3 | GPIO 2 (SDA1) | I2C Data | BME280 SDI |
| Pin 5 | GPIO 3 (SCL1) | I2C Clock | BME280 SCK |
| Pin 6 | GND | Common Ground | BME280 GND AND Relay Module GND |
| Pin 11 | GPIO 17 | Relay 1 Trigger | Relay Module IN1 (Exhaust Fan) |
| Pin 13 | GPIO 27 | Relay 2 Trigger | Relay Module IN2 (Heater) |
Python Environment & Dependencies
Raspberry Pi OS Bookworm enforces PEP 668, meaning you can no longer install packages globally via pip. We will use a virtual environment. Open your terminal and run these numbered steps:
- Enable the I2C interface:
sudo raspi-config→ Interface Options → I2C → Enable. - Reboot the Pi, then verify the sensor is visible:
sudo i2cdetect -y 1. You should see76or77in the grid. - Create and activate a virtual environment:
python3 -m venv ~/enviro_controller source ~/enviro_controller/bin/activate - Install the required libraries. We use
rpi-lgpiobecause the legacyRPi.GPIOlibrary is deprecated on modern 64-bit Pi OS builds:pip install adafruit-circuitpython-bme280 gpiozero rpi-lgpio
The Python Control Script
This script reads the BME280 sensor every 5 seconds. If the temperature exceeds 28°C, it triggers the exhaust fan relay. If it drops below 18°C, it triggers the heater relay. It includes robust error handling for I2C bus dropouts and clean GPIO teardown.
import time
import sys
import board
import busio
import adafruit_bme280
from gpiozero import OutputDevice
from signal import pause
# --- PIN DEFINITIONS ---
# Using BCM numbering (Physical Pin 11 = GPIO 17)
RELAY_FAN_PIN = 17
RELAY_HEAT_PIN = 27
# --- THRESHOLDS ---
TEMP_HIGH = 28.0 # Celsius
TEMP_LOW = 18.0 # Celsius
def setup_hardware():
"""Initialize I2C bus and GPIO relay pins."""
try:
i2c = busio.I2C(board.SCL, board.SDA)
# BME280 default I2C address is 0x77, Adafruit breakout is often 0x76
try:
sensor = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76)
except ValueError:
sensor = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77)
sensor.sea_level_pressure = 1013.25
except Exception as e:
print(f'CRITICAL: Failed to initialize I2C sensor. Error: {e}')
sys.exit(1)
# active_high=False because most relay modules are active-LOW
# (They trigger when the GPIO pulls to GND)
fan_relay = OutputDevice(RELAY_FAN_PIN, active_high=False, initial_value=False)
heat_relay = OutputDevice(RELAY_HEAT_PIN, active_high=False, initial_value=False)
return sensor, fan_relay, heat_relay
def main():
sensor, fan_relay, heat_relay = setup_hardware()
print('Environmental controller started. Press CTRL+C to exit.')
try:
while True:
temp_c = sensor.temperature
humidity = sensor.humidity
print(f'Temp: {temp_c:.1f}C | Humidity: {humidity:.1f}%')
# Hysteresis logic to prevent relay chatter
if temp_c >= TEMP_HIGH:
fan_relay.on()
heat_relay.off()
print(' -> Exhaust Fan ON')
elif temp_c <= TEMP_LOW:
fan_relay.off()
heat_relay.on()
print(' -> Heater ON')
else:
fan_relay.off()
heat_relay.off()
print(' -> Idle (Both OFF)')
time.sleep(5.0)
except KeyboardInterrupt:
print('\nShutdown signal received. Turning off relays...')
except OSError as e:
print(f'\nI2C Bus Error during read: {e}. Check wiring.')
finally:
fan_relay.off()
heat_relay.off()
fan_relay.close()
heat_relay.close()
print('GPIO cleaned up. Exiting.')
if __name__ == '__main__':
main()
Debugging: When the I2C Bus or GPIO Fails
Hardware interaction in Python rarely works perfectly on the first compile. Here are the exact error strings you will encounter and how to fix them.
Error 1: ValueError: No I2C device at address: 0x76
Ranked Causes:
- I2C not enabled: You skipped
raspi-config. Runsudo raspi-configand enable I2C. - Address mismatch: The sensor is at 0x77. The code above handles this via the try/except block, but if you are using a raw script, verify the address with
i2cdetect -y 1. - SDA/SCL swapped: You wired Pin 3 to SCK and Pin 5 to SDI. Swap them.
Error 2: FileNotFoundError: [Errno 2] No such file or directory: '/dev/i2c-1'
Ranked Causes:
- Kernel module missing: The
i2c-devmodule isn't loaded. Fix it temporarily withsudo modprobe i2c-devor permanently by addingi2c-devto/etc/modules. - Wrong bus number: You are using a Pi Compute Module or an older Pi 1 where the bus is
/dev/i2c-0. Change the bus index in your I2C initialization.
Error 3: gpiozero.exc.PinFactoryFallback: Falling back from rpigpio
Ranked Causes:
- Missing lgpio backend: On 64-bit Bookworm,
RPi.GPIOis broken. You must install the compatibility layer:pip install rpi-lgpio. - Virtual environment isolation: You installed
rpi-lgpioglobally but are running the script inside avenvthat doesn't have it installed.
- Run
i2cdetect -y 1: If the grid is empty, your hardware wiring or I2C enablement is wrong. Stop debugging Python and fix the hardware. - Multimeter the rails: Measure voltage between Pin 1 and Pin 6 (should be ~3.3V) and Pin 2 and Pin 6 (should be ~5.1V). If Pin 1 reads 0V, your Pi's polyfuse or power supply is failing.
- Check the VCC/JDVCC jumper: If the relay clicks but the Pi reboots or throws GPIO errors, the 5V coil noise is backfeeding into the logic rail. Ensure the jumper is removed and JDVCC is on 5V.
Extending or Simplifying the Build
How to Simplify: If you only want to log data for a CircuitPython sensor tutorial and don't need physical switching, drop the relay module entirely. Replace the OutputDevice logic with a simple CSV write operation using Python's built-in csv library. This reduces the hardware cost to under $20 and eliminates all mains-voltage risks.
How to Extend: To integrate this into a smart home, add the paho-mqtt library. Inside the while True loop, publish the temp_c and humidity variables to an MQTT broker (like Mosquitto or Home Assistant). You can then move the threshold logic out of the Python script and into Home Assistant automations, turning the Pi into a dumb sensor node rather than a local controller.
FAQ: Python and Raspberry Pi Projects
What are the best python and raspberry pi projects for beginners?
The best beginner projects isolate one variable at a time. Start with a simple GPIO button input using gpiozero, then move to a single I2C sensor (like the BME280 or BME680), and finally combine inputs and outputs (like this relay controller). Avoid projects that require simultaneous camera processing and high-speed motor control until you understand Linux process threading.
How do I debug I2C errors in python and raspberry pi projects?
Always drop down to the Linux command line first. Python libraries abstract the hardware, which hides the root cause. Use i2cdetect -y 1 to verify physical connectivity. If the device shows up as UU in the grid, it means a kernel driver has already claimed the device, and your Python script will be blocked from accessing it. You'll need to blacklist the conflicting driver in /boot/firmware/config.txt.
Can I use MicroPython instead of CPython for python and raspberry pi projects?
While MicroPython is excellent for ESP32 and Pico boards, it is not the standard for full-sized Raspberry Pi single-board computers. The Pi 4 and Pi 5 have the RAM and processing power to run full CPython (standard Python 3.11+), which gives you access to the massive PyPI ecosystem, including numpy, paho-mqtt, and requests. Stick to CPython for Pi, and reserve MicroPython for microcontrollers.
How do I run python and raspberry pi projects automatically on boot?
The most robust method in modern Raspberry Pi OS is using systemd. Create a service file at /etc/systemd/system/enviro.service. Define the ExecStart path pointing to your virtual environment's Python binary (e.g., /home/pi/enviro_controller/bin/python /home/pi/script.py). Enable it with sudo systemctl enable enviro.service. Avoid using rc.local or .bashrc, as they lack proper process management and will leave orphaned GPIO states if the script crashes.






