|

Home Automation with Raspberry Pi Zero – Complete Step-by-Step Guide

Introduction

In this tutorial, we’ll build a home-automation controller using a Raspberry Pi Zero W, two MOSFET switches, and a minimal Flask web console.
By the end, you’ll be able to toggle devices ON/OFF from your phone or laptop over Wi-Fi. We’ll keep the circuit clean, the power safe, and the code simple so you can customize it later.


Components You’ll Need

  • Raspberry Pi Zero W (40-pin header soldered)
  • 12 V DC adapter for your loads
  • 2 × logic-level N-MOSFETs (e.g., AO3400A, IRLZ44N, FQP30N06L)
  • 2 × flyback diodes (1N4007/SS14) for inductive loads (relays, solenoids, motors)
  • Gate resistors: 100–330 Ω (one per MOSFET)
  • Gate pulldowns: 100 kΩ (one per MOSFET)
  • 5 V supply for the Pi: USB 5 V or TSR 1-2450 switching regulator (drop-in 7805 replacement)
  • Wires, screw terminals, your 12 V devices (lamp, pump, lock, fan, etc.)

Why Raspberry Pi Zero W

  • Tiny, affordable, and has built-in Wi-Fi
  • Runs Python + Flask easily for a web UI
  • Perfect for small automations without cloud complexity

Circuit

Circuit – Full Explanation

The circuit is simple and reliable. We use two low-side N-MOSFET switches controlled by the Pi’s GPIO pins.

  • Each device connects from +12 V → Device → MOSFET Drain.
  • The MOSFET Source goes to Ground.
  • The MOSFET Gate is driven by a Pi GPIO pin through a 100–330 Ω resistor.
  • A 100 kΩ pulldown from Gate to Ground keeps the MOSFET OFF at boot.
  • For inductive loads (relay/solenoid/motor), add a flyback diode across the device: cathode to +12 V, anode to the device/MOSFET side.
  • Common ground is mandatory: connect Pi GND to the 12 V supply negative so the gate signal has the right reference.

Behavior: When the Pi drives the gate HIGH, the MOSFET turns ON and completes the path to ground → the device receives power from 12 V and turns ON.
When the gate goes LOW, the device turns OFF.

Pin Map (BCM → Physical Header Pin)

FunctionBCMPhysical Pin
MOSFET A Gate2316
MOSFET B Gate2418
Ground (any one)6 / 9 / 14 / 20 / 25 / 30 / 34 / 39
5 V (Pi rail)2 / 4

We use BCM numbering in the code. The physical positions help if you wire from a loose Pi.

Power (Speakable Summary)

We have two rails:

  • 12 V for the devices
  • 5 V for the Raspberry Pi

Power the Pi in one of two ways:

  1. A good 5 V USB supply into the Pi, or
  2. A switching regulator like TSR 1-2450 stepping 12 V down to 5 V.
    Avoid a linear 7805 from 12 V; it overheats at Pi currents.

If you might use USB and the 5 V regulator at the same time, isolate them—add a series Schottky diode from the regulator to the Pi 5 V rail (or use an ideal-diode/power-mux IC) to prevent back-feeding.

Voltage Regulator (Speakable Summary)

The TSR 1-2450 is a drop-in switching replacement for 7805. It delivers a cool, efficient 5 V up to 1 A from 9–12 V input.
Place a 10–22 µF capacitor near VIN and a 22–47 µF capacitor near the 5 V output for stability during Wi-Fi spikes.
If you plan heavy 5 V USB devices, consider a 1.5–2 A regulator.

GPIO Voltage (Speakable Summary)

Pi GPIO is 3.3 V logic. Pick MOSFETs that turn fully ON at Vgs ≈ 3.3 V with low Rds(on) (e.g., AO3400A, IRLZ44N, FQP30N06L).
Always include the gate series resistor and the 100 kΩ pulldown for clean, predictable switching.


Advantage of Using a PCB

A PCB gives you:

  • Neat screw terminals and proper footprints
  • Clean labeling and consistent wiring
  • Space for fuses/TVS/protection
  • A robust build that fits in an enclosure
    Prototype on a breadboard first, then move the same netlist to PCB for reliability.

Setting up the Pi (Brief)

  • Flash Raspberry Pi OS (Bookworm), boot, connect Wi-Fi
  • Enable SSH if needed
  • For a full imaging/first-boot guide, see my earlier video where everything is shown step-by-step

Initial Testing of GPIO Pins (Simple Code)

Before the web UI, test that GPIO 23 and 24 switch correctly.

File: simple_on_off_test.py

# simple_on_off_test.py
import time
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(23, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(24, GPIO.OUT, initial=GPIO.LOW)

# Turn ON GPIO 23 for 2s
GPIO.output(23, GPIO.HIGH)
time.sleep(2)
GPIO.output(23, GPIO.LOW)

# Turn ON GPIO 24 for 2s
GPIO.output(24, GPIO.HIGH)
time.sleep(2)
GPIO.output(24, GPIO.LOW)

GPIO.cleanup()

Run with:

sudo python3 simple_on_off_test.py

Install Libraries (Option A — apt for Pi Zero W)

On Bookworm, use apt for a smooth experience on the Zero W:

sudo apt update
sudo apt install -y python3-rpi.gpio python3-flask

Home Automation Web Console (Flask)

Now we’ll create a tiny web console to toggle the two channels from any browser on your LAN.

File: home_automation.py

#!/usr/bin/env python3
# home_automation_v2.py — 6 devices (3 rows × 2 cols)

import atexit
from datetime import datetime
from flask import Flask, render_template_string, jsonify, request, abort
import RPi.GPIO as GPIO

TITLE = "RootSaid • Home Automation"
BRAND = "RootSaid"
SITE_URL = "https://rootsaid.com"

# Edit names/pins to match your PCB (BCM numbering)
DEVICES = [
    {"pin": 23, "name": "Light 1 — GPIO 23"},
    {"pin": 24, "name": "Fan 1 — GPIO 24"},
    {"pin": 25, "name": "Device 3 — GPIO 25"},
    {"pin": 26, "name": "Device 4 — GPIO 26"},
    {"pin": 17, "name": "Device 5 — GPIO 17"},
    {"pin": 27, "name": "Device 6 — GPIO 27"},
]

# --- GPIO setup ---
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
for d in DEVICES:
    GPIO.setup(d["pin"], GPIO.OUT, initial=GPIO.LOW)

state = {d["pin"]: False for d in DEVICES}

def set_pin(pin: int, on: bool):
    GPIO.output(pin, GPIO.HIGH if on else GPIO.LOW)
    state[pin] = on

@atexit.register
def _cleanup():
    for d in DEVICES:
        GPIO.output(d["pin"], GPIO.LOW)
    GPIO.cleanup()

app = Flask(__name__)

INDEX_HTML = '''
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>{{ title }}</title>
  <style>
    :root{--bg:#0e0f12;--card:#181b22;--muted:#9aa1ac;--text:#e6e9ef;
          --ok:#00d18f;--bad:#e74c3c;--accent:#007aff;}
    *{box-sizing:border-box} html,body{margin:0;height:100%}
    body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;color:var(--text);background:var(--bg)}
    .wrap{max-width:980px;margin:0 auto;padding:24px}
    .top{display:flex;justify-content:space-between;align-items:center;gap:12px}
    h1{margin:0;font-size:clamp(20px,3vw,28px)}
    .muted{color:var(--muted)} a{color:var(--accent);text-decoration:none}
    /* Fixed 2 columns -> 3 rows (with 6 items) */
    .grid{display:grid;grid-template-columns:repeat(2,minmax(280px,1fr));gap:16px;margin-top:16px}
    @media (max-width:640px){ .grid{grid-template-columns:1fr} }
    .card{background:var(--card);border:1px solid #2a2d36;border-radius:14px;padding:16px}
    .card h3{margin:0 0 6px}
    .status{margin-top:2px}.dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:8px}
    .on{background:var(--ok)} .off{background:var(--bad)}
    .row{display:flex;gap:10px;margin-top:12px}
    .btn{flex:1;border:0;border-radius:10px;padding:12px 14px;color:#fff;font-weight:600;cursor:pointer}
    .onb{background:#0ab685} .offb{background:#c8453b}
    footer{margin-top:18px;font-size:13px;color:var(--muted);display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap}
  </style>
</head>
<body>
  <div class="wrap">
    <div class="top">
      <div>
        <h1>{{ title }}</h1>
        <div class="muted">Control panel for your Raspberry Pi Zero W • Powered by {{ brand }}</div>
      </div>
      <div><a href="{{ site_url }}" target="_blank" rel="noopener">Visit {{ brand }}</a></div>
    </div>

    <div id="cards" class="grid">
      {% for d in devices %}
      <div class="card" data-pin="{{ d.pin }}">
        <h3>{{ d.name }}</h3>
        <div class="status">
          <span class="dot {{ 'on' if state[d.pin] else 'off' }}"></span>
          <span class="label">{{ 'ON' if state[d.pin] else 'OFF' }}</span> • GPIO {{ d.pin }}
        </div>
        <div class="row">
          <button class="btn onb"  onclick="send({{ d.pin }}, 'on')">Turn ON</button>
          <button class="btn offb" onclick="send({{ d.pin }}, 'off')">Turn OFF</button>
        </div>
      </div>
      {% endfor %}
    </div>

    <footer>
      <div>© {{ year }} {{ brand }} • <a href="{{ site_url }}" target="_blank">rootsaid.com</a></div>
      <div>Device: {{ request.host }}</div>
    </footer>
  </div>

<script>
async function send(pin, mode){
  try{
    const r = await fetch('/api/toggle', {
      method:'POST', headers:{'Content-Type':'application/json'},
      body: JSON.stringify({pin:pin, mode:mode})
    });
    if(!r.ok) throw new Error('HTTP '+r.status);
    await refresh();
  }catch(e){ alert('Failed: '+e.message); }
}
async function refresh(){
  const r = await fetch('/api/state'); if(!r.ok) return;
  const data = await r.json();
  document.querySelectorAll('.card').forEach(card=>{
    const pin = Number(card.dataset.pin);
    const on = !!data.state[pin];
    const dot = card.querySelector('.dot');
    const label = card.querySelector('.label');
    dot.classList.toggle('on', on);
    dot.classList.toggle('off', !on);
    label.textContent = on ? 'ON' : 'OFF';
  });
}
setInterval(refresh, 2000);
</script>
</body>
</html>
'''

@app.get("/")
def index():
    return render_template_string(
        INDEX_HTML, title=TITLE, brand=BRAND, site_url=SITE_URL,
        devices=DEVICES, state=state, year=datetime.now().year, request=request
    )

@app.get("/api/state")
def api_state():
    return jsonify({"ok": True, "state": state})

@app.post("/api/toggle")
def api_toggle():
    data = request.get_json(silent=True) or {}
    pin = data.get("pin"); mode = data.get("mode")
    pins = [d["pin"] for d in DEVICES]
    if pin not in pins or mode not in ("on","off"):
        return abort(400)
    set_pin(pin, on=(mode=="on"))
    return jsonify({"ok": True, "pin": pin, "on": state[pin]})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000, debug=False)

Run it:

sudo python3 home_automation.py

Then open:

http://<your-pi-ip>:8000

How It Works?

  • We’ll create a small Flask web server that shows two buttons for GPIO 23 and GPIO 24. When you click ON, the app sets that pin HIGH; when you click OFF, it sets the pin LOW. The page refreshes to show the current state.
  • First, we import Flask and RPi.GPIO.
  • We define our pin list as [23, 24].
  • We set the GPIO mode to BCM, and configure those pins as outputs, initially LOW.
  • We keep a simple state dictionary to display ON or OFF in the web page.
  • The HTML is embedded right in the script, so there’s no extra template files.
  • The route / renders the page. The route /toggle/<pin>/<mode> receives a form post, flips the pin, updates the state, and redirects back to the main page.
  • On exit, we force pins LOW and cleanup so nothing stays stuck ON.

Troubleshooting

  • Browser can’t reach the Pi? Make sure you use http:// (not https), disable VPN/Proxy/Private Relay, or test via SSH tunnel:
    ssh -L 8000:localhost:8000 pi@<pi-ip> → open http://localhost:8000.
  • Devices not switching? Check common ground, gate resistors, pulldowns, and diode orientation.
  • Power issues? Use a switching regulator, not a 7805 from 12 V. Add bulk caps near the regulator.

Safety Notes

  • For mains AC, only use proper relay modules with isolation and follow local electrical codes.
  • Add a fuse on your 12 V line if your load can draw high current.
  • Keep wires short and connections tight.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *