Shops, libraries and classrooms often need to know how many people are inside. This counter works it out by itself. Two IR sensors sit side by side on the door frame. Someone walking in passes sensor A first and then B; someone walking out passes B first. The ESP32 counts up or down, and the Blynk app shows the number inside, a chart over the day and the total visits. Set the maximum with a slider: when the room is full, the red LED lights and you get a notification. It is a great way to see how busy your room really is.
What you'll learn
- How the order of two sensors tells the direction
- How to keep a count that can never go below zero
- How to send a notification only once until things change
- How to reset a count from the app and at midnight
What you need
- ESP32 board, expansion board and USB cable (ESP32 IoT Starter Kit)
- 2 IR obstacle avoidance sensor modules (45-in-1 Sensor Kit)
- Green and red LEDs and 2 resistors, 220 Ω (ESP32 IoT Starter Kit)
- Long jumper wires to reach the door frame
- A free Blynk account and the Blynk IoT app on a phone
- A 2.4 GHz Wi-Fi network
- The printed parts from the 3D printing box below (doorway sensor bar), or the cardboard version described there
- Breadboard and jumper wires (ESP32 IoT Starter Kit)
Wiring

| From | To |
|---|---|
| IR sensor A (outside) OUT | ESP32 GPIO 34 |
| IR sensor B (inside) OUT | ESP32 GPIO 35 |
| Both IR sensors + and GND | 3V3 and GND |
| ESP32 GPIO 25 | 220 Ω resistor, then the green LED's long leg |
| ESP32 GPIO 26 | 220 Ω resistor, then the red LED's long leg |
| Both LED short legs | GND |
Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.
Set up Blynk
In the Blynk console, create a new template called Room Counter (hardware ESP32, connection type Wi-Fi) and add these datastreams. Lesson 1 of this path shows each step in detail.
| Virtual pin | Name | Data type | Range | Phone widget |
|---|---|---|---|---|
| V0 | Inside | Integer | 0–50 | Gauge or Superchart |
| V1 | Visits today | Integer | 0–1000 | Value display |
| V2 | Maximum | Integer | 1–30 | Slider |
| V3 | Status | String | – | Label |
| V4 | Reset | Integer | 0–1 | Button (push) |
Then open the template's Events & Notifications tab, add an event with the code room_full (name: Room is full) 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 Gauge (V0), a Value display (V1), a Slider (V2), a Label (V3) and a Button in push mode (V4). 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>.
Change TIME_ZONE to your own time zone. The example is for Central Europe; search for "POSIX time zone" and your city to find yours (for example EET-2EEST,M3.5.0/3,M10.5.0/4 for Estonia or GMT0BST,M3.5.0/1,M10.5.0 for the UK).
#define BLYNK_TEMPLATE_ID "YOUR_TEMPLATE_ID"
#define BLYNK_TEMPLATE_NAME "Room Counter"
#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 = "CET-1CEST,M3.5.0,M10.5.0/3"; // your time zone
const int A_PIN = 34, B_PIN = 35; // A outside, B inside
const int GREEN_PIN = 25, RED_PIN = 26;
const unsigned long WALK_MS = 1500; // the second sensor must follow within 1.5 s
BlynkTimer timer;
int inside = 0, visits = 0, maximum = 10, today = -1;
bool fullSent = false;
bool aWas = false, bWas = false;
unsigned long aAt = 0, bAt = 0; // when each sensor last saw someone arrive
void update() {
bool full = inside >= maximum;
digitalWrite(GREEN_PIN, !full);
digitalWrite(RED_PIN, full);
Blynk.virtualWrite(V0, inside);
Blynk.virtualWrite(V1, visits);
Blynk.virtualWrite(V3, full ? "Full" : (String(maximum - inside) + " places free"));
if (full && !fullSent) {
Blynk.logEvent("room_full", String("There are ") + inside + " people in the room.");
fullSent = true;
}
if (inside < maximum - 1) fullSent = false; // ready to warn again
}
void checkSensors() {
bool a = digitalRead(A_PIN) == LOW; // LOW: something in front
bool b = digitalRead(B_PIN) == LOW;
unsigned long now = millis();
if (a && !aWas) { // someone reaches A
if (bAt != 0 && now - bAt < WALK_MS) { // B came first: walking out
inside = max(0, inside - 1);
bAt = 0;
Serial.println("Out");
update();
} else {
aAt = now;
}
}
if (b && !bWas) { // someone reaches B
if (aAt != 0 && now - aAt < WALK_MS) { // A came first: walking in
inside++;
visits++;
aAt = 0;
Serial.println("In");
update();
} else {
bAt = now;
}
}
aWas = a;
bWas = b;
}
void newDay() {
time_t now = time(nullptr);
if (now < 1700000000) return;
struct tm local;
localtime_r(&now, &local);
if (today != -1 && local.tm_yday != today) { // midnight: the room is empty again
inside = 0;
visits = 0;
update();
}
today = local.tm_yday;
}
BLYNK_WRITE(V2) { // Maximum slider
maximum = constrain(param.asInt(), 1, 30);
update();
}
BLYNK_WRITE(V4) { // Reset button
if (param.asInt() == 1) {
inside = 0;
update();
}
}
BLYNK_CONNECTED() {
Blynk.syncVirtual(V2);
update();
}
void setup() {
Serial.begin(115200);
pinMode(A_PIN, INPUT);
pinMode(B_PIN, INPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(RED_PIN, OUTPUT);
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
configTzTime(TIME_ZONE, "pool.ntp.org");
timer.setInterval(60000L, newDay);
}
void loop() {
Blynk.run();
timer.run();
checkSensors();
}
Try it
- Walk in through the door at a normal speed. The Serial Monitor says "In", and the app shows 1 inside and 1 visit.
- Walk out: "Out", and the number inside goes back to 0.
- Set the maximum to 2 and walk in twice: the red LED lights and your phone gets "Room is full".
- If the count drifts (two people at once can fool it), tap Reset when the room is empty.
How it works
One sensor can tell that someone passed, but not which way. Two sensors side by side can: whoever is walking in reaches A a moment before B, and whoever is walking out reaches B before A. The sketch remembers when each sensor last saw someone arrive. When B fires and A fired less than 1.5 seconds earlier, someone walked in. When A fires shortly after B, someone walked out. Using that time also stops a person who only looks through the door from being counted.
The count can't go below zero: max(0, inside - 1) keeps it there, even if someone was already inside when the counter started.
The "room is full" notification is sent once. It is only allowed again when the room has emptied by at least two places, so one person stepping in and out at the door doesn't send a flood of messages.
Every minute, newDay() checks the date from internet time. When the day changes, the counts start again from zero.
Try this next
- Add a Superchart to see when the room was busiest during the day.
- Show the number inside on the 4-digit display next to the door.
- Count the visits per hour and find the busiest hour of the week.
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. - Everyone is counted as going in: sensors A and B are swapped. Swap the two wires, or the two pin numbers.
- Nobody is counted: turn each sensor's screw until its light comes on when you walk past. Check that both see you, one after the other.
- Two people side by side count as one: the sensors can only see one person at a time. Use it for a door where people walk through one by one.
- A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.