The Verdict: Which Raspberry Pi and Relay Module to Use

Before buying parts, you need to match the compute module to the physical constraints of your garage ceiling. A garage door opener environment is harsh: it experiences heavy vibration from the motor, wide temperature swings, and dusty conditions. You do not need a powerhouse computer to close a dry-contact switch; you need reliability and a small footprint.

Decision Path: Selecting the Right Board
Scenario / Constraint Recommended Board Why
Need local HDMI screen or multiple USB cameras Raspberry Pi 4 Model B (2GB) Full-size ports, dedicated Ethernet, high thermal output.
Low power, headless, tight space inside the motor housing Raspberry Pi Zero 2 W DEFAULT PICK. Quad-core, low idle current, fits in a 2x3 project box.
Need to control >10 relays or run heavy AI vision Raspberry Pi 5 + Custom HAT PCIe bandwidth and high I/O, but overkill for a single door.

The Concrete Pick: For 95% of residential garage door opener integrations, use the Raspberry Pi Zero 2 W paired with a 5V Songle SRD-05VDC-SL-C opto-isolated relay module. The Pi Zero 2 W has enough processing headroom to maintain a stable WiFi and MQTT connection without the thermal throttling issues of the Pi 3, and the Songle relay provides the necessary 10A dry-contact rating to handle the opener's logic board trigger.

Parts List & Wiring Pinout

Difficulty: Intermediate (3/5) | Time: 2 Hours | Cost: ~$28

Spec-Sheet-Table: Exact Components

Component Exact Model / Variant Est. Price (2026)
Microcontroller Raspberry Pi Zero 2 W (with pre-soldered GPIO header) $15.00
Relay Module 5V 1-Channel Opto-isolated (Songle SRD-05VDC-SL-C) $4.50
Power Supply Official Raspberry Pi 27W USB-C Supply (or high-quality 5V 2.5A micro-USB adapter) $8.00
Wiring & Connectors 22 AWG solid core wire, 1/4' insulated female quick-disconnect spades $5.00
Enclosure ABS Plastic Project Box (approx 3.5 x 2.5 x 1.2 inches) $3.00

Pin Mapping Table (BCM Numbering)

The code below targets the Raspberry Pi Zero 2 W using BCM (Broadcom) pin numbering. Do not use physical board pin numbers, as they vary across Pi generations.

Pi Zero 2 W Pin (BCM) Physical Pin # Relay Module Terminal Function
5V (Power) Pin 2 or 4 VCC Powers the relay coil and opto-isolator LED
GND Pin 6, 9, or 14 GND Common ground reference
GPIO 17 Pin 11 IN (Signal) Logic HIGH/LOW to trigger the relay coil

Note: The relay's COM (Common) and NO (Normally Open) screw terminals will wire directly to your garage door opener's wall-button terminals.

Step-by-Step Installation & Safety

⚠️ SAFETY WARNING: Mains vs. Low Voltage. Garage door openers contain lethal 120V/240V AC mains power and high-voltage DC bus capacitors. However, the two terminals meant for the wall push-button are a low-voltage dry contact (typically 12V-24V AC or DC, under 100mA). You must only connect your relay to the push-button terminals. Unplug the main unit from the ceiling outlet before opening the chassis. Verify dead with a multimeter.
  1. Prep the Pi: Flash Raspberry Pi OS Lite (64-bit, headless) onto a high-endurance microSD card. Configure your wpa_supplicant.conf and enable SSH and I2C/SPI if needed. Boot the Pi and ensure it connects to your 2.4GHz WiFi network.
  2. Wire the Control Side: Using 22 AWG wire, connect Pi Pin 2 (5V) to Relay VCC, Pi Pin 6 (GND) to Relay GND, and Pi Pin 11 (GPIO 17) to Relay IN. Double-check polarity; reversing VCC and GND on the relay module will instantly destroy the opto-isolator and may backfeed 5V into the Pi's data line.
  3. Wire the Load Side (Dry Contact): Locate the two screw terminals on the garage door motor board where the existing wall button wires connect. Disconnect the existing wall button wires (or use a multimeter to find the two pins that short together when the physical button is pressed). Crimp 1/4' female spade connectors onto two 22 AWG wires. Connect one wire to the Relay COM terminal and the other to the Relay NO (Normally Open) terminal.
  4. Physical Mounting: Garage door motors vibrate heavily. Do not rely on screw terminals alone for the relay connections; use the crimped spades and secure the Pi and relay inside the ABS project box using nylon standoffs. Mount the box to the ceiling joist or the side of the motor rail, avoiding direct attachment to the vibrating motor housing.
  5. Test the Hardware: Power the Pi. Run a quick terminal command to test the pin: raspi-gpio set 17 op dh (drives high, clicks relay), then raspi-gpio set 17 dl (drives low, releases relay). If the door toggles, your hardware is sound.

Complete Python MQTT Control Code

This script uses the Eclipse Paho MQTT library to listen for a 'PRESS' payload, simulating a momentary button press by holding the relay closed for 0.5 seconds. It includes robust error handling and GPIO cleanup.

Prerequisites: sudo apt install python3-pip python3-rpi.gpio and pip3 install paho-mqtt.

import paho.mqtt.client as mqtt
import RPi.GPIO as GPIO
import time
import sys
import logging

# --- Configuration & Pin Definitions ---
RELAY_PIN = 17          # BCM 17 / Physical Pin 11
PRESS_DURATION = 0.5    # Seconds to hold relay closed (simulates human press)

MQTT_BROKER = '192.168.1.100'
MQTT_PORT = 1883
MQTT_TOPIC_CMD = 'home/garage/door/command'
MQTT_TOPIC_STATE = 'home/garage/door/state'
MQTT_USER = 'mqtt_user'
MQTT_PASS = 'secure_password'

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

# --- GPIO Setup ---
# Using BCM numbering. Set to OUT, default HIGH (relay inactive for active-low modules)
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RELAY_PIN, GPIO.OUT, initial=GPIO.HIGH)

def simulate_button_press():
    '''Closes the relay circuit momentarily to trigger the garage door.'''
    logging.info('Triggering garage door relay...')
    GPIO.output(RELAY_PIN, GPIO.LOW)  # Active LOW for most opto-relays
    time.sleep(PRESS_DURATION)
    GPIO.output(RELAY_PIN, GPIO.HIGH)
    logging.info('Relay released.')

# --- MQTT Callbacks ---
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        logging.info('Connected to MQTT Broker')
        client.subscribe(MQTT_TOPIC_CMD)
        client.publish(MQTT_TOPIC_STATE, 'IDLE', retain=True)
    else:
        logging.error(f'MQTT Connection failed with code {rc}')

def on_message(client, userdata, msg):
    payload = msg.payload.decode('utf-8').strip().upper()
    logging.info(f'Received command: {payload} on topic {msg.topic}')
    
    if payload == 'PRESS' or payload == 'TOGGLE':
        simulate_button_press()
        client.publish(MQTT_TOPIC_STATE, 'TOGGLED', retain=False)
    else:
        logging.warning(f'Unknown command payload: {payload}')

def main():
    client = mqtt.Client(client_id='Pi_Garage_Opener', protocol=mqtt.MQTTv311)
    client.username_pw_set(MQTT_USER, MQTT_PASS)
    client.on_connect = on_connect
    client.on_message = on_message

    try:
        client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60)
        logging.info('Starting MQTT loop...')
        client.loop_forever()
    except KeyboardInterrupt:
        logging.info('Shutdown signal received.')
    except Exception as e:
        logging.critical(f'MQTT or System Error: {e}')
    finally:
        # CRITICAL: Always cleanup GPIO to release hardware locks
        GPIO.cleanup()
        client.disconnect()
        logging.info('GPIO cleaned up and MQTT disconnected. Exiting.')
        sys.exit(0)

if __name__ == '____main__':
    main()

Debugging: 'GPIO Channel Already in Use' & Stuck Relays

When working with Raspberry Pi GPIO and Python, you will inevitably encounter hardware lock issues. If your script crashes or you force-quit it, the OS may retain the pin state.

The Exact Error String

RuntimeWarning: This channel is already in use, continuing anyway. Use GPIO.setwarnings(False) to disable warnings.

Ranked Causes & Fixes

  1. Previous Process Failed to Cleanup (Most Likely): You killed the script using kill -9 or it crashed before reaching GPIO.cleanup(). The Linux gpiod character device still holds the lock. Fix: Run sudo killall python3, then reboot the Pi, or use the raspi-gpio tool to manually release the pin.
  2. Pin Numbering Mismatch: You are using physical pin numbers (BOARD) in your head but BCM in the code, accidentally targeting a pin already reserved by the system (like GPIO 2/3 which have hard pull-ups). Fix: Verify GPIO.setmode(GPIO.BCM) is explicitly declared at the top of your script.
  3. Background Service Conflict: A lingering systemd service or Docker container from a previous smart home project (like Home Assistant Supervised) is polling the GPIO header. Fix: Check running services with systemctl list-units | grep gpio and disable them.

The First Three Things to Check When It Fails

If the MQTT message sends but the door doesn't move, do not rewrite your code. Follow this hardware decision path:

  1. Multimeter Continuity Test: Disconnect the wires from the garage door board. Set your multimeter to continuity/beep mode. Trigger the Python script manually. You should hear a beep for exactly 0.5 seconds. If you don't, the relay is dead or the Pi isn't outputting 3.3V on GPIO 17.
  2. Isolate the Network: Bypass your smart home hub (Node-RED/Home Assistant). Open a terminal on your laptop and run: mosquitto_pub -h 192.168.1.100 -u mqtt_user -P secure_password -t 'home/garage/door/command' -m 'PRESS'. If the door opens, your Pi is fine; your hub's automation logic is broken.
  3. Check the 5V Rail Under Load: The Pi Zero 2 W is notorious for voltage drop if powered by a cheap USB cable. When the relay coil energizes, it draws ~70mA. If your cable has high resistance, the Pi's 5V rail dips below 4.6V, causing the Pi to brownout and the relay to chatter weakly without closing the contact. Measure VCC at the relay screw terminals while triggering.

Extending and Simplifying the Build

Once the basic toggle is working, you have two distinct paths depending on your project goals.

How to Simplify (The 'I Just Want It to Work' Path)

If your only goal is Home Assistant integration and you don't need the Pi for local processing, ditch the Raspberry Pi entirely. Use an ESP32-WROOM-32 development board ($6) flashed with ESPHome. ESPHome handles the WiFi reconnection, MQTT fallback, and GPIO toggling natively via YAML configuration without writing a single line of Python. It boots in 2 seconds and consumes a fraction of the power, making it the superior choice for single-purpose dry-contact switching.

How to Extend (The 'Full Telemetry' Path)

The current build is 'blind'—it sends a pulse but doesn't know if the door actually moved. To fix this, add a wired magnetic reed switch (like the Seco-Larm SM-226L) to the door track. Wire the reed switch to GPIO 27 (Physical Pin 13) with a 10kΩ pull-up resistor to 3.3V. Update the Python script to read GPIO 27. When the magnet passes the sensor, publish CLOSED to the MQTT state topic; when it moves away, publish OPEN. This transforms your setup from a simple remote trigger into a fully aware security sensor, allowing you to set up automations like 'Close the door if it has been OPEN for more than 30 minutes and it is past 10:00 PM.'