Is someone in your room while you're out? This alarm tells you. The PIR sensor notices people moving, and the ESP32 counts every movement and shows the time of the last one in the Blynk app. When the alarm is armed, the buzzer sounds and your phone gets a notification. You arm and disarm it from the app, wherever you are.
What you'll learn
- How to arm and disarm a system remotely
- How to detect the start of an event (an edge) in a timer
- How to add the real time to messages (NTP)
- How to stop notifications from flooding your phone
What you need
- ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
- PIR motion sensor (ESP32 IoT Starter Kit)
- Active buzzer module (45-in-1 Sensor Kit)
- Breadboard and jumper wires (ESP32 IoT Starter Kit)
- A free Blynk account and the Blynk IoT app on a phone
- A 2.4 GHz Wi-Fi network
Wiring

| From | To |
|---|---|
| PIR VCC | 5V |
| PIR OUT | ESP32 GPIO 27 |
| PIR GND | GND |
| Buzzer module S | ESP32 GPIO 26 |
| Buzzer module + | 3V3 |
| Buzzer module − | GND |
Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.
The PIR sensor needs 5 V to run, but its OUT pin only gives about 3.3 V, which is safe for the ESP32.
Set up Blynk
In the Blynk console, create a new template called Motion alarm (hardware ESP32, connection type Wi-Fi) and add these datastreams. Lesson 1 shows each step in detail.
| Virtual pin | Name | Data type | Range | Phone widget |
|---|---|---|---|---|
| V0 | Armed | Integer | 0 – 1 | Button (switch mode) |
| V1 | Movements | Integer | 0 – 9999 | Value Display |
| V2 | Last movement | String | n/a | Value Display |
Then open the template's Events & Notifications tab, add an event with the code motion (name: Motion detected) and turn on push notifications for it. The event code must match the sketch exactly.
Save the template, create a new device from it and copy its three BLYNK_… lines. In the phone app, open the device, tap the wrench icon and add a Button in switch mode (V0) and two Value Displays (V1, V2). Link each widget to its datastream.

The sketch
Replace the three BLYNK_… lines at the top with the ones from your device's Device info page, and add your Wi-Fi name and password (2.4 GHz only). The BLYNK_… lines must stay above #include <BlynkSimpleEsp32.h>.
#define BLYNK_TEMPLATE_ID "YOUR_TEMPLATE_ID"
#define BLYNK_TEMPLATE_NAME "Motion alarm"
#define BLYNK_AUTH_TOKEN "YOUR_AUTH_TOKEN"
#define BLYNK_PRINT Serial // show connection messages in the Serial Monitor
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <time.h>
char ssid[] = "YOUR_WIFI_NAME";
char pass[] = "YOUR_WIFI_PASSWORD";
const char* TIME_ZONE = "EET-2EEST,M3.5.0/3,M10.5.0/4"; // Estonia; change for your country
const int PIR_PIN = 27;
const int BUZZER_PIN = 26;
BlynkTimer timer;
bool armed = false;
bool wasMotion = false;
long movements = 0;
unsigned long sirenStart = 0;
bool sirenOn = false;
unsigned long lastAlert = 0;
bool alertedOnce = false;
String timeNow() {
struct tm t;
if (!getLocalTime(&t, 10)) return "time not set yet";
char text[20];
strftime(text, sizeof(text), "%d.%m %H:%M:%S", &t);
return String(text);
}
BLYNK_WRITE(V0) {
armed = param.asInt();
if (!armed) { sirenOn = false; digitalWrite(BUZZER_PIN, LOW); }
Serial.println(armed ? "Armed" : "Disarmed");
}
BLYNK_CONNECTED() { Blynk.syncVirtual(V0); }
void checkMotion() {
bool motion = digitalRead(PIR_PIN) == HIGH;
if (motion && !wasMotion) { // a new movement starts
movements++;
Blynk.virtualWrite(V1, movements);
Blynk.virtualWrite(V2, timeNow());
Serial.println("Movement!");
if (armed) {
sirenOn = true;
sirenStart = millis();
// at most one notification per minute
if (!alertedOnce || millis() - lastAlert > 60000) {
Blynk.logEvent("motion", "Movement at " + timeNow());
lastAlert = millis();
alertedOnce = true;
}
}
}
wasMotion = motion;
if (sirenOn && millis() - sirenStart > 3000) sirenOn = false; // 3-second siren
digitalWrite(BUZZER_PIN, sirenOn && (millis() / 200) % 2); // beep-beep
}
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
configTzTime(TIME_ZONE, "pool.ntp.org"); // real time from the internet
timer.setInterval(50L, checkMotion);
Serial.println("Give the PIR sensor about a minute to settle");
}
void loop() {
Blynk.run();
timer.run();
}
How it works
The PIR sensor's OUT pin goes HIGH while it sees movement and stays HIGH for a few seconds. To count each movement only once, the sketch remembers the previous reading in wasMotion and reacts only when the signal changes from LOW to HIGH. That moment is called a rising edge. The time comes from an internet time server (NTP), and the ESP32 keeps counting it accurately afterwards. When armed, a movement starts a 3-second siren and sends a notification, but never more than one per minute, so a cat walking around doesn't fill your phone with alerts.
Try this next
- Add a 20-second exit delay after arming, so you can leave the room.
- Add a Value Display that counts only the movements while armed.
- Add a "Silence" button in the app that stops the siren.
Troubleshooting
-
The device shows "Offline" in the app: check the Serial Monitor at 115200 baud. "Invalid auth token" means the
BLYNK_AUTH_TOKENline is wrong; if it never connects, check the Wi-Fi name and password, and that your network is 2.4 GHz. - It counts movements when nobody is there: the PIR sensor needs about a minute to settle after power-on. Turn its sensitivity knob down a little and keep it away from heaters and windows.
- Movements are counted only every few seconds: turn the PIR's time-delay knob fully anticlockwise for the shortest delay.
- The time says "time not set yet": wait a minute after starting; the ESP32 needs to reach the time server once.