Built-in project · ESP32 DevKit V1

ESP32 Telegram doorbell with a PIR sensor

An ESP32 doorbell: the button rings a buzzer and sends you a Telegram message with UniversalTelegramBot, and a PIR sensor reports motion at the door. The whole build, an ESP32 DevKit and 3 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $11.50. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Sending a Telegram message from an ESP32
  • Connecting to WiFi and using a secure client
  • Reading a button with INPUT_PULLUP
  • Rate limiting with millis() and a minimum gap
  • Reading a PIR motion sensor
  • Difficulty: Intermediate
  • Time: About 20 minutes
  • Board: ESP32 DevKit V1
  • Parts: 5

How it works

Pressing the button on pin 4 rings the buzzer on pin 25 with two beeps, then sends a Telegram message through the UniversalTelegramBot library. The sketch waits for you to let go, so one press is one ring.

A PIR motion sensor on pin 27 watches the door. When it sees movement it sends a second message, but never more than one every 60 seconds. If the message cannot be sent, the Serial Monitor says so and the doorbell still rings.

Parts list

5 parts, plus the jumper wires. They retail for about $11.50; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Buzzer pin 1ESP32 DevKit V1 pin D25
Motion sensor pin OUTESP32 DevKit V1 pin D27
Pushbutton pin 1aESP32 DevKit V1 pin D4
Pushbutton pin 2aGround
Buzzer pin 2Ground
Motion sensor pin VCC5 V
Motion sensor pin GNDGround

The note on the canvas

Word for word, as it sits beside the circuit in the editor.

Try it
1. Press Run and wait for "Doorbell ready" on the Serial Monitor.
2. Press the button: the buzzer rings ding-dong and the sketch tries to send a Telegram message.
3. Click the PIR sensor to make someone move at the door: one motion message, then none for a minute.
4. To get the messages on your phone: put your bot token and chat id in the sketch, sign in and switch on Real internet in the Cloud panel. Without it the Serial Monitor says why nothing was sent.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · ESP32 DevKit V1
/*
 * ESP32 Telegram doorbell: press the button and the buzzer rings and your
 * phone gets a Telegram message. A PIR sensor at the door sends another when
 * someone moves there, at most once a minute.
 *
 *   Button   one side -> GPIO 4 (D4), other side -> GND (INPUT_PULLUP)
 *   Buzzer   active buzzer, + -> GPIO 25 (D25), - -> GND
 *   PIR      HC-SR501, VCC -> 5 V, OUT -> GPIO 27 (D27), GND -> GND
 *            (its output is 3.3 V, safe for the ESP32)
 *
 * Library: UniversalTelegramBot (Brian Lough), which needs ArduinoJson.
 *
 * Before uploading to a real board:
 *   1. In Telegram, talk to @BotFather, send /newbot and copy the token it
 *      gives you into BOT_TOKEN.
 *   2. Talk to @userinfobot to get your chat id, and put it in CHAT_ID.
 *   3. Send your new bot any message once, so it is allowed to message you.
 *   4. Put your WiFi name and password in SSID and PASSWORD.
 *
 * In Mokxi the buzzer and the Serial Monitor work as soon as you press Run.
 * The message itself goes out only with Real internet switched on in the
 * Cloud panel (signed in) and a real token; otherwise the Serial Monitor
 * says why nothing was sent, and the doorbell still rings.
 */
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

const char *SSID = "Mokxi-Lab";
const char *PASSWORD = "breadboard";

#define BOT_TOKEN "123456789:REPLACE_WITH_YOUR_BOT_TOKEN"
#define CHAT_ID "123456789"

const int BUTTON_PIN = 4;
const int BUZZER_PIN = 25;
const int PIR_PIN = 27;

const unsigned long MOTION_GAP_MS = 60000; // one motion message a minute at most

WiFiClientSecure client;
UniversalTelegramBot bot(BOT_TOKEN, client);

unsigned long lastMotionMessage = 0;
bool motionSentOnce = false;
int rings = 0;

void ring()
{
    // ding ... dong: two beeps from an active buzzer
    digitalWrite(BUZZER_PIN, HIGH);
    delay(300);
    digitalWrite(BUZZER_PIN, LOW);
    delay(150);
    digitalWrite(BUZZER_PIN, HIGH);
    delay(500);
    digitalWrite(BUZZER_PIN, LOW);
}

void notify(const String &text)
{
    if (bot.sendMessage(CHAT_ID, text, "")) {
        Serial.println("Telegram message sent");
    } else {
        Serial.println("Telegram message NOT sent");
    }
}

void setup()
{
    Serial.begin(115200);
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    pinMode(BUZZER_PIN, OUTPUT);
    pinMode(PIR_PIN, INPUT);

    Serial.print("Connecting to ");
    Serial.println(SSID);
    WiFi.begin(SSID, PASSWORD);
    client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // api.telegram.org's root certificate
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println();
    Serial.print("WiFi connected, IP ");
    Serial.println(WiFi.localIP());
    Serial.println("Doorbell ready");
}

void loop()
{
    if (digitalRead(BUTTON_PIN) == LOW) {
        rings++;
        Serial.print("Ding dong! Ring ");
        Serial.println(rings);
        ring();
        notify("Ding dong! Someone is at the door.");
        // wait for the button to be let go, so one press is one ring
        while (digitalRead(BUTTON_PIN) == LOW) delay(10);
        delay(50);
    }

    if (digitalRead(PIR_PIN) == HIGH) {
        unsigned long now = millis();
        if (!motionSentOnce || now - lastMotionMessage >= MOTION_GAP_MS) {
            motionSentOnce = true;
            lastMotionMessage = now;
            Serial.println("Motion at the door");
            notify("Motion at the front door.");
        }
    }
    delay(20);
}

Code walkthrough

The key functions of the sketch, and what each one does.

ring sketch.ino

Plays ding-dong on an active buzzer: 300 ms on, 150 ms off, then 500 ms on. An active buzzer has its own tone circuit, so setting the pin high is enough.

void ring()
{
    // ding ... dong: two beeps from an active buzzer
    digitalWrite(BUZZER_PIN, HIGH);
    delay(300);
    digitalWrite(BUZZER_PIN, LOW);
    delay(150);
    digitalWrite(BUZZER_PIN, HIGH);
    delay(500);
    digitalWrite(BUZZER_PIN, LOW);
}

notify sketch.ino

Sends text to your chat with bot.sendMessage and prints whether it worked. It returns false when there is no real token or no internet, which is why the Serial Monitor can say nothing was sent.

void notify(const String &text)
{
    if (bot.sendMessage(CHAT_ID, text, "")) {
        Serial.println("Telegram message sent");
    } else {
        Serial.println("Telegram message NOT sent");
    }
}

loop sketch.ino

First checks the button: it counts the ring, rings the buzzer, sends a message, and waits for release. Then it checks the PIR. A motion message goes out only the first time, or when MOTION_GAP_MS (60000) has passed since the last one.

void loop()
{
    if (digitalRead(BUTTON_PIN) == LOW) {
        rings++;
        Serial.print("Ding dong! Ring ");
        Serial.println(rings);
        ring();
        notify("Ding dong! Someone is at the door.");
        // wait for the button to be let go, so one press is one ring
        while (digitalRead(BUTTON_PIN) == LOW) delay(10);
        delay(50);
    }

    if (digitalRead(PIR_PIN) == HIGH) {
        unsigned long now = millis();
        if (!motionSentOnce || now - lastMotionMessage >= MOTION_GAP_MS) {
            motionSentOnce = true;
            lastMotionMessage = now;
            Serial.println("Motion at the door");
            notify("Motion at the front door.");
        }
    }
    delay(20);
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The buzzer rings but no Telegram message arrives This project
    Put your real bot token in BOT_TOKEN and your chat id in CHAT_ID, and send the bot a message once. In Mokxi, switch on Real internet in the Cloud panel.
  • Motion only sends one message This project
    That is by design. Wait 60 seconds, or lower MOTION_GAP_MS.
  • The input reads random HIGH and LOW when the button is not pressed. Pushbutton
    A floating pin picks up noise. Add a 10 k pull-down or pull-up resistor, or use INPUT_PULLUP and wire the button between the pin and GND. Then pressed reads LOW.
  • The button seems always pressed, or never changes. Pushbutton
    A 4-leg tactile switch has legs joined in pairs. Place it across the breadboard center gap so the pairs sit on different rows. Wired along one side, the legs are already connected.
  • The buzzer makes a constant tone, or you cannot change its pitch. Buzzer
    An active buzzer has its own oscillator, so it only beeps on and off. A passive buzzer needs tone() to set pitch. Check which type you have.
  • The buzzer is silent or very quiet. Buzzer
    Check polarity: the plus mark goes to the pin or supply. For loud buzzers, drive them through a transistor instead of straight from a pin.
  • The sensor reads high for ages after power up. Motion sensor
    A real PIR needs 30 to 60 seconds to settle. Ignore its output during that time, and keep still in front of it until the delay passes.
  • Wrong output or it never triggers. Motion sensor
    Power it from 5 V, join GND, and read OUT as a digital input (3.3 V logic high). Check the trimmers for delay and sensitivity.

Questions and answers

Why did no message reach my phone?

Telegram is a real service. In Mokxi the message only goes out with Real internet switched on in the Cloud panel, signed in, and a real bot token and chat id in the sketch. Otherwise the Serial Monitor says why, and the buzzer still rings.

How do I get a bot token and chat id?

Send /newbot to @BotFather for the token, and ask @userinfobot for your chat id. Then message your new bot once so it is allowed to write to you.

Why does the motion alert come at most once a minute?

MOTION_GAP_MS is 60000, so someone standing at the door does not send a message every loop. With Real internet on, Mokxi's Free plan also caps the gateway at 10 requests a minute and 100 a day.

Does a press ring the bell even when the message fails?

Yes. The buzzer rings first, then the sketch tries to send. A message that does not go out is reported on the Serial Monitor, so the doorbell keeps working without WiFi.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.