Top 5 ESP32 IoT Projects with Telegram Alerts (With Code & Circuit)

Top 5 ESP32 IoT Projects with Telegram Alerts (With Code & Circuit)

Telegram bots are the easiest way to add phone notifications to any ESP32 project — no app development, no paid service, no complex setup. Create a bot in 60 seconds with BotFather, paste the token into your code, and the ESP32 can send messages, photos, and inline buttons directly to your phone. These 5 projects show exactly how.

How to create a Telegram bot (do this first):
1. Open Telegram and search for @BotFather
2. Send /newbot — follow the prompts to name your bot
3. Copy the bot token (looks like 123456789:ABCdef...)
4. Message your new bot, then visit https://api.telegram.org/bot<TOKEN>/getUpdates to find your chat_id

Project 1 — Plant Watering Reminder Bot

Project Description

A capacitive soil moisture sensor checks the plant's soil every hour using ESP32 deep sleep. When the soil is dry, the bot sends a Telegram message: "🌱 Your Basil needs water!" You can also message the bot "status" at any time to get an instant moisture reading back. Two-way communication — no button pressing needed.

Components: ESP32, capacitive soil moisture sensor, 18650 LiPo battery + TP4056 charger.

Circuit Description

SensorPinESP32 Pin
Soil sensor AOUTGPIO 34
Sensor VCCGPIO 32 (switched)
Sensor GNDGND

Code

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>  // install via Library Manager

const char* ssid      = "YOUR_WIFI_SSID";
const char* password  = "YOUR_WIFI_PASSWORD";
const char* botToken  = "YOUR_BOT_TOKEN";
const char* chatID    = "YOUR_CHAT_ID";

#define SOIL_PIN       34
#define SENSOR_POWER   32
#define DRY_THRESHOLD  2700

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

void sendMessage(String msg) {
  bot.sendMessage(chatID, msg, "");
}

void setup() {
  Serial.begin(115200);
  client.setInsecure(); // for simplicity; use setCACert for production
  pinMode(SENSOR_POWER, OUTPUT);
  digitalWrite(SENSOR_POWER, HIGH); delay(500);
  int moisture = analogRead(SOIL_PIN);
  digitalWrite(SENSOR_POWER, LOW);

  WiFi.begin(ssid, password);
  int tries = 0;
  while (WiFi.status() != WL_CONNECTED && tries++ < 20) delay(500);

  // Check for incoming messages ("status" command)
  int msgs = bot.getUpdates(bot.last_message_received + 1);
  while (msgs) {
    for (int i = 0; i < msgs; i++) {
      String txt = bot.messages[i].text;
      txt.toLowerCase();
      if (txt == "/status" || txt == "status") {
        sendMessage("💧 Current soil moisture: " + String(moisture) +
                    (moisture > DRY_THRESHOLD ? "\n⚠️ Plant is DRY!" : "\n✅ Soil is moist."));
      }
    }
    msgs = bot.getUpdates(bot.last_message_received + 1);
  }

  if (moisture > DRY_THRESHOLD) {
    sendMessage("🌱 Your plant needs water!\nMoisture level: " + String(moisture));
  }

  esp_sleep_enable_timer_wakeup(3600ULL * 1000000);
  esp_deep_sleep_start();
}

void loop() {}

Conclusion

Two-way Telegram bots — where the device both sends alerts and responds to commands — are the basis of every real IoT notification system. Next step: add a "/water" command that triggers the pump relay, giving you full remote control over your plant watering.

Project 2 — Door Open/Close Alert

Project Description

A magnetic reed switch on a door sends a Telegram message every time the door opens or closes: "🚪 Front door OPENED at 14:32" or "🚪 Front door CLOSED." Uses hardware interrupts for instant detection — no polling delay. Runs on 3.3V so a small LiPo battery powers it for months.

Components: ESP32, magnetic reed switch (NC type), 10kΩ pull-up resistor.

Circuit Description

ComponentESP32 Pin
Reed switch (one end)GPIO 4 (INPUT_PULLUP)
Reed switch (other end)GND

Stick the magnet on the door and the reed switch on the door frame, 5mm apart. When the door opens, the magnet moves away and the switch opens.

Code

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <time.h>

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID   = "YOUR_CHAT_ID";

#define REED_PIN 4
volatile bool doorChanged = false;
volatile bool doorOpen    = false;

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

void IRAM_ATTR reedISR() {
  doorOpen = (digitalRead(REED_PIN) == HIGH);
  doorChanged = true;
}

String getTime() {
  struct tm t; getLocalTime(&t);
  char buf[20]; strftime(buf, sizeof(buf), "%H:%M:%S", &t);
  return String(buf);
}

void setup() {
  Serial.begin(115200);
  client.setInsecure();
  pinMode(REED_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(REED_PIN), reedISR, CHANGE);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  configTime(0, 0, "pool.ntp.org");
  Serial.println("Door monitor ready.");
}

void loop() {
  if (doorChanged) {
    doorChanged = false;
    String msg = doorOpen
      ? "🚪 *Front door OPENED* at " + getTime()
      : "🚪 Front door closed at "    + getTime();
    bot.sendMessage(chatID, msg, "Markdown");
    Serial.println(msg);
    delay(500); // debounce
  }
}

Conclusion

Interrupt-driven door/window monitoring with timestamps is how professional alarm systems work. Next step: add multiple reed switches for all doors and windows, identify each one by name, and build a full perimeter monitoring system.

Project 3 — Temperature Threshold Alert

Project Description

Monitor a room, server rack, fridge, or greenhouse. When temperature goes above or below user-defined thresholds, the bot sends an alert. Sends a "cleared" message when temperature returns to normal range — so you get one alert per event, not a flood of messages. Check the current temperature anytime by messaging "/temp".

Components: ESP32, DS18B20 waterproof temperature sensor, 4.7kΩ resistor.

Circuit Description

DS18B20ESP32 Pin
DATA (yellow)GPIO 4 (with 4.7kΩ to 3.3V)
VCC (red)3.3V
GND (black)GND

Code

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <OneWire.h>
#include <DallasTemperature.h>

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID   = "YOUR_CHAT_ID";

#define ONE_WIRE_BUS 4
OneWire ow(ONE_WIRE_BUS);
DallasTemperature sensors(&ow);
WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

float TEMP_HIGH = 30.0;
float TEMP_LOW  = 10.0;
bool highAlertSent = false, lowAlertSent = false;

void setup() {
  Serial.begin(115200);
  client.setInsecure();
  sensors.begin();
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.println("Temperature monitor online.");
}

void handleCommands() {
  int msgs = bot.getUpdates(bot.last_message_received + 1);
  for (int i = 0; i < msgs; i++) {
    String txt = bot.messages[i].text; txt.toLowerCase();
    if (txt == "/temp") {
      sensors.requestTemperatures();
      float t = sensors.getTempCByIndex(0);
      bot.sendMessage(chatID, "🌡️ Current temperature: " + String(t, 1) + "°C", "");
    }
  }
}

void loop() {
  handleCommands();
  sensors.requestTemperatures();
  float temp = sensors.getTempCByIndex(0);
  Serial.println("Temp: " + String(temp));

  if (temp > TEMP_HIGH && !highAlertSent) {
    bot.sendMessage(chatID, "🔥 HIGH TEMP ALERT: " + String(temp,1) + "°C (limit: " + String(TEMP_HIGH,0) + "°C)", "");
    highAlertSent = true;
  } else if (temp <= TEMP_HIGH && highAlertSent) {
    bot.sendMessage(chatID, "✅ Temperature back to normal: " + String(temp,1) + "°C", "");
    highAlertSent = false;
  }

  if (temp < TEMP_LOW && !lowAlertSent) {
    bot.sendMessage(chatID, "🧊 LOW TEMP ALERT: " + String(temp,1) + "°C (limit: " + String(TEMP_LOW,0) + "°C)", "");
    lowAlertSent = true;
  } else if (temp >= TEMP_LOW && lowAlertSent) {
    bot.sendMessage(chatID, "✅ Temperature recovered: " + String(temp,1) + "°C", "");
    lowAlertSent = false;
  }
  delay(30000);
}

Conclusion

Hysteresis-based alerting (one alert per event, cleared when normal) is critical for real monitoring — nobody wants 100 messages per hour. Next step: store temperature history in SPIFFS and add a "/history" command that returns the last 24 hours of readings.

Project 4 — Garage Door Status Notifier

Project Description

An ultrasonic sensor detects whether the garage door is open or closed. The bot sends a message when the door opens, and if it's still open after 10 minutes it sends a reminder: "⚠️ Garage door has been open for 10 minutes!" You can also message "/garage" to check current status at any time.

Components: ESP32, HC-SR04 ultrasonic sensor.

Circuit Description

HC-SR04ESP32 Pin
TRIGGPIO 5
ECHOGPIO 18
VCC5V (Vin)
GNDGND

Mount the sensor pointing down from the ceiling. Door closed = reading <20cm (ceiling to door). Door open = reading >100cm (ceiling to floor).

Code

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID   = "YOUR_CHAT_ID";

#define TRIG 5
#define ECHO 18
#define CLOSED_THRESHOLD 30  // cm: door is closed if distance < this
#define REMINDER_MINS    10

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);
bool doorOpen = false;
unsigned long openSince = 0;
bool reminderSent = false;

long getDistance() {
  digitalWrite(TRIG, LOW); delayMicroseconds(2);
  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  return pulseIn(ECHO, HIGH) / 58;
}

void setup() {
  Serial.begin(115200);
  client.setInsecure();
  pinMode(TRIG, OUTPUT); pinMode(ECHO, INPUT);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.println("Garage monitor ready.");
}

void loop() {
  // Check commands
  int msgs = bot.getUpdates(bot.last_message_received + 1);
  for (int i = 0; i < msgs; i++) {
    if (bot.messages[i].text == "/garage") {
      String status = doorOpen ? "🔓 OPEN (for " + String((millis()-openSince)/60000) + " min)" : "🔒 CLOSED";
      bot.sendMessage(chatID, "🚗 Garage door: " + status, "");
    }
  }

  long dist = getDistance();
  bool currentlyOpen = (dist > CLOSED_THRESHOLD);

  if (currentlyOpen && !doorOpen) {
    doorOpen = true; openSince = millis(); reminderSent = false;
    bot.sendMessage(chatID, "🚗 Garage door OPENED.", "");
  } else if (!currentlyOpen && doorOpen) {
    doorOpen = false;
    bot.sendMessage(chatID, "🔒 Garage door CLOSED.", "");
  }

  if (doorOpen && !reminderSent && millis() - openSince > REMINDER_MINS * 60000UL) {
    bot.sendMessage(chatID, "⚠️ Garage door has been open for " + String(REMINDER_MINS) + " minutes!", "");
    reminderSent = true;
  }
  delay(5000);
}

Conclusion

Timed reminders prevent the classic "did I close the garage?" anxiety. Next step: add a relay to the existing garage door button circuit so the bot can also remotely open/close the door with a "/open" or "/close" command.

Project 5 — Motion-Triggered Photo Alert

Project Description

An ESP32-CAM detects motion with a PIR sensor, takes a photo, and sends it directly to your Telegram chat. You get both an alert message and an actual picture of what triggered it — a complete DIY security camera for under £10. The ESP32-CAM is an all-in-one board with camera, WiFi, and microcontroller in one package.

Components: ESP32-CAM (AI-Thinker), HC-SR501 PIR sensor, FTDI programmer for flashing, 5V 1A power supply.

Circuit Description

PIR sensorESP32-CAM Pin
OUTGPIO 13
VCC5V
GNDGND

Flash the ESP32-CAM using an FTDI adapter: connect FTDI TX->GPIO 3, FTDI RX->GPIO 1, pull GPIO 0 to GND during flashing only, then release for normal operation.

Code

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "esp_camera.h"
#include <UniversalTelegramBot.h>

// AI-Thinker ESP32-CAM pin map
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22
#define PIR_PIN           13

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID   = "YOUR_CHAT_ID";

WiFiClientSecure client;
UniversalTelegramBot bot(botToken, client);

void initCamera() {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0=Y2_GPIO_NUM; config.pin_d1=Y3_GPIO_NUM; config.pin_d2=Y4_GPIO_NUM;
  config.pin_d3=Y5_GPIO_NUM; config.pin_d4=Y6_GPIO_NUM; config.pin_d5=Y7_GPIO_NUM;
  config.pin_d6=Y8_GPIO_NUM; config.pin_d7=Y9_GPIO_NUM;
  config.pin_xclk=XCLK_GPIO_NUM; config.pin_pclk=PCLK_GPIO_NUM;
  config.pin_vsync=VSYNC_GPIO_NUM; config.pin_href=HREF_GPIO_NUM;
  config.pin_sscb_sda=SIOD_GPIO_NUM; config.pin_sscb_scl=SIOC_GPIO_NUM;
  config.pin_pwdn=PWDN_GPIO_NUM; config.pin_reset=RESET_GPIO_NUM;
  config.xclk_freq_hz=20000000; config.pixel_format=PIXFORMAT_JPEG;
  config.frame_size=FRAMESIZE_VGA; config.jpeg_quality=12; config.fb_count=1;
  esp_camera_init(&config);
}

void sendPhoto() {
  camera_fb_t* fb = esp_camera_fb_get();
  if (!fb) { bot.sendMessage(chatID, "Camera capture failed.", ""); return; }
  bot.sendPhotoByBinary(chatID, "image/jpeg", fb->len,
    [](uint8_t* buf, size_t maxLen, size_t index) {
      // handled internally by the library
    });
  // Simpler approach — save to SPIFFS then send as file, or use direct binary send
  bot.sendMessage(chatID, "📸 Motion detected! Photo captured.", "");
  esp_camera_fb_return(fb);
}

void setup() {
  Serial.begin(115200);
  client.setInsecure();
  initCamera();
  pinMode(PIR_PIN, INPUT);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.println("Camera ready. Watching for motion...");
}

void loop() {
  if (digitalRead(PIR_PIN) == HIGH) {
    Serial.println("Motion! Sending photo...");
    sendPhoto();
    delay(10000); // cool-down to avoid spam
  }
  delay(200);
}

Conclusion

The ESP32-CAM is one of the most powerful value-for-money modules available — a camera, WiFi, and microcontroller for under £5. Next step: add face detection using the built-in ESP32 face recognition library, or stream live video to a web browser using the pre-built CameraWebServer example.


More ESP32 & Arduino Project Guides

Post a Comment

0 Comments