If you want to build a reliable home automation Raspberry Pi node to control mains appliances, lighting, or HVAC dampers, the most robust approach is a 4-channel optocoupler relay module controlled via MQTT. The direct answer for your hardware stack: use a Raspberry Pi 5 (4GB) running Raspberry Pi OS Bookworm, a 5V optocoupler relay module with a removable JD-VCC jumper, and Python with the gpiozero and paho-mqtt libraries. This setup provides physical galvanic isolation between your high-voltage loads and the Pi’s sensitive 3.3V logic.

The Decision Path: Picking Your Hardware

Not every home automation project needs a flagship board. Use this decision tree to select the exact board variant for your specific node requirements.

Use Case Recommended Board Why This Pick?
Main Hub (Home Assistant + MQTT Broker + Database) Raspberry Pi 5 (8GB) NVMe PCIe support for fast DB writes; handles heavy Docker containers.
Dedicated Relay Controller (This Guide) Raspberry Pi 5 (4GB) Overkill for just relays, but guarantees zero latency and future-proofing for local vision AI.
Remote Sensor/Single Relay Node (Battery/Solar) Raspberry Pi Zero 2 W Low power draw (~1.2W idle); fits in tiny enclosures; has WiFi built-in.
High-Density I/O (16+ Relays) ESP32-WROOM-32 + I2C Expanders Pi is wasted here. ESP32 handles rapid GPIO toggling and deep sleep better.
Default Recommendation: For this build, we are targeting the Raspberry Pi 5 (4GB) running Raspberry Pi OS Bookworm (64-bit). The code and pin mappings below are specifically validated for the Pi 5's new RP1 southbridge GPIO architecture.

Parts List and Spec Sheet

Do not substitute the power supply or the relay module type. Cheap clones without optocouplers will eventually backfeed 5V into your Pi's 3.3V rail and fry the RP1 chip.

Component Exact Variant / Model Estimated Cost (2026)
Microcontroller Raspberry Pi 5 (4GB RAM) $60.00
Power Supply Official 27W USB-C PD Power Supply (White/Black) $12.00
Relay Module 4-Channel 5V Optocoupler Relay (Songle SRD-05VDC-SL-C) Must have JD-VCC jumper $8.50
Logic Wiring 22 AWG Silicone stranded wire + Dupont headers $5.00
Storage 64GB SanDisk Extreme Pro microSD (A2 rated) $14.00

Pin Mapping and the JD-VCC Isolation Trick

This is where 90% of hobbyists destroy their boards. A standard 4-channel relay module has a jumper labeled JD-VCC. By default, this jumper links the relay coil power (5V) to the optocoupler LED power (which expects 3.3V from the Pi). If you plug a 5V pin into the Pi's 3.3V GPIO, you will kill the board.

The Fix: Remove the JD-VCC jumper. Wire the Pi's 3.3V pin to the module's VCC pin (powering the optocoupler LEDs), and wire an external 5V source (or the Pi's 5V pin) to the JD-VCC header pin (powering the relay coils). This provides true galvanic isolation.

Relay Module Pin Pi 5 Physical Pin Pi 5 BCM GPIO Wire Color (Suggested)
VCC (Optocoupler) 1 (3.3V Power) N/A Orange
GND 6 (Ground) N/A Black
IN1 (Living Room) 11 GPIO 17 Yellow
IN2 (Bedroom Fan) 13 GPIO 27 Green
IN3 (Garden Pump) 15 GPIO 22 Blue
IN4 (HVAC Aux) 16 GPIO 23 Purple
JD-VCC (Coil Power) 2 or 4 (5V Power) N/A Red
Mains Voltage Warning: The load side of the relay (Common, NO, NC) will be switching 120V/240V AC. De-energize the circuit at the breaker, verify dead with a CAT III multimeter, and ensure all mains connections are enclosed in a grounded, fire-retardant junction box. Local electrical codes may require a licensed electrician for permanent mains wiring.

Step-by-Step Wiring Procedure

  1. Prep the Relay Module: Use tweezers to pull the yellow JD-VCC jumper cap off the relay module. Leave it off permanently.
  2. Connect Logic Power: Wire Pi Physical Pin 1 (3.3V) to the relay module's VCC pin. Wire Pi Physical Pin 6 (GND) to the module's GND pin.
  3. Connect Coil Power: Wire Pi Physical Pin 2 (5V) to the relay module's JD-VCC header pin. (If your relay clicks are weak, upgrade this to a dedicated external 5V 2A power supply, tying the external GND to the Pi GND).
  4. Connect GPIO Signals: Wire Physical Pins 11, 13, 15, and 16 to IN1, IN2, IN3, and IN4 respectively.
  5. Boot and Verify: Power on the Pi. Run sudo apt update && sudo apt install python3-gpiozero python3-paho-mqtt to ensure the Bookworm-compatible libraries are installed.

The Python MQTT Control Script

This script targets Raspberry Pi OS Bookworm. It uses gpiozero (which leverages the lgpio backend on Pi 5) and paho-mqtt v2.0. It listens for JSON payloads on the home/relays/set topic.

import paho.mqtt.client as mqtt
from gpiozero import OutputDevice
import json
import time
import logging

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

# Pin Definitions (BCM) - Active Low for most optocoupler relays
RELAYS = {
    "living_light": OutputDevice(17, active_high=False, initial_value=False),
    "bedroom_fan": OutputDevice(27, active_high=False, initial_value=False),
    "garden_pump": OutputDevice(22, active_high=False, initial_value=False),
    "hvac_aux": OutputDevice(23, active_high=False, initial_value=False)
}

MQTT_BROKER = "192.168.1.100"
MQTT_PORT = 1883
MQTT_TOPIC_SUB = "home/relays/set"
MQTT_TOPIC_PUB = "home/relays/status"

def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code == 0:
        logging.info("Connected to MQTT Broker successfully.")
        client.subscribe(MQTT_TOPIC_SUB)
    else:
        logging.error(f"MQTT Connection failed with code: {reason_code}")

def on_message(client, userdata, msg):
    try:
        payload = json.loads(msg.payload.decode('utf-8'))
        device = payload.get("device")
        state = payload.get("state")
        
        if device in RELAYS and state in ["ON", "OFF"]:
            if state == "ON":
                RELAYS[device].on()
                logging.info(f"Activated {device}")
            else:
                RELAYS[device].off()
                logging.info(f"Deactivated {device}")
            
            # Publish status back
            client.publish(MQTT_TOPIC_PUB, json.dumps({"device": device, "state": state}))
        else:
            logging.warning(f"Unknown device or state in payload: {payload}")
    except json.JSONDecodeError:
        logging.error("Received invalid JSON payload.")
    except Exception as e:
        logging.error(f"Unexpected error processing message: {e}")

def main():
    # paho-mqtt v2.0 requires explicit callback API version
    client = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION2, client_id="pi5_relay_node")
    client.on_connect = on_connect
    client.on_message = on_message
    
    try:
        client.connect(MQTT_BROKER, MQTT_PORT, 60)
        client.loop_forever()
    except KeyboardInterrupt:
        logging.info("Shutting down relays and exiting.")
    except ConnectionRefusedError:
        logging.critical("MQTT Broker refused connection. Is Mosquitto running?")
    finally:
        for name, relay in RELAYS.items():
            relay.off()
            relay.close()

if __name__ == "__main__":
    main()

Debugging: Exact Errors and the First Three Checks

When your home automation Raspberry Pi node fails to toggle a load, do not guess. Follow this diagnostic path.

The First Three Things to Check

  1. Is the user in the gpio group? Bookworm restricts GPIO access. Run sudo usermod -aG gpio $USER and reboot. Running scripts with sudo is a security risk and breaks MQTT environment variables.
  2. Is Mosquitto actually listening on the network? By default, Mosquitto 2.0+ only binds to localhost. You must add listener 1883 and allow_anonymous true (or configure passwords) in /etc/mosquitto/conf.d/default.conf.
  3. Did you remove the JD-VCC jumper? If the relay clicks but the Pi reboots randomly under load, 5V is backfeeding into the 3.3V rail. Remove the jumper immediately.

Ranked Causes for Specific Error Strings

Error 1: lgpio.error: 'gpiochip4' is not accessible

  • Cause A (Most Likely): You are using the legacy RPi.GPIO library on a Pi 5. RPi.GPIO does not support the Pi 5's RP1 chip. Fix: Switch to gpiozero as shown in the code above.
  • Cause B: Permissions issue. Fix: Ensure your user is in the gpio group and you have rebooted.

Error 2: ConnectionRefusedError: [Errno 111] Connection refused

  • Cause A (Most Likely): Mosquitto service is dead or bound to 127.0.0.1. Fix: Run sudo systemctl status mosquitto. Check /var/log/mosquitto/mosquitto.log. Add listener 1883 0.0.0.0 to your config.
  • Cause B: Firewall blocking port 1883. Fix: Run sudo ufw allow 1883/tcp.

Extending or Simplifying the Build

Once your base node is stable, you will inevitably want to change the scale. Here is how to adapt the hardware without rewriting your entire home automation architecture.

How to Extend (Scaling to 16+ Channels)

Do not wire 16 individual GPIO pins. The Pi 5's RP1 chip has limits, and wiring becomes a nightmare. Instead, use an MCP23017 I2C GPIO Expander.
The Pick: Buy an Adafruit MCP23017 breakout board (Part #732). Wire it to Pi Physical Pins 3 (SDA) and 5 (SCL). It gives you 16 extra I/O pins over just two wires. You will swap gpiozero.OutputDevice for gpiozero.MCP23017 in the Python script, keeping your MQTT logic completely untouched.

How to Simplify (Downsizing to a Single Remote Switch)

If you only need to control a single distant load (like a gate motor or a detached garage light) and running Ethernet or relying on WiFi stability is an issue, abandon the Pi for that specific node.
The Pick: Use an ESP32-WROOM-32 DevKit v1 running ESPHome. It costs $6, draws a fraction of the power, and integrates directly into Home Assistant via the native API, eliminating the need for a dedicated Python MQTT script on the node itself. Reserve the Pi 5 strictly for the central broker and logic processing.

By keeping your home automation Raspberry Pi focused on centralized logic and using proper galvanic isolation on your GPIO outputs, you build a system that survives power surges, network hiccups, and OS upgrades without taking your house's lighting down with it.