ESP32 IoT Projects · Intermediate
ESP32 Smart Doorbell with Notifications
Build an intelligent doorbell with motion detection, camera simulation, OLED display, and a live web dashboard — all running on your ESP32.
Project Overview
This project turns an ESP32 into a fully-featured smart doorbell. When someone presses the button or triggers the PIR motion sensor, the system plays a chime, activates the camera indicator, logs the event, updates the OLED display, and sends a simulated notification — all while serving a live web dashboard over WiFi.
Perfect for learning how to combine multiple peripherals with WiFi connectivity on the ESP32, while building something genuinely useful for home security.
Key Features
Components Required
Pin Connections & Wiring
Diagram.json (Wokwi)
Copy this into your diagram.json file in Wokwi to instantly load the full circuit.
{
"version": 1,
"author": "ESP32 Smart Doorbell",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-esp32-devkit-v1", "id": "esp", "top": 0, "left": 0, "attrs": {} },
{ "type": "wokwi-pushbutton", "id": "btn1", "top": -124.8, "left": 249.6,
"attrs": { "color": "blue", "label": "DOORBELL" } },
{ "type": "wokwi-pir-motion-sensor", "id": "pir1", "top": -105.49, "left": 345.31, "attrs": {} },
{ "type": "wokwi-led", "id": "red_led", "top": -124.8, "left": 480,
"attrs": { "color": "red", "lightColor": "red", "label": "R" } },
{ "type": "wokwi-resistor", "id": "r1", "top": -67.2, "left": 480, "rotate": 90,
"attrs": { "value": "220" } },
{ "type": "wokwi-led", "id": "green_led", "top": -124.8, "left": 556.8,
"attrs": { "color": "green", "lightColor": "green", "label": "G" } },
{ "type": "wokwi-resistor", "id": "r2", "top": -67.2, "left": 556.8, "rotate": 90,
"attrs": { "value": "220" } },
{ "type": "wokwi-led", "id": "blue_led", "top": -124.8, "left": 633.6,
"attrs": { "color": "blue", "lightColor": "blue", "label": "B" } },
{ "type": "wokwi-resistor", "id": "r3", "top": -67.2, "left": 633.6, "rotate": 90,
"attrs": { "value": "220" } },
{ "type": "wokwi-led", "id": "camera_led", "top": 38.4, "left": 480,
"attrs": { "color": "yellow", "lightColor": "yellow", "label": "Camera" } },
{ "type": "wokwi-resistor", "id": "r4", "top": 96, "left": 480, "rotate": 90,
"attrs": { "value": "220" } },
{ "type": "wokwi-led", "id": "lock_led", "top": 38.4, "left": 556.8,
"attrs": { "color": "white", "lightColor": "white", "label": "Door Lock" } },
{ "type": "wokwi-resistor", "id": "r5", "top": 96, "left": 556.8, "rotate": 90,
"attrs": { "value": "220" } },
{ "type": "wokwi-buzzer", "id": "bz1", "top": 172.8, "left": 518.4,
"attrs": { "volume": "0.5" } },
{ "type": "wokwi-ssd1306", "id": "oled1", "top": 192, "left": 249.6,
"attrs": { "i2cAddress": "0x3C" } },
{ "type": "wokwi-potentiometer", "id": "light1", "top": 86.4, "left": 326.4, "rotate": 180,
"attrs": { "label": "Light Sensor" } }
],
"connections": [
["esp:TX0", "$serialMonitor:RX", "", []],
["esp:RX0", "$serialMonitor:TX", "", []],
["btn1:1.l", "esp:D14", "blue", ["v0"]],
["btn1:2.r", "esp:GND.1", "black", ["v0"]],
["pir1:VCC", "esp:3V3", "red", ["v0"]],
["pir1:GND", "esp:GND.1", "black", ["v0"]],
["pir1:OUT", "esp:D13", "orange", ["v0"]],
["red_led:A", "esp:D26", "red", ["v0"]],
["red_led:C", "r1:1", "red", ["v0"]],
["r1:2", "esp:GND.1", "black", ["v0"]],
["green_led:A", "esp:D27", "green", ["v0"]],
["green_led:C", "r2:1", "green", ["v0"]],
["r2:2", "esp:GND.1", "black", ["v0"]],
["blue_led:A", "esp:D12", "blue", ["v0"]],
["blue_led:C", "r3:1", "blue", ["v0"]],
["r3:2", "esp:GND.1", "black", ["v0"]],
["camera_led:A", "esp:D33", "yellow", ["v0"]],
["camera_led:C", "r4:1", "yellow", ["v0"]],
["r4:2", "esp:GND.2", "black", ["v0"]],
["lock_led:A", "esp:D32", "white", ["v0"]],
["lock_led:C", "r5:1", "white", ["v0"]],
["r5:2", "esp:GND.2", "black", ["v0"]],
["bz1:1", "esp:D25", "purple", ["v0"]],
["bz1:2", "esp:GND.2", "black", ["v0"]],
["oled1:VCC", "esp:3V3", "red", ["h0"]],
["oled1:GND", "esp:GND.2", "black", ["h0"]],
["oled1:SDA", "esp:D21", "blue", ["h0"]],
["oled1:SCL", "esp:D22", "yellow", ["h0"]],
["light1:GND", "esp:GND.2", "black", ["v0"]],
["light1:VCC", "esp:3V3", "red", ["v0"]],
["light1:SIG", "esp:D34", "green", ["v0"]]
],
"dependencies": {}
}
Arduino Code
Adafruit GFX Library and Adafruit SSD1306. WiFi and WebServer are built into the ESP32 Arduino core./* * ESP32 Smart Doorbell with Notifications * MakeMindz.com — IoT Project Series * Features: Doorbell button, PIR motion, OLED, RGB LED, * Buzzer chime, Web dashboard, Visitor log */ #include <WiFi.h> #include <WebServer.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> // ── WiFi Credentials ────────────────────────── const char* ssid = "Wokwi-GUEST"; const char* password = ""; // ── Web Server ───────────────────────────────── WebServer server(80); // ── Pin Definitions ──────────────────────────── #define DOORBELL_BUTTON_PIN 14 // Doorbell push button #define PIR_SENSOR_PIN 13 // PIR motion sensor #define BUZZER_PIN 25 // Doorbell chime #define RED_LED_PIN 26 // RGB Red #define GREEN_LED_PIN 27 // RGB Green #define BLUE_LED_PIN 12 // RGB Blue #define CAMERA_TRIGGER_PIN 33 // Camera simulation LED #define RELAY_DOOR_PIN 32 // Door lock relay #define LIGHT_SENSOR_PIN 34 // ADC light sensor // ── OLED Display ─────────────────────────────── #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // ── System State ─────────────────────────────── struct DoorbellState { int visitorCount; unsigned long lastRingTime; bool motionDetected; bool nightMode; bool doorbellPressed; String lastVisitorTime; int lightLevel; bool cameraActive; bool doorLocked; }; DoorbellState doorbell; // ── Visitor Log ──────────────────────────────── struct VisitorLog { String timestamp; String event; bool notificationSent; }; VisitorLog visitorHistory[20]; int historyIndex = 0; // ── Button Debounce ──────────────────────────── unsigned long lastDebounceTime = 0; const unsigned long debounceDelay = 50; int lastButtonState = HIGH; int buttonState = HIGH; // ── Timing ───────────────────────────────────── unsigned long lastMotionCheck = 0, lastDisplayUpdate = 0; const long motionCheckInterval = 500, displayUpdateInterval = 100; // ── Melodies ─────────────────────────────────── const int melody1[] = {523, 587, 659, 784}; // C D E G const int noteDurations[] = {200, 200, 200, 400}; // ────────────────────────────────────────────── // SETUP // ────────────────────────────────────────────── void setup() { Serial.begin(115200); pinMode(DOORBELL_BUTTON_PIN, INPUT_PULLUP); pinMode(PIR_SENSOR_PIN, INPUT); pinMode(BUZZER_PIN, OUTPUT); pinMode(RED_LED_PIN, OUTPUT); pinMode(GREEN_LED_PIN, OUTPUT); pinMode(BLUE_LED_PIN, OUTPUT); pinMode(CAMERA_TRIGGER_PIN, OUTPUT); pinMode(RELAY_DOOR_PIN, OUTPUT); pinMode(LIGHT_SENSOR_PIN, INPUT); // OLED Init if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("OLED failed")); } else { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println("SMART DOORBELL"); display.println("Initializing..."); display.display(); delay(2000); } // State Init doorbell = {0, 0, false, false, false, "None", 0, false, true}; digitalWrite(RELAY_DOOR_PIN, HIGH); // Start locked // WiFi WiFi.begin(ssid, password); Serial.print("Connecting to WiFi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\n✓ WiFi connected! IP: " + WiFi.localIP().toString()); // Web Routes server.on("/", handleRoot); server.on("/status", handleStatus); server.on("/history", handleHistory); server.on("/ring", handleTestRing); server.on("/unlock", handleUnlock); server.on("/lock", handleLock); server.on("/camera", handleCamera); server.on("/clear", handleClear); server.begin(); Serial.println("✓ Web server started!"); setRGBColor(0, 255, 0); playStartupChime(); delay(500); setRGBColor(0, 0, 0); updateDisplay(); } // ────────────────────────────────────────────── // LOOP // ────────────────────────────────────────────── void loop() { server.handleClient(); unsigned long now = millis(); checkDoorbellButton(); if (now - lastMotionCheck >= motionCheckInterval) { lastMotionCheck = now; checkMotionSensor(); checkLightLevel(); } if (now - lastDisplayUpdate >= displayUpdateInterval) { lastDisplayUpdate = now; updateDisplay(); } } // ── Button Debounce ──────────────────────────── void checkDoorbellButton() { int reading = digitalRead(DOORBELL_BUTTON_PIN); if (reading != lastButtonState) lastDebounceTime = millis(); if ((millis() - lastDebounceTime) > debounceDelay) { if (reading != buttonState) { buttonState = reading; if (buttonState == LOW) handleDoorbellRing(); } } lastButtonState = reading; } // ── Doorbell Ring Handler ────────────────────── void handleDoorbellRing() { doorbell.doorbellPressed = true; doorbell.visitorCount++; doorbell.lastRingTime = millis(); doorbell.lastVisitorTime = getTimestamp(); Serial.println("\n🔔 DOORBELL RANG! Visitor #" + String(doorbell.visitorCount)); logVisitor("Doorbell Ring"); setRGBColor(0, 0, 255); // Blue activateCamera(); playDoorbellChime(); sendNotification("DOORBELL", "Someone is at your door!"); flashDisplay("VISITOR AT DOOR!"); delay(500); setRGBColor(0, 0, 0); doorbell.doorbellPressed = false; } // ── Motion Detection ─────────────────────────── void checkMotionSensor() { bool motion = digitalRead(PIR_SENSOR_PIN); if (motion && !doorbell.motionDetected) { doorbell.motionDetected = true; Serial.println("👁️ Motion detected at front door"); logVisitor("Motion Detected"); setRGBColor(255, 255, 0); if (doorbell.nightMode) setRGBColor(255, 255, 255); activateCamera(); sendNotification("MOTION", "Motion detected at front door"); delay(1000); setRGBColor(0, 0, 0); } else if (!motion && doorbell.motionDetected) { doorbell.motionDetected = false; } } // ── Light Level Check ────────────────────────── void checkLightLevel() { doorbell.lightLevel = map(analogRead(LIGHT_SENSOR_PIN), 0, 4095, 0, 100); bool shouldBeNight = (doorbell.lightLevel < 30); if (shouldBeNight != doorbell.nightMode) { doorbell.nightMode = shouldBeNight; Serial.println(doorbell.nightMode ? "🌙 Night mode ON" : "☀️ Day mode ON"); } } // ── Camera Activation ────────────────────────── void activateCamera() { doorbell.cameraActive = true; digitalWrite(CAMERA_TRIGGER_PIN, HIGH); Serial.println("📷 Camera ON — visitor_" + String(doorbell.visitorCount) + ".jpg saved"); delay(2000); digitalWrite(CAMERA_TRIGGER_PIN, LOW); doorbell.cameraActive = false; } // ── Doorbell Chime ───────────────────────────── void playDoorbellChime() { for (int i = 0; i < 4; i++) { tone(BUZZER_PIN, melody1[i], noteDurations[i]); delay(noteDurations[i] + 50); } noTone(BUZZER_PIN); } void playStartupChime() { tone(BUZZER_PIN, 1000, 100); delay(150); tone(BUZZER_PIN, 1500, 100); } // ── RGB Helper ───────────────────────────────── void setRGBColor(int r, int g, int b) { analogWrite(RED_LED_PIN, r); analogWrite(GREEN_LED_PIN, g); analogWrite(BLUE_LED_PIN, b); } // ── OLED Update ──────────────────────────────── void updateDisplay() { display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.println("SMART DOORBELL"); display.drawLine(0, 9, 128, 9, SSD1306_WHITE); display.setCursor(0, 12); display.print("Visitors: "); display.println(doorbell.visitorCount); display.setCursor(0, 22); display.print("Last: "); display.println(doorbell.lastVisitorTime); display.setCursor(0, 32); display.println(doorbell.motionDetected ? "MOTION DETECTED!" : "Motion: None"); display.setCursor(0, 42); display.print("Door: "); display.println(doorbell.doorLocked ? "LOCKED" : "UNLOCKED"); display.setCursor(0, 52); display.print(doorbell.nightMode ? "Night Mode" : "Day Mode"); if (doorbell.cameraActive) { display.setCursor(80, 52); display.print("CAM ON"); } display.display(); } void flashDisplay(String msg) { for (int i = 0; i < 3; i++) { display.clearDisplay(); display.setTextSize(2); display.setCursor(0, 20); display.println(msg); display.display(); delay(500); display.clearDisplay(); display.display(); delay(300); } } // ── Notification Simulator ───────────────────── void sendNotification(String type, String message) { Serial.println("\n📱 NOTIFICATION — " + type + ": " + message); Serial.println(" ✉️ Email: user@example.com"); Serial.println(" 📱 SMS: +1-234-567-8900"); } void logVisitor(String event) { visitorHistory[historyIndex] = {getTimestamp(), event, true}; historyIndex = (historyIndex + 1) % 20; } String getTimestamp() { unsigned long s = millis() / 1000; unsigned long m = s / 60, h = m / 60; s %= 60; m %= 60; h %= 24; String ts = (h < 10 ? "0" : "") + String(h) + ":"; ts += (m < 10 ? "0" : "") + String(m) + ":"; ts += (s < 10 ? "0" : "") + String(s); return ts; } // ── Web Handlers (abbreviated) ───────────────── // Full handleRoot() generates the HTML dashboard. // handleStatus() → JSON status endpoint // handleHistory() → JSON visitor log // handleTestRing() / handleUnlock() / handleLock() // handleCamera() / handleClear() — see full source above. void handleStatus() { String json = "{"; json += "\"visitors\":" + String(doorbell.visitorCount) + ","; json += "\"motion\":" + String(doorbell.motionDetected ? "true":"false") + ","; json += "\"doorLocked\":"+ String(doorbell.doorLocked ? "true":"false") + ","; json += "\"nightMode\":" + String(doorbell.nightMode ? "true":"false") + ","; json += "\"lightLevel\":"+ String(doorbell.lightLevel) + "}"; server.send(200, "application/json", json); } void handleTestRing() { handleDoorbellRing(); server.sendHeader("Location","/"); server.send(303); } void handleUnlock() { doorbell.doorLocked=false; digitalWrite(RELAY_DOOR_PIN,LOW); logVisitor("Door Unlocked"); setRGBColor(0,255,0); delay(500); setRGBColor(0,0,0); server.sendHeader("Location","/"); server.send(303); } void handleLock() { doorbell.doorLocked=true; digitalWrite(RELAY_DOOR_PIN,HIGH); logVisitor("Door Locked"); setRGBColor(255,0,0); delay(500); setRGBColor(0,0,0); server.sendHeader("Location","/"); server.send(303); } void handleCamera() { activateCamera(); logVisitor("Camera Triggered"); server.sendHeader("Location","/"); server.send(303); } void handleClear() { doorbell.visitorCount=0; historyIndex=0; for(int i=0;i<20;i++) visitorHistory[i]={"","",false}; server.sendHeader("Location","/"); server.send(303); } void handleRoot() { /* Full HTML dashboard — see simulation for complete code */ server.send(200,"text/html","<h1>Smart Doorbell Online</h1>"); }
RGB LED Status Meanings
Step-by-Step: How to Test
🚀 Start the Simulation
Click the simulation link below, then press the ▶ Play button in Wokwi. Wait for "WiFi connected!" in the Serial Monitor.
🔔 Press the DOORBELL Button
Click the blue DOORBELL button. The buzzer plays a 4-note chime (C–D–E–G), the blue LED lights up, the camera LED activates, and the OLED flashes "VISITOR AT DOOR!". Check Serial Monitor for notification output.
👁️ Trigger Motion Detection
Click the PIR sensor in Wokwi and toggle it HIGH. The yellow LED activates, camera triggers, and a motion notification is sent. The visitor log updates automatically.
🌙 Test Night Mode
Rotate the "Light Sensor" potentiometer to a low value (below 30%). Night mode activates — the next motion detection will turn on the white LED (simulating an outdoor light).
🌐 Open the Web Dashboard
Find the IP address printed in the Serial Monitor (e.g., http://10.0.0.2). Open it in your browser to see the live dashboard with visitor stats, lock controls, and event log.
🔓 Remote Lock / Unlock Door
From the web dashboard, click "Unlock Door". The door lock LED turns off, a short tone plays, and the OLED updates to "UNLOCKED". Click "Lock Door" to re-secure.
📋 View Visitor Log
The web dashboard shows the 5 most recent events with timestamps. Click "Test Doorbell" to add entries from the browser. "Clear Log" resets the counter and history.
▶ Try it Live in Wokwi Simulator
No hardware needed — run the full doorbell simulation in your browser. Click the button, trigger motion, and watch the web dashboard in real time.
🔗 Open Free Simulation
Comments
Post a Comment