This final capstone brings all the Blynk lessons together in one device. Your smart home hub measures temperature and humidity, switches a lamp from the app, and has two personalities. At home, it's a helper: when it's dark and someone moves, the lamp switches on by itself and goes off again two minutes after the last movement. Away, it's a guard: any movement sounds the siren, switches the lamp on and sends an alert to your phone. One switch in the app changes between the two.
What you'll learn
- How to make one device behave differently in different modes
- How to combine several sensors into one decision
- How to keep app controls and automatic rules from fighting each other
- How to plan pins and power for a project with five parts
What you need
- ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
- DHT11 module, KY-015 (ESP32 IoT Starter Kit)
- PIR motion sensor (ESP32 IoT Starter Kit)
- Photoresistor module (45-in-1 Sensor Kit)
- Relay module (45-in-1 Sensor Kit)
- Active buzzer module (45-in-1 Sensor Kit)
- A free Blynk account and the Blynk IoT app on a phone
- Breadboard and jumper wires (ESP32 IoT Starter Kit)
- A 2.4 GHz Wi-Fi network
Plan the rules
Before wiring anything, write down what the hub should do. Clear rules on paper make the code much easier.
| Mode | When this happens | The hub does this |
|---|---|---|
| Home | Movement while the room is dark | Lamp on. It goes off 2 minutes after the last movement |
| Home | You use the lamp switch in the app | Lamp follows the switch and the automatic timer is cancelled |
| Away | Any movement | Siren for 30 seconds, lamp on, phone alert |
| Away | You switch back to Home | Siren off |
| Both | Every 5 seconds | Temperature and humidity are sent to the app |
Wiring
The breadboard's + rail carries 3.3 V. The relay and the PIR sensor need 5 V, so they share one wire to the ESP32's 5V pin.

| From | To |
|---|---|
| DHT11 S (signal) | ESP32 GPIO 27 |
| DHT11 + and − | 3V3 and GND |
| Photoresistor module S | ESP32 GPIO 34 |
| Photoresistor module + and − | 3V3 and GND |
| Relay module S | ESP32 GPIO 26 |
| Relay module + | ESP32 5V |
| Relay module − | GND |
| Active buzzer module S | ESP32 GPIO 25 |
| Active buzzer module + and − | 3V3 and GND |
| PIR OUT | ESP32 GPIO 14 |
| PIR VCC | 5V (same breadboard row as the relay's + pin) |
| PIR GND | GND |
Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.
The relay is your "lamp". You hear it click and see its LED. If you want a real light, let the relay switch a small battery-powered lamp or LED strip (up to 12 V). Never connect mains electricity to the relay.
Set up Blynk
In the Blynk console, create a new template called Smart home hub (hardware ESP32, connection type Wi-Fi) and add these datastreams. Connect the ESP32 to Blynk shows each step in detail.
| Virtual pin | Name | Data type | Range | Phone widget |
|---|---|---|---|---|
| V0 | Lamp | Integer | 0 – 1 | Button (switch mode) |
| V1 | Temperature | Double | 0 – 50, units °C | Gauge |
| V2 | Humidity | Double | 0 – 100, units % | Gauge |
| V3 | Away mode | Integer | 0 – 1 | Button (switch mode) |
| V4 | Status | String | n/a | Value Display |
Then open the template's Events & Notifications tab, add an event with the code intruder (name: Movement while away) 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 two Buttons in switch mode (V0 and V3), two Gauges (V1 and V2) and a Value Display (V4). Link each widget to its datastream.
Build it in stages
This is a bigger build than the earlier lessons, so don't wire everything at once. Add one part, test it, then add the next. When something stops working, you know it's the part you just added.
- Climate. Wire the DHT11, upload the sketch and check that the two gauges show values.
- Lamp. Add the relay module. The Lamp switch in the app now clicks the relay.
- Automatic light. Add the photoresistor and the PIR sensor. Give the PIR sensor a minute to settle. Cover the photoresistor and wave your hand: the lamp switches on, and the status line shows when.
- Guard. Add the buzzer. Switch Away mode on in the app and leave the sensor's view for 30 seconds. Then walk in.

The sketch
Install the DHT sensor library by Adafruit.
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 "Smart home hub"
#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 <DHT.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 DHT_PIN = 27;
const int LIGHT_PIN = 34;
const int RELAY_PIN = 26;
const int BUZZER_PIN = 25;
const int PIR_PIN = 14;
const int DARK_BELOW = 30; // "dark" means less light than this (%)
const unsigned long LAMP_TIME = 120000; // automatic light stays on for 2 minutes
const unsigned long SIREN_TIME = 30000; // the siren sounds for 30 seconds
const unsigned long ARM_DELAY = 30000; // time to leave after switching to Away
DHT dht(DHT_PIN, DHT11);
BlynkTimer timer;
bool away = false;
bool lampOn = false;
bool lampIsAuto = false; // true when the hub switched the lamp on by itself
bool sirenOn = false;
bool motionBefore = false;
unsigned long lastMotion = 0;
unsigned long sirenStart = 0;
unsigned long awaySince = 0;
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", &t);
return String(text);
}
void setLamp(bool on, bool automatic) {
lampOn = on;
lampIsAuto = on && automatic;
digitalWrite(RELAY_PIN, on);
Blynk.virtualWrite(V0, on); // keep the app's switch in step
}
void setSiren(bool on) {
sirenOn = on;
sirenStart = millis();
digitalWrite(BUZZER_PIN, on);
}
BLYNK_WRITE(V0) { // lamp switch in the app
setLamp(param.asInt(), false);
}
BLYNK_WRITE(V3) { // Away mode switch
away = param.asInt();
awaySince = millis();
setSiren(false);
Blynk.virtualWrite(V4, away ? "Away: guarding in 30 s" : "Home");
}
BLYNK_CONNECTED() {
Blynk.syncVirtual(V0, V3);
}
void sendClimate() {
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
if (isnan(temperature) || isnan(humidity)) return;
Blynk.virtualWrite(V1, temperature);
Blynk.virtualWrite(V2, humidity);
}
void onMotion() {
lastMotion = millis();
if (away) {
if (millis() - awaySince < ARM_DELAY) return; // still time to leave
if (!sirenOn) {
setSiren(true);
setLamp(true, true);
Blynk.logEvent("intruder", "Movement at " + timeNow());
Blynk.virtualWrite(V4, "ALARM " + timeNow());
}
} else {
int light = map(analogRead(LIGHT_PIN), 0, 4095, 100, 0); // bright = high %
if (light < DARK_BELOW && !lampOn) {
setLamp(true, true);
Blynk.virtualWrite(V4, "Light on, movement " + timeNow());
}
}
}
void checkHome() {
bool motion = digitalRead(PIR_PIN) == HIGH;
if (motion && !motionBefore) onMotion(); // a new movement starts
if (motion) lastMotion = millis(); // still moving: restart the timer
motionBefore = motion;
if (sirenOn && millis() - sirenStart >= SIREN_TIME) setSiren(false);
if (lampIsAuto && !sirenOn && millis() - lastMotion >= LAMP_TIME) {
setLamp(false, true);
if (!away) Blynk.virtualWrite(V4, "Light off " + timeNow());
}
}
void setup() {
Serial.begin(115200);
pinMode(RELAY_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(PIR_PIN, INPUT);
dht.begin();
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
configTzTime(TIME_ZONE, "pool.ntp.org"); // real time from the internet
timer.setInterval(5000L, sendClimate); // climate every 5 seconds
timer.setInterval(200L, checkHome); // sensors five times per second
}
void loop() {
Blynk.run();
timer.run();
}
How it works
The hub has one variable that changes its whole behaviour: away. The same movement leads to completely different actions depending on that one value. Designing a device around a small number of clear modes is what keeps a growing project understandable.
The PIR sensor's output stays HIGH for several seconds after a movement. If the sketch reacted every time it read HIGH, it would send dozens of alerts. Instead, checkHome() compares the reading with the previous one and calls onMotion() only at the moment the signal changes from LOW to HIGH. This is called edge detection.
The lamp can be switched by you or by the hub, so the sketch remembers who did it. lampIsAuto is true only when the hub switched the lamp on by itself, and only then does the two-minute timer switch it off again. If you switch the lamp on in the app, it stays on until you switch it off. All changes go through setLamp(), which also writes the new state back to V0, so the switch in the app always shows what the lamp is really doing.
After you switch to Away, the hub ignores movement for 30 seconds so that you can leave the room. Two timers run in the background: one sends the climate every five seconds, the other checks the sensors five times per second. Neither uses delay(), so the hub always stays connected.
Where to go from here
You've now built data loggers, alarm systems, a game, a radar and connected devices that act on their own. The next step is a project of your own. Pick a problem at home or at school, write down the rules like you did here, choose the sensors, and build it in stages. When it works, share it with other makers on our project submission page.
Try this next
- Add a third mode, "Night": movement switches the lamp on for only 20 seconds and never sounds the siren.
- Send a second event when the temperature goes above 30 °C.
- Add the flame sensor and sound the siren in every mode when it detects fire.
- Add a second relay module for a fan and switch it on above 27 °C.
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. -
The lamp never switches on by itself: the room isn't "dark" for the sensor. Cover the photoresistor to test, or raise
DARK_BELOW. If the light reading is the wrong way round, swap100, 0to0, 100. - The alarm doesn't react just after switching to Away: that's the 30-second leaving time. Wait and try again.
-
The relay or buzzer is on when it should be off: some modules switch on with LOW. Swap the logic in
setLamp()orsetSiren()by writing!oninstead ofonin thedigitalWriteline. - The PIR sensor triggers all the time: wait a full minute after power-up, turn its sensitivity screw down, and keep it away from the relay and from warm air.
-
No notification arrives: check that the event code is exactly
intruderand that notifications are switched on for it in the template.