Project: Smart Home Automation
This project builds a real smart home controller: ESP32 devices sense temperature and occupancy, a Raspberry Pi acts as the hub, MQTT connects everything, and a web dashboard lets you monitor and control appliances remotely.
System Architecture
ESP32 Node (Bedroom) ESP32 Node (Living Room)
├── DHT22 (temp/humidity) ├── DHT22
├── PIR (motion) ├── PIR
└── Relay (AC/Fan control) ├── Relay (lights)
│ └── Relay (TV power)
│ MQTT (local WiFi) │
▼ ▼
Raspberry Pi 4 (Hub)
├── Mosquitto MQTT Broker
├── Python Backend (automation rules)
├── SQLite / InfluxDB (history)
└── Flask Web Dashboard
│
│ HTTPS / WS
▼
Mobile/Web Browser
├── Live readings
├── Manual controls
└── Schedule editorDevice Firmware (ESP32 / MicroPython)
python
# main.py on ESP32 node
import dht, utime, network, ujson
from machine import Pin, Timer
from umqtt.simple import MQTTClient
# ── Configuration ──────────────────────────────────
from config import WIFI_SSID, WIFI_PASSWORD, MQTT_BROKER, DEVICE_ID, ROOM
# ── Hardware setup ─────────────────────────────────
dht_sensor = dht.DHT22(Pin(4))
pir_sensor = Pin(5, Pin.IN)
relay_ac = Pin(26, Pin.OUT, value=1) # Active LOW, start OFF
relay_fan = Pin(27, Pin.OUT, value=1)
TELEMETRY_TOPIC = f"home/{ROOM}/telemetry"
COMMAND_TOPIC = f"home/{ROOM}/commands"
STATUS_TOPIC = f"home/{ROOM}/status"
# ── State ──────────────────────────────────────────
state = {
"ac_on": False,
"fan_on": False,
"occupancy": False,
"last_motion": 0,
"temperature": None,
"humidity": None,
}
# ── MQTT callback ──────────────────────────────────
def on_command(topic, msg):
"""Handle commands from the hub."""
cmd = ujson.loads(msg)
action = cmd.get("action")
if action == "SET_AC":
state["ac_on"] = cmd["value"]
relay_ac.value(0 if state["ac_on"] else 1) # Active LOW
elif action == "SET_FAN":
state["fan_on"] = cmd["value"]
relay_fan.value(0 if state["fan_on"] else 1)
elif action == "GET_STATUS":
publish_status()
print(f"Command executed: {cmd}")
def publish_status():
status = ujson.dumps({
"device_id": DEVICE_ID,
"room": ROOM,
"ac_on": state["ac_on"],
"fan_on": state["fan_on"],
"occupancy": state["occupancy"],
"temperature": state["temperature"],
"humidity": state["humidity"],
"timestamp": utime.time()
})
mqtt.publish(STATUS_TOPIC, status, retain=True)
# ── WiFi ───────────────────────────────────────────
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
while not wlan.isconnected():
utime.sleep_ms(500)
print(f"WiFi: {wlan.ifconfig()[0]}")
# ── MQTT ───────────────────────────────────────────
mqtt = MQTTClient(
client_id=DEVICE_ID,
server=MQTT_BROKER,
keepalive=60
)
mqtt.set_callback(on_command)
mqtt.connect()
mqtt.subscribe(COMMAND_TOPIC)
# ── Publish online status ──────────────────────────
mqtt.publish(
f"home/{ROOM}/online",
ujson.dumps({"online": True, "device_id": DEVICE_ID}),
retain=True
)
# ── Main loop ──────────────────────────────────────
last_telemetry = 0
TELEMETRY_INTERVAL = 60_000 # ms
while True:
mqtt.check_msg() # Non-blocking MQTT poll
# PIR motion detection
if pir_sensor.value():
state["occupancy"] = True
state["last_motion"] = utime.ticks_ms()
elif utime.ticks_diff(utime.ticks_ms(), state["last_motion"]) > 300_000:
state["occupancy"] = False # No motion for 5 min
# Periodic sensor reading and telemetry
now = utime.ticks_ms()
if utime.ticks_diff(now, last_telemetry) >= TELEMETRY_INTERVAL:
last_telemetry = now
try:
dht_sensor.measure()
state["temperature"] = dht_sensor.temperature()
state["humidity"] = dht_sensor.humidity()
payload = ujson.dumps({
"device_id": DEVICE_ID,
"room": ROOM,
"temperature": state["temperature"],
"humidity": state["humidity"],
"occupancy": state["occupancy"],
"timestamp": utime.time()
})
mqtt.publish(TELEMETRY_TOPIC, payload)
except OSError as e:
print(f"Sensor error: {e}")
utime.sleep_ms(200)Hub Backend (Raspberry Pi / Python)
python
# hub.py — automation rules engine
import paho.mqtt.client as mqtt
import json
import sqlite3
import time
import threading
from datetime import datetime
class SmartHomeHub:
def __init__(self, broker="localhost"):
self.rooms = {} # room → latest state
self.db = self._init_db()
self.rules = [
self.rule_auto_ac,
self.rule_unoccupied_off,
]
self.client = mqtt.Client("smart-hub")
self.client.on_connect = self._on_connect
self.client.on_message = self._on_message
self.client.connect(broker, 1883, 60)
def _init_db(self):
conn = sqlite3.connect("smarthome.db", check_same_thread=False)
conn.execute("""
CREATE TABLE IF NOT EXISTS readings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT,
room TEXT,
device_id TEXT,
temperature REAL,
humidity REAL,
occupancy INTEGER,
ac_on INTEGER,
fan_on INTEGER
)
""")
return conn
def _on_connect(self, c, u, f, rc):
print(f"Hub connected (rc={rc})")
c.subscribe("home/+/telemetry", qos=1)
c.subscribe("home/+/status", qos=1)
def _on_message(self, c, u, msg):
try:
data = json.loads(msg.payload)
room = data.get("room", "unknown")
# Update in-memory state
self.rooms[room] = {**self.rooms.get(room, {}), **data}
# Store in database
self._store(data)
# Evaluate automation rules
for rule in self.rules:
rule(room, data)
except Exception as e:
print(f"Hub error: {e}")
def _store(self, d):
self.db.execute("""
INSERT INTO readings
(timestamp, room, device_id, temperature, humidity, occupancy, ac_on, fan_on)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (
datetime.utcnow().isoformat(),
d.get("room"), d.get("device_id"),
d.get("temperature"), d.get("humidity"),
int(d.get("occupancy", False)),
int(d.get("ac_on", False)),
int(d.get("fan_on", False))
))
self.db.commit()
def send_command(self, room, action, value=None):
payload = json.dumps({"action": action, "value": value})
self.client.publish(f"home/{room}/commands", payload, qos=1)
print(f"Command → {room}: {action}={value}")
# ── Automation Rules ───────────────────────────────
def rule_auto_ac(self, room, data):
"""Auto-enable AC if room temp exceeds 28°C and room is occupied."""
temp = data.get("temperature")
occupied = data.get("occupancy", False)
if temp and occupied and temp > 28:
if not self.rooms[room].get("ac_on"):
print(f"Rule: Auto-AC ON in {room} ({temp}°C)")
self.send_command(room, "SET_AC", True)
elif temp and (not occupied or temp < 25):
if self.rooms[room].get("ac_on"):
print(f"Rule: Auto-AC OFF in {room}")
self.send_command(room, "SET_AC", False)
def rule_unoccupied_off(self, room, data):
"""Turn off all appliances if room has been unoccupied for 10 minutes."""
if not data.get("occupancy"):
room_state = self.rooms.get(room, {})
if room_state.get("ac_on") or room_state.get("fan_on"):
print(f"Rule: Room {room} unoccupied — turning off appliances")
self.send_command(room, "SET_AC", False)
self.send_command(room, "SET_FAN", False)
def run(self):
print("Smart Home Hub running...")
self.client.loop_forever()
if __name__ == "__main__":
hub = SmartHomeHub()
hub.run()Web Dashboard (Flask)
python
# dashboard.py
from flask import Flask, render_template, jsonify, request
from flask_socketio import SocketIO, emit
import paho.mqtt.client as mqtt
import json
import sqlite3
app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*")
rooms_state = {}
# MQTT listener
def setup_mqtt():
c = mqtt.Client("dashboard-listener")
def on_msg(client, ud, msg):
data = json.loads(msg.payload)
room = data.get("room")
if room:
rooms_state[room] = data
socketio.emit("room_update", data) # Push to browsers via WebSocket
c.on_connect = lambda c, u, f, r: c.subscribe("home/#", qos=1)
c.on_message = on_msg
c.connect("localhost", 1883, 60)
c.loop_start()
return c
@app.route('/')
def index():
return render_template('dashboard.html')
@app.route('/api/rooms')
def get_rooms():
return jsonify(rooms_state)
@app.route('/api/command', methods=['POST'])
def send_command():
body = request.json
room = body.get("room")
action = body.get("action")
value = body.get("value")
# Forward to MQTT hub
mqtt_client.publish(f"home/{room}/commands",
json.dumps({"action": action, "value": value}), qos=1)
return jsonify({"ok": True})
@app.route('/api/history/<room>')
def get_history(room):
conn = sqlite3.connect("smarthome.db")
rows = conn.execute("""
SELECT timestamp, temperature, humidity, occupancy
FROM readings WHERE room = ?
ORDER BY timestamp DESC LIMIT 100
""", (room,)).fetchall()
return jsonify([
{"timestamp": r[0], "temperature": r[1],
"humidity": r[2], "occupancy": bool(r[3])}
for r in rows
])
mqtt_client = setup_mqtt()
if __name__ == '__main__':
socketio.run(app, host='0.0.0.0', port=5000)Summary
- ESP32 nodes publish sensor data and accept relay commands over MQTT
- A Raspberry Pi hub runs automation rules, stores history, and serves the web dashboard
- Automation rules evaluate each reading and send commands — no cloud involved
- WebSockets (via flask-socketio) push real-time updates to the browser without polling
- The
STATUS_TOPICwithretain=Trueensures new dashboard sessions immediately see device state - This architecture scales from a single room to a full building — add more ESP32 nodes without changing the hub
Next, we'll build an environmental monitoring station that uploads data to the cloud.