The most reliable baseline for a DIY raspberry pi alarm system is a Raspberry Pi 5 (4GB) running Python with the gpiozero and paho-mqtt libraries, utilizing HC-SR501 PIR sensors for spatial detection and MC-38 magnetic reed switches for perimeter breach detection. Unlike cloud-dependent commercial hubs, this hardwired approach processes logic locally and pushes state changes via MQTT, ensuring your perimeter remains monitored even during internet outages.

Difficulty: Intermediate (Requires basic GPIO wiring and Linux command line familiarity)
Time to Build: 90 minutes
Target Board: Raspberry Pi 5 (4GB) running Raspberry Pi OS (Bookworm or newer)

Project Verdict & Sensor Decision Path

Before ordering parts, you need to match your sensor topology to your physical space. Commercial alarm panels use complex resistor networks to detect wire cuts (EOL resistors), but for a DIY microcontroller build, we rely on Normally Closed (NC) magnetic loops and active-HIGH PIR outputs. Use the decision tree below to finalize your bill of materials.

Security Requirement Sensor Option A Sensor Option B Concrete Pick (Default)
Door/Window Perimeter MC-38 Magnetic Reed (NC) Hall Effect Sensor (Analog) MC-38 (Binary, immune to analog drift)
Room Motion Detection HC-SR501 PIR (Infrared) RCWL-0516 (Microwave Radar) HC-SR501 (Does not trigger through drywall)
Alert Routing Local MQTT Broker (Mosquitto) Direct IFTTT Webhook MQTT (Integrates natively with Home Assistant)

Hardware Spec Sheet & GPIO Pin Mapping

The Raspberry Pi 5 requires a robust power supply, especially when driving 5V sensors alongside the main SoC. Do not use a standard 5V/3A phone charger; the Pi 5 will throttle and drop GPIO voltage under load.

Bill of Materials (2026 Pricing)

  • Microcontroller: Raspberry Pi 5 (4GB RAM) - ~$60.00
  • Power Supply: Official Raspberry Pi 27W USB-C PD (5V/5A) - ~$12.00
  • Motion Sensor: HC-SR501 PIR Motion Sensor Module - ~$2.50
  • Door Sensor: MC-38 Normally Closed (NC) Magnetic Reed Switch - ~$1.50
  • Wiring: 22 AWG stranded hook-up wire and a Pi GPIO breakout board with ribbon cable - ~$10.00

Pin Mapping Table

We use physical pins mapped to Broadcom (BCM) GPIO numbers. Always reference the official Raspberry Pi GPIO Pinout before applying power.

Component Component Pin Pi 5 BCM GPIO Pi Physical Pin Notes
HC-SR501 PIR VCC 5V Power Pin 2 or 4 Must be 5V for stable internal regulation
HC-SR501 PIR OUT GPIO 17 Pin 11 Outputs ~3.3V when HIGH (safe for Pi)
HC-SR501 PIR GND Ground Pin 9 Common ground required
MC-38 Reed Switch Wire 1 GPIO 27 Pin 13 Internal pull-up enabled in software
MC-38 Reed Switch Wire 2 Ground Pin 14 Completes the NC circuit
Bench Note on the HC-SR501: The HC-SR501 has an onboard voltage regulator. If you power it with 5V, the OUT pin will output roughly 3.3V when triggered, which is perfectly safe for the Pi's 3.3V logic limits. If you attempt to power it directly from the Pi's 3.3V pin, the sensor will behave erratically and fail to detect motion. Always power the PIR from 5V.

Step-by-Step Hardwired Assembly

Follow these steps to physically wire the perimeter and spatial sensors. Ensure the Pi is completely powered down and unplugged during assembly.

  1. Mount the Breakout Board: Connect the GPIO ribbon cable to the Pi 5 and the breakout board. This prevents accidental shorting of the 5V rail to adjacent data pins.
  2. Wire the MC-38 Reed Switch (NC Loop): Connect one wire of the MC-38 to Physical Pin 13 (GPIO 27) and the other to Physical Pin 14 (GND). Security logic: We use the Normally Closed configuration. When the door is closed, the magnet keeps the switch closed, pulling GPIO 27 to GND (LOW). If the door opens—or if a burglar cuts the wire—the circuit breaks, and the internal pull-up resistor pulls the pin HIGH, triggering the alarm. This mimics commercial EOL (End of Line) supervision.
  3. Wire the HC-SR501 PIR: Connect the VCC pin to Physical Pin 2 (5V), GND to Physical Pin 9, and the OUT pin to Physical Pin 11 (GPIO 17).
  4. Calibrate the PIR Potentiometers: On the back of the HC-SR501, locate the two orange trim pots. Turn the "Delay Time" pot fully counter-clockwise (minimum ~3 seconds). Turn the "Sensitivity" pot to the 12 o'clock position. Set the jumper pin to "Single Trigger" (H) mode so it doesn't continuously re-trigger the script.
  5. Verify with a Multimeter: Before booting the Pi, set your multimeter to continuity mode. Probe the MC-38 wires; it should beep when the magnet is adjacent and go silent when pulled away. Probe the PIR OUT to GND; it should read open circuit.

Python Alarm Logic with MQTT and Error Handling

This script targets Raspberry Pi OS (Bookworm or later). It uses gpiozero for hardware abstraction and the Eclipse Paho MQTT Python Client to publish state changes to a broker (like Mosquitto running on a local Home Assistant server).

Install dependencies first:
sudo apt update && sudo apt install python3-gpiozero python3-pip -y
pip3 install paho-mqtt --break-system-packages

#!/usr/bin/env python3
"""
Raspberry Pi Alarm System Node
Target: Raspberry Pi 5 (4GB) / Raspberry Pi OS Bookworm
Dependencies: gpiozero, paho-mqtt
"""

import time
import logging
import signal
import sys
from gpiozero import MotionSensor, Button
from gpiozero.exc import BadPinFactory
import paho.mqtt.client as mqtt

# --- Configuration ---
MQTT_BROKER = "192.168.1.50"  # Replace with your local Mosquitto broker IP
MQTT_PORT = 1883
MQTT_TOPIC_PIR = "home/alarm/zone1/motion"
MQTT_TOPIC_DOOR = "home/alarm/zone1/door"

# BCM GPIO Pin Definitions
PIR_GPIO = 17
DOOR_GPIO = 27

# Logging setup
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s"
)

# --- Hardware Initialization ---
try:
    # PIR is active HIGH. 
    pir = MotionSensor(PIR_GPIO, queue_len=1, threshold=0.5)
    
    # Door sensor is Normally Closed (NC). 
    # When closed (magnet near), circuit completes to GND -> reads LOW (0V).
    # When open, internal pull_up brings it HIGH (3.3V).
    # We invert the logic in the callback so 'True' means 'Open/Breached'.
    door = Button(DOOR_GPIO, pull_up=True, active_state=False)
    
except BadPinFactory as e:
    logging.critical(f"GPIO initialization failed: {e}. Are you running on a Pi?")
    sys.exit(1)
except Exception as e:
    logging.critical(f"Unexpected hardware error: {e}")
    sys.exit(1)

# --- MQTT Setup ---
def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code == 0:
        logging.info("Connected to MQTT Broker successfully.")
        # Publish online status
        client.publish("home/alarm/node/status", "online", retain=True)
    else:
        logging.error(f"MQTT Connection failed with code: {reason_code}")

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="pi_alarm_node")
client.on_connect = on_connect
client.will_set("home/alarm/node/status", "offline", retain=True)

def connect_mqtt():
    try:
        client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60)
        client.loop_start()
    except ConnectionRefusedError as e:
        logging.error(f"MQTT Broker unreachable: {e}. Running in local-only mode.")

# --- Event Callbacks ---
def pir_motion_detected():
    logging.warning("ALARM: Motion detected in Zone 1!")
    try:
        client.publish(MQTT_TOPIC_PIR, "ON", retain=False)
    except Exception as e:
        logging.error(f"MQTT publish failed: {e}")

def pir_motion_stopped():
    logging.info("Zone 1 motion cleared.")
    try:
        client.publish(MQTT_TOPIC_PIR, "OFF", retain=False)
    except Exception as e:
        logging.error(f"MQTT publish failed: {e}")

def door_opened():
    logging.warning("ALARM: Perimeter breached! Door opened or wire cut.")
    try:
        client.publish(MQTT_TOPIC_DOOR, "ON", retain=False)
    except Exception as e:
        logging.error(f"MQTT publish failed: {e}")

def door_closed():
    logging.info("Zone 1 door secured.")
    try:
        client.publish(MQTT_TOPIC_DOOR, "OFF", retain=False)
    except Exception as e:
        logging.error(f"MQTT publish failed: {e}")

# --- Main Execution ---
def graceful_exit(signum, frame):
    logging.info("Shutting down alarm node...")
    client.publish("home/alarm/node/status", "offline", retain=True)
    client.loop_stop()
    sys.exit(0)

if __name__ == "__main__":
    signal.signal(signal.SIGINT, graceful_exit)
    signal.signal(signal.SIGTERM, graceful_exit)

    connect_mqtt()

    # Bind callbacks
    pir.when_motion = pir_motion_detected
    pir.when_no_motion = pir_motion_stopped
    door.when_pressed = door_opened   # 'Pressed' in gpiozero means active state (Open circuit here)
    door.when_released = door_closed  # 'Released' means inactive state (Closed circuit)

    logging.info("Raspberry Pi Alarm System active. Monitoring Zone 1...")
    
    # Keep main thread alive
    while True:
        time.sleep(1)

Debugging: First Three Checks and Exact Error Strings

When your raspberry pi alarm system fails to trigger or crashes on boot, do not guess. Follow this ranked diagnostic path.

The First Three Things to Check

  1. Power Supply Voltage Sag: The HC-SR501 draws spikes of current when triggering. If your Pi 5 reboots or the PIR acts dead, measure the 5V rail at the breakout board with a multimeter. If it reads below 4.8V under load, your power supply is inadequate or your jumper wires are too thin (upgrade to 20 AWG for the power rails).
  2. GPIO Permissions: If running the script via a cron job or systemd service, ensure the executing user is in the gpio and dialout groups. Run sudo usermod -aG gpio,dialout $USER and reboot.
  3. MQTT Broker Reachability: Verify the Mosquitto broker isn't blocking the Pi's IP. From the Pi terminal, run mosquitto_pub -h 192.168.1.50 -t "test" -m "hello". If it times out, check the broker's allow_anonymous setting or firewall rules.

Exact Error Strings and Ranked Causes

Error 1: gpiozero.exc.BadPinFactory: Unable to load any default pin factory!

  • Cause A (Most Likely): You are running the script inside a Docker container or WSL environment without passing the --device /dev/gpiomem flag.
  • Cause B: The RPi.GPIO or lgpio underlying libraries are missing or corrupted. Fix via sudo apt install python3-lgpio.

Error 2: ConnectionRefusedError: [Errno 111] Connection refused (thrown during client.connect())

  • Cause A (Most Likely): The Mosquitto broker service is stopped on the target machine. Check with sudo systemctl status mosquitto.
  • Cause B: Mosquitto 2.0+ defaults to blocking external connections without a listener configured. Add listener 1883 and allow_anonymous true to your mosquitto.conf file and restart the service.

Error 3: RuntimeWarning: This channel is already in use, continuing anyway.

  • Cause A: A previous instance of the Python script crashed and didn't release the GPIO pins. The gpiozero library usually handles cleanup, but if the Pi hard-locked, run sudo killall python3 to clear hung processes before restarting.

Extending or Simplifying the Build

Once the baseline perimeter and motion detection are stable, you can scale the system to match your specific deployment needs.

How to Simplify (No Local Broker)

If setting up a Mosquitto broker and Home Assistant is overkill for your use case, strip the MQTT logic entirely. Replace the client.publish() calls in the Python script with a direct HTTP POST request to an IFTTT Webhook or a Telegram Bot API endpoint using the requests library. This reduces the system to a standalone Pi that pushes alerts directly to the cloud, though it sacrifices local network resilience.

How to Extend (Visual Verification)

To add visual verification without relying on continuous recording, integrate a Raspberry Pi Camera Module 3. Use the libcamera-still command-line tool triggered inside the pir_motion_detected() callback. Save the image to a local /tmp directory and use an MQTT binary payload to push the base64-encoded image to your dashboard. This provides definitive proof of intrusion, eliminating false alarms caused by pets or HVAC drafts triggering the PIR sensor.

For multi-zone scaling, transition from direct GPIO wiring to an MCP23017 I2C GPIO Expander. This chip allows you to wire up to 16 additional NC magnetic reed switches over just four wires (VCC, GND, SDA, SCL), keeping your physical wiring harness clean and manageable as you expand the raspberry pi alarm system to cover an entire property.