Skip to main contentSkip to footer

Have you ever wanted to know if someone is sneaking into your room, or monitor when a specific area of your house is occupied? Building your own smart home security system is easier than you might think.

In this tutorial, we are going to build a DIY motion-activated alarm using an ESP32 and a PIR motion sensor, powered by the Arduino IoT Cloud.

We will actually build two different versions of this project:

  1. The Silent Monitor (100% Free): A stealthy dashboard that logs exactly what time someone entered the room and graphs the activity.

  2. The Instant Alarm (Requires Premium – 30 Days Free): A full security system that sends a push notification to your phone and an email to your inbox the second motion is detected.

You can also watch the video tutorial below:

What You Will Need

To build this project, you only need a few cheap components:

  • ESP32 Development Board (Any standard ESP32, like the WROOM-32)

  • HC-SR501 PIR Motion Sensor

  • 3 Female-to-Female Jumper Wires

  • Micro-USB or USB-C Cable (Make sure it is a data cable, not just for charging)

The Wiring

The HC-SR501 is incredibly easy to wire. Pop off the white plastic dome to see the pin labels, and connect them directly to your ESP32:

PIR Sensor ESP32 Pin Function
VCC VIN (or 5V) Powers the sensor
OUT (Middle) GPIO 33 Sends the motion signal
GND GND Ground

Project 1: The Silent Room Monitor (Free Tier)

This version uses the free tier of the Arduino IoT Cloud. It doesn’t send push notifications, but it gives you a live dashboard on your phone or computer where you can check if a room is currently occupied, see the exact time it was last entered, and view a historical graph of activity.

Step 1: Set Up the Cloud Variables

Log into the Arduino IoT Cloud and create a new Thing. Name it “Room Monitor”. Connect your ESP32 board in the Network section, and then create these three variables exactly as written:

  1. roomOccupied (Type: Boolean, Permission: Read Only, Update: On Change)

  2. lastMovementTime (Type: Character String, Permission: Read Only, Update: On Change)

  3. activityLevel (Type: Integer Number, Permission: Read Only, Update: On Change)

Step 2: Build the Dashboard

Go to the Dashboards tab and create a new dashboard to visualize your data:

  • Add an LED widget and link it to roomOccupied. (Lights up when someone is there).

  • Add a Value widget and link it to lastMovementTime. (Shows the timestamp).

  • Add a Chart widget and link it to activityLevel. (Draws a timeline graph of intrusions).

Step 3: The Code

Go to the Sketch tab. You will need to include the NTPClient library (search for it in the library manager on the left menu). Replace the default code with this:

C++

#include "thingProperties.h"
#include <NTPClient.h>
#include <WiFiUdp.h>

const int pirPin = 33; 

// Setup for the NTP Time Client (SAST UTC+2 = 7200 seconds offset)
// Adjust the 7200 for your own time zone!
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 7200, 60000); 

void setup() {
  Serial.begin(115200);
  pinMode(pirPin, INPUT);
  
  initProperties();
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop() {
  ArduinoCloud.update(); 
  
  // Only update time if connected to Wi-Fi to prevent crashes
  if (ArduinoCloud.connected()) {
    static bool timeStarted = false;
    if (!timeStarted) {
      timeClient.begin();
      timeStarted = true;
    }
    timeClient.update();
  }
  
  int pirState = digitalRead(pirPin);
  
  // When motion is detected
  if (pirState == HIGH && !roomOccupied) {
    Serial.println("Motion detected!");
    
    roomOccupied = true;       // Turns on dashboard LED
    activityLevel = 1;         // Spikes the graph UP
    
    if (ArduinoCloud.connected()) {
      lastMovementTime = timeClient.getFormattedTime(); // Updates text time
    }
  } 
  
  // When motion stops
  else if (pirState == LOW && roomOccupied) {
    Serial.println("Area clear.");
    
    roomOccupied = false;      // Turns off dashboard LED
    activityLevel = 0;         // Drops the graph DOWN
  }
}

Upload the code, open your dashboard, and wave your hand in front of the sensor. You now have a working security log!

Project 2: The Instant Push-Notification Alarm

If you want your phone to buzz with an alert the absolute second someone opens your door, you need to use the Arduino Cloud Triggers feature.

Note: The Triggers feature requires the Arduino Cloud Maker plan (Premium). However, Arduino offers a free 30-day trial of the Maker plan, so you can test this out completely risk-free!

Step 1: Set Up the Alarm Variables

Create a new Thing and call it “Home Alarm”. Because the cloud handles the heavy lifting, we only need one variable this time:

  • alarmTriggered (Type: Boolean, Permission: Read & Write, Update: On Change)

Step 2: Configure the Cloud Triggers

This is where the magic happens. Look at the left-hand menu in Arduino Cloud and click on Triggers.

  1. Click Create Trigger.

  2. Condition: If alarmTriggered becomes true.

  3. Action: Select Push Notification.

  4. Message: “🚨 INTRUDER ALERT: Motion detected!”

  5. (Optional: You can create a second trigger with the exact same condition, but set the action to Email to get dual alerts!)

Make sure you download the free Arduino IoT Cloud Remote app on your iOS or Android device and allow notifications.

Step 3: The Code

Because the Cloud Triggers do all the hard work in the background, our ESP32 code becomes incredibly clean. Paste this into your Sketch tab:

C++

#include "thingProperties.h"

const int pirPin = 33; // GPIO pin connected to the PIR sensor

void setup() {
  Serial.begin(115200);
  pinMode(pirPin, INPUT);
  
  initProperties();
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop() {
  ArduinoCloud.update(); 
  
  int pirState = digitalRead(pirPin);
  
  // If motion is detected and the alarm isn't already active
  if (pirState == HIGH && !alarmTriggered) {
    Serial.println("Motion detected! Triggering alarm...");
    
    // Setting this to true fires the Cloud Triggers (Push & Email)
    alarmTriggered = true; 
  } 
  
  // If motion stops
  else if (pirState == LOW && alarmTriggered) {
    Serial.println("Area clear. Resetting alarm.");
    
    // Resetting to false prepares the system for the next detection
    alarmTriggered = false; 
    
    // A 5-second delay to prevent rapid-fire notifications
    delay(5000); 
  }
}

/* 
  Required empty callback for the Read & Write variable 
*/
void onAlarmTriggeredChange() {
  // We handle the logic in the loop, so this stays empty
}