Bot de Telegram con ESP32: alertas y control de LED y relé
Crea un bot de Telegram con ESP32: token con BotFather, chat ID, UniversalTelegramBot, alertas de movimiento y gas, relé por comando y respuesta solo a tu ID.
Un bot de Telegram con ESP32 permite que la placa ESP32 de tu casa te envíe mensajes al móvil y ejecute las órdenes que tú le mandes. Cuando hay movimiento en la puerta o se detecta gas en la cocina, te llega una notificación al instante; escribes "/ac" y un relé enciende la lámpara, escribes "/kapat" y la apaga. No necesitas programar tu propia app, abrir puertos en el router ni pagar un servicio en la nube: basta con la Bot API gratuita de Telegram y el Wi-Fi integrado del ESP32.
En esta guía montarás desde cero un sistema que funciona: crearás un bot con BotFather y obtendrás su token, averiguarás tu chat ID, instalarás las librerías UniversalTelegramBot y ArduinoJson y enviarás tu primer mensaje desde el ESP32. Después añadirás alertas de movimiento y gas con un sensor PIR y un sensor de gas MQ-2, y el control de un LED y un relé con comandos de Telegram. También veremos cómo hacer que el bot responda solo a tu ID, cómo funciona el ajuste del certificado, qué cambia en el ESP8266 y en la Raspberry Pi Pico W y cuáles son los errores más habituales.

¿Qué es un bot de Telegram con ESP32 y cómo funciona?
En Telegram, un bot es una cuenta especial que maneja un programa en lugar de una persona; en nuestro proyecto ese programa lo ejecuta el ESP32. El ESP32 y tu móvil nunca se conectan directamente: los dos hablan con el servidor de Telegram. Cuando le escribes "/ac" al bot desde el móvil, el mensaje espera en el servidor. Cada segundo, el ESP32 envía una petición HTTPS cifrada (getUpdates) a api.telegram.org preguntando "¿hay mensajes nuevos para mí?", recoge el mensaje y lo ejecuta. Para enviar una alerta hace una petición sendMessage en sentido contrario. Como siempre es el ESP32 quien abre la conexión, no necesitas redirigir puertos ni tener IP fija: la placa funciona en cualquier red Wi-Fi de 2,4 GHz con salida a internet.
| Concepto | ¿Para qué sirve? | Ejemplo |
|---|---|---|
| BotFather | El bot de Telegram para crear y gestionar bots | /newbot, /setcommands, /revoke |
| Token del bot | La contraseña del bot; tu código se conecta a Telegram con ella | 1234567890:AAE... (número, dos puntos, cadena larga) |
| Chat ID | El número de la persona o grupo que recibe el mensaje | Persona: 123456789, grupo: -1001234567890 |
| getUpdates | Descarga del servidor los mensajes nuevos enviados al bot | bot.getUpdates(...) |
| sendMessage | Envía un mensaje al chat ID indicado | bot.sendMessage(CHAT_ID, "Hola") |
| Comando | Mensaje corto que empieza por / y que el bot entiende | /ac, /kapat, /durum |
¿Por qué Telegram? Comparación con otras formas de recibir avisos
| Método | Ventajas | Inconvenientes | Ideal para |
|---|---|---|---|
| Bot de Telegram | Gratis, avisos al instante, comandos en ambos sentidos, sin abrir puertos | Sin gráficas ni panel ya hecho | Alarmas y control remoto sencillo |
| Blynk IoT | Botones, indicadores y gráficas listos para usar | Límites de mensajes y dispositivos en el plan gratuito | Paneles de monitorización de sensores |
| Correo electrónico (SMTP) | Funciona en cualquier móvil, admite informes largos | Con retraso, difícil recibir comandos | Informes diarios |
| SMS (SIM800L) | No necesita Wi-Fi ni internet | Coste de la SIM y de los SMS, módulo extra | Casas de campo o almacenes sin internet |
| Servidor web en el ESP32 | Totalmente local y gratuito | Normalmente solo accesible desde la misma red, sin avisos | Panel de control dentro de casa |
En resumen, Telegram es la opción más fácil y barata para proyectos del tipo "avísame cuando pase algo y haz esto cuando te mande un comando". Si quieres ver los datos de los sensores en gráficas, te encajará mejor la guía de Blynk con ESP32; si necesitas avisos desde un lugar sin Wi-Fi, échale un vistazo a la guía del módulo GSM SIM800L.
Paso 1: crear el bot con BotFather y obtener el token
- Abre Telegram en el móvil o el ordenador, escribe BotFather en el buscador y elige la cuenta oficial, la que tiene la marca azul de verificación junto al nombre. Ojo con las cuentas falsas de nombre parecido.
- Pulsa Iniciar (Start) y envía el comando /newbot.
- BotFather te pide primero el nombre visible del bot; escribe el que quieras, por ejemplo "Alarma Casa".
- Después te pide un nombre de usuario. Debe ser único, usar solo letras latinas, números y guiones bajos, y terminar en bot, por ejemplo alarma_casa_bot. Si ya está cogido, añade un número al final y prueba otra vez.
- BotFather responde con "Done! Congratulations on your new bot.", el enlace a tu bot y su token. El token tiene la forma 1234567890:AAE...: un número, dos puntos y una cadena larga de caracteres. Cópialo entero y guárdalo en un lugar seguro.
- Toca el enlace que te envió BotFather para abrir tu bot y pulsa Iniciar. Este paso es obligatorio: en Telegram los bots no pueden enviar el primer mensaje a un usuario, así que siempre tienes que empezar tú la conversación.
Paso 2: ¿cómo saber tu chat ID de Telegram?
El token del bot responde a "¿qué bot?", y el chat ID a "¿quién recibe el mensaje?". Cada usuario, grupo y canal de Telegram tiene un identificador numérico fijo, que no es lo mismo que tu nombre de usuario con @. Hay tres formas sencillas de averiguar tu propio chat ID:
- Con un bot de identificación: busca en Telegram un bot como @userinfobot y pulsa Iniciar; te envía tu número en la línea Id. Estos bots no son tuyos, solo te muestran tu ID.
- Con el navegador: primero envía cualquier mensaje a tu propio bot. Luego abre api.telegram.org/botTOKEN/getUpdates en el navegador del ordenador (pega tu token en lugar de TOKEN y deja la palabra "bot" delante). En el texto que aparece, el número de "chat":{"id":123456789 es tu chat ID. Si la página muestra una lista result vacía, vuelve a escribir al bot y recarga la página.
- Con el ESP32: cuando cargues el código de comandos de más abajo, el ID de cualquier persona no autorizada se imprime en el Monitor Serie con la línea "unauthorized ID". Si quieres añadir a un familiar, pídele que escriba al bot y lee allí su número.
Paso 3: instalar UniversalTelegramBot y ArduinoJson
- Si todavía no tienes el paquete de placas ESP32 en el Arduino IDE, instálalo primero; los pasos están en ¿Qué es el ESP32?.
- Abre Herramientas > Administrar bibliotecas, escribe UniversalTelegramBot en el buscador e instala la librería de Brian Lough. Los códigos de esta guía se han compilado con UniversalTelegramBot 1.3.0, ArduinoJson 7 y la versión 2.0.11 del paquete de placas ESP32.
- Si el instalador te pregunta por las dependencias, elige Install all; si no te pregunta, busca ArduinoJson en la misma ventana e instala aparte la librería de Benoit Blanchon.
- En Archivo > Ejemplos > UniversalTelegramBot > ESP32 encontrarás ejemplos listos como FlashLED; los códigos de esta guía son versiones simplificadas de ellos con seguridad añadida.
UniversalTelegramBot está escrita con la sintaxis de la versión 6 de ArduinoJson, pero compila sin problemas con ArduinoJson 7. Si has activado los avisos del compilador en el Arduino IDE verás líneas como "'DynamicJsonDocument' is deprecated: use JsonDocument instead"; son advertencias, no errores, y el programa funciona con normalidad. Si tienes problemas para instalar librerías, consulta la guía Cómo añadir una librería en Arduino.
Materiales y esquema de conexión
Para el proyecto completo necesitas una placa de desarrollo ESP32, un sensor de movimiento PIR HC-SR501, un módulo sensor de gas MQ-2, un módulo de relé de 5 V, una resistencia de 10 kΩ y otra de 20 kΩ, una protoboard y cables jumper. Como LED usaremos el LED azul integrado del ESP32 en el GPIO 2. Para probar los dos primeros pasos solo necesitas el ESP32 y un cable USB; los sensores puedes añadirlos después.




| Conexión | ESP32 | ESP8266 (NodeMCU) | Raspberry Pi Pico W |
|---|---|---|---|
| PIR VCC | VIN (5V) | VIN (5V) | VBUS (5V) |
| PIR OUT | GPIO 27 | D5 (GPIO 14) | GP16 |
| MQ-2 VCC | VIN (5V) | VIN (5V) | VBUS (5V) |
| MQ-2 AO (con divisor de 10 kΩ / 20 kΩ) | GPIO 34 | A0 | GP26 (ADC0) |
| Relé VCC | VIN (5V) | VIN (5V) | VBUS (5V) |
| Relé IN | GPIO 26 | D1 (GPIO 5) | GP15 |
| LED | GPIO 2 (integrado) | D4 (integrado, se enciende con LOW) | "LED" (integrado) |
| Todas las GND | GND | GND | GND |
Cuida también la alimentación: solo el calentador del MQ-2 consume unos 150 mA y la bobina del relé unos 70 mA, mientras que el ESP32 puede llegar brevemente a 250 mA cuando transmite por Wi-Fi. El puerto USB del ordenador no siempre da para todo eso, y la placa puede reiniciarse justo al enviar un mensaje. Usa un adaptador de 5 V de al menos 1 A y un buen cable; si el problema sigue, lee ESP32 Brownout y Guru Meditation. Puedes ver todos los pines de tu placa en la herramienta Pinout.
Primera prueba: enviar un mensaje del ESP32 a Telegram
Antes de pasar a los sensores, comprobemos el token y el chat ID con el código más sencillo posible. En el código de abajo, cambia el nombre de la red Wi-Fi, la contraseña y las líneas BOT_TOKEN y CHAT_ID por tus datos y cárgalo en la placa. Después abre el Monitor Serie a 115200 baudios:
1#include <WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5
6const char* WIFI_SSID = "AgAdi"; // Wi-Fi adın (yalnız 2,4 GHz) / your Wi-Fi name (2.4 GHz only)
7const char* WIFI_PASS = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
8#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // BotFather'ın verdiği token / token from BotFather
9#define CHAT_ID "123456789" // kendi Telegram ID'n / your own Telegram ID
10
11WiFiClientSecure client; // şifreli (HTTPS) bağlantı / encrypted (HTTPS) connection
12UniversalTelegramBot bot(BOT_TOKEN, client);
13
14void setup() {
15 Serial.begin(115200);
16 WiFi.mode(WIFI_STA);
17 WiFi.begin(WIFI_SSID, WIFI_PASS);
18 Serial.print("Wi-Fi'ye baglaniyor / connecting to Wi-Fi");
19 while (WiFi.status() != WL_CONNECTED) {
20 delay(500);
21 Serial.print(".");
22 }
23 Serial.println();
24 Serial.println(WiFi.localIP());
25
26 client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // api.telegram.org kök sertifikası / root certificate of api.telegram.org
27
28 if (bot.sendMessage(CHAT_ID, "Merhaba! ESP32 çevrimiçi / Hello! ESP32 is online")) {
29 Serial.println("Mesaj gonderildi / message sent");
30 } else {
31 Serial.println("Mesaj gonderilemedi / message failed");
32 }
33}
34
35void loop() {
36}En unos segundos tu bot debería enviarte "Hello! ESP32 is online" por Telegram y el Monitor Serie debería mostrar "Mesaj gonderildi / message sent". sendMessage() devuelve true cuando el mensaje se entrega; si ves "message failed", el problema está en la conexión Wi-Fi, el token, el chat ID o el ajuste del certificado. Antes de culpar al código, abre api.telegram.org/botTOKEN/getMe en el navegador: con un token válido verás "ok":true y el nombre de tu bot; con uno incorrecto, "Unauthorized".
Ajuste del certificado: ¿setCACert o setInsecure?
Telegram solo acepta HTTPS, por eso el código usa WiFiClientSecure en lugar de un WiFiClient normal. En una conexión cifrada, el ESP32 debe asegurarse de que el servidor con el que habla es de verdad api.telegram.org; lo hace comprobando el certificado del servidor con el certificado raíz que está en lo alto de la cadena. UniversalTelegramBot incluye ese certificado raíz listo para usar con el nombre TELEGRAM_CERTIFICATE_ROOT. La cadena de certificados de Telegram llega hasta Go Daddy Root Certificate Authority - G2, válido hasta finales de 2037; si algún día Telegram cambia de proveedor de certificados, habrá que actualizar también la librería.
| Ajuste | Qué hace | Seguridad | Cuándo usarlo |
|---|---|---|---|
| client.setCACert(TELEGRAM_CERTIFICATE_ROOT) | Cifra la conexión y verifica la identidad del servidor | Alta | Siempre; es el ajuste recomendado |
| client.setInsecure() | Cifra la conexión pero no comprueba quién es el servidor | Media: no detecta un servidor falso intermedio | Prueba rápida para descartar problemas de certificado |
| Ninguno de los dos | Con los núcleos ESP32 actuales la conexión no llega a establecerse | No funciona | Nunca: no sale ningún mensaje y tampoco aparece error en el Monitor Serie |
| ESP8266: setTrustAnchors(&cert) | El equivalente en el ESP8266; el certificado se pasa como X509List | Alta | NodeMCU y Wemos D1 Mini; necesita la hora correcta |
Controlar un LED y un relé con comandos de Telegram (/ac, /kapat)
Ahora hagamos el bot bidireccional. El código de abajo pregunta a Telegram una vez por segundo si hay mensajes nuevos; si el texto es /ac ("encender" en turco) enciende el relé y el LED, /kapat ("apagar") los apaga, /durum ("estado") informa del estado actual y /start envía la lista de comandos. Puedes renombrarlos como /encender y /apagar, pero ten en cuenta que los comandos de Telegram solo admiten letras latinas minúsculas, números y guiones bajos, con un máximo de 32 caracteres. Un comando con tilde o eñe, como "/apagar_baño", no se reconoce como comando pulsable.
1#include <WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5
6const char* WIFI_SSID = "AgAdi";
7const char* WIFI_PASS = "Sifre123";
8#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
9#define CHAT_ID "123456789" // yalnız bu ID'ye yanıt verilir / only this ID gets answers
10
11const int LED_PIN = 2; // çoğu DevKit'te dahili LED / onboard LED on most DevKits
12const int ROLE_PIN = 26; // röle modülünün IN pini / relay module IN pin
13const bool ROLE_AKTIF_LOW = true; // çoğu modül LOW'da çeker / most modules switch on with LOW
14
15const unsigned long KONTROL_ARALIGI = 1000; // Telegram'a saniyede bir sor / ask Telegram once per second
16unsigned long sonKontrol = 0;
17bool roleAcik = false;
18
19WiFiClientSecure client;
20UniversalTelegramBot bot(BOT_TOKEN, client);
21
22void roleYaz(bool acik) {
23 roleAcik = acik;
24 if (ROLE_AKTIF_LOW) digitalWrite(ROLE_PIN, acik ? LOW : HIGH);
25 else digitalWrite(ROLE_PIN, acik ? HIGH : LOW);
26 digitalWrite(LED_PIN, acik ? HIGH : LOW); // LED röle durumunu gösterir / LED mirrors the relay
27}
28
29void mesajlariIsle(int adet) {
30 for (int i = 0; i < adet; i++) {
31 String chatId = bot.messages[i].chat_id;
32 String metin = bot.messages[i].text;
33
34 // Güvenlik: tanımadığın kişiye cevap verme / security: never answer strangers
35 if (chatId != CHAT_ID) {
36 Serial.println("Yetkisiz ID / unauthorized ID: " + chatId);
37 continue;
38 }
39
40 int at = metin.indexOf('@'); // gruptaki "/ac@bot_adi" -> "/ac" / "/ac@bot_name" in groups -> "/ac"
41 if (at > 0) metin = metin.substring(0, at);
42
43 if (metin == "/start") {
44 String yardim = "Merhaba " + bot.messages[i].from_name + "!\n";
45 yardim += "/ac - röleyi aç / relay ON\n";
46 yardim += "/kapat - röleyi kapat / relay OFF\n";
47 yardim += "/durum - son durum / status";
48 bot.sendMessage(chatId, yardim);
49 } else if (metin == "/ac") {
50 roleYaz(true);
51 bot.sendMessage(chatId, "Röle AÇIK / relay ON");
52 } else if (metin == "/kapat") {
53 roleYaz(false);
54 bot.sendMessage(chatId, "Röle KAPALI / relay OFF");
55 } else if (metin == "/durum") {
56 bot.sendMessage(chatId, roleAcik ? "Röle AÇIK / relay ON" : "Röle KAPALI / relay OFF");
57 } else {
58 bot.sendMessage(chatId, "Bilinmeyen komut, /start yaz / unknown command, send /start");
59 }
60 }
61}
62
63void setup() {
64 Serial.begin(115200);
65 pinMode(LED_PIN, OUTPUT);
66 pinMode(ROLE_PIN, OUTPUT);
67 roleYaz(false); // açılışta röle kapalı / relay off at boot
68
69 WiFi.mode(WIFI_STA);
70 WiFi.begin(WIFI_SSID, WIFI_PASS);
71 while (WiFi.status() != WL_CONNECTED) delay(500);
72 Serial.println(WiFi.localIP());
73
74 client.setCACert(TELEGRAM_CERTIFICATE_ROOT);
75 bot.sendMessage(CHAT_ID, "Bot hazır, /start yaz / bot ready, send /start");
76}
77
78void loop() {
79 if (millis() - sonKontrol > KONTROL_ARALIGI) {
80 int adet = bot.getUpdates(bot.last_message_received + 1); // yeni mesaj var mı? / any new messages?
81 while (adet) {
82 mesajlariIsle(adet);
83 adet = bot.getUpdates(bot.last_message_received + 1);
84 }
85 sonKontrol = millis();
86 }
87}| Línea | Función |
|---|---|
| bot.getUpdates(bot.last_message_received + 1) | Pide los mensajes posteriores al último procesado y devuelve cuántos han llegado; el +1 evita que vuelva el mismo mensaje |
| bot.messages[i].chat_id | El ID del chat del que viene el mensaje; la respuesta va allí y el control de seguridad lo usa |
| bot.messages[i].text | El texto recibido; con él comparamos el comando |
| metin.indexOf('@') | Corta la parte @nombre_bot que Telegram añade a los comandos en los grupos |
| ROLE_AKTIF_LOW | La mayoría de los módulos de relé se activan con LOW; si el tuyo se activa con HIGH, ponlo en false |
| KONTROL_ARALIGI | Cada cuánto se consulta; 1000 ms responde rápido, 3000–5000 ms carga menos la red |
Seguridad: que el bot solo responda a tu ID
En Telegram, los nombres de usuario de los bots son públicos y se pueden buscar. Si alguien adivina alarma_casa_bot o lo ve en una captura y le escribe /ac, un ESP32 sin control también obedecerá esa orden. Por eso la línea más importante del código es la comprobación chatId != CHAT_ID. Tu chat ID no tiene por qué ser secreto; la protección viene de que el propio Telegram verifica quién envía cada mensaje y nadie puede suplantar el ID de otra persona. No contestar a desconocidos también es una decisión consciente: mientras el bot calla, nadie sabe siquiera que está funcionando.
- Usa una lista blanca: guarda los ID autorizados en un array, ignora todos los demás mensajes y añade a los familiares uno a uno.
- En grupos, comprueba la persona: en un grupo el chat ID es el del grupo; si solo ciertas personas deben dar órdenes, compara from_id en lugar de chat_id.
- Guarda el token en un archivo aparte: pon el token y la contraseña del Wi-Fi en un archivo como secrets.h y déjalo fuera cuando compartas tu código.
- Descarta los comandos antiguos: un /ac enviado durante un corte de luz no debería ejecutarse en un momento inesperado cuando la placa vuelva; la limpieza inicial del código completo se encarga de ello.
- Estado inicial seguro: arranca siempre con el relé apagado; es muy importante con cargas como calefactores o bombas.
- No dejes lo crítico solo en manos del bot: en una cerradura o una caldera piensa qué puede provocar una orden equivocada y añade un mensaje de confirmación o un límite de tiempo.
Alarma de movimiento y gas: proyecto completo con avisos por Telegram
Ahora juntamos todas las piezas. El código de abajo te envía un aviso de Telegram con la hora cuando el sensor PIR detecta movimiento y cuando la lectura de gas del MQ-2 supera el umbral. Al mismo tiempo escucha los comandos /ac, /kapat, /alarm_ac, /alarm_kapat y /durum, muestra un menú de botones en la parte inferior de la pantalla cuando escribes /start y añade los comandos al botón Menú de Telegram. Para los detalles de cada sensor, consulta las guías del sensor de movimiento PIR y del sensor de gas MQ-2.
1#include <WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5#include <time.h>
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
10#define CHAT_ID "123456789" // bildirimlerin gideceği ID / the ID that receives alerts
11
12// Komut verebilecek kişiler; aile üyesi eklemek için: { CHAT_ID, "222222222" }
13// People allowed to send commands; to add a family member: { CHAT_ID, "222222222" }
14const char* YETKILI[] = { CHAT_ID };
15const int YETKILI_SAYISI = sizeof(YETKILI) / sizeof(YETKILI[0]);
16
17const int LED_PIN = 2; // dahili LED / onboard LED
18const int ROLE_PIN = 26; // röle modülü IN / relay module IN
19const int PIR_PIN = 27; // PIR sensörü OUT / PIR sensor OUT
20const int GAZ_PIN = 34; // MQ-2 AO, gerilim bölücü üzerinden / MQ-2 AO through a voltage divider
21const bool ROLE_AKTIF_LOW = true; // çoğu modül LOW'da çeker / most modules switch on with LOW
22
23const int GAZ_ESIK = 1800; // temiz havadaki değere göre ayarla / tune it from your clean-air reading
24const int GAZ_FARK = 200; // histerezis payı / hysteresis margin
25const unsigned long HAREKET_BEKLEME = 60000; // iki hareket bildirimi arası en az 60 sn / at least 60 s between motion alerts
26const unsigned long GAZ_ISINMA = 120000; // MQ-2 ilk 2 dakika ısınır / MQ-2 warms up for the first 2 minutes
27const unsigned long KONTROL_ARALIGI = 1000; // Telegram'a saniyede bir sor / poll Telegram once per second
28
29WiFiClientSecure client;
30UniversalTelegramBot bot(BOT_TOKEN, client);
31
32bool alarmKurulu = true; // hareket alarmı açık mı / is the motion alarm armed
33bool roleAcik = false;
34bool gazVar = false;
35unsigned long sonKontrol = 0;
36unsigned long sonHareket = 0;
37
38String saat() { // "14:05:31" biçiminde saat / time as "14:05:31"
39 struct tm t;
40 if (!getLocalTime(&t, 100)) return "--:--:--";
41 char buf[9];
42 strftime(buf, sizeof(buf), "%H:%M:%S", &t);
43 return String(buf);
44}
45
46bool yetkiliMi(const String& id) {
47 for (int i = 0; i < YETKILI_SAYISI; i++) {
48 if (id == YETKILI[i]) return true;
49 }
50 return false;
51}
52
53void roleYaz(bool acik) {
54 roleAcik = acik;
55 if (ROLE_AKTIF_LOW) digitalWrite(ROLE_PIN, acik ? LOW : HIGH);
56 else digitalWrite(ROLE_PIN, acik ? HIGH : LOW);
57}
58
59String durumMetni() {
60 String s = "Röle / relay: ";
61 s += roleAcik ? "AÇIK / ON" : "KAPALI / OFF";
62 s += "\nAlarm: ";
63 s += alarmKurulu ? "KURULU / ARMED" : "KAPALI / DISARMED";
64 s += "\nGaz / gas: " + String(analogRead(GAZ_PIN));
65 s += "\nWi-Fi: " + String(WiFi.RSSI()) + " dBm";
66 return s;
67}
68
69void mesajlariIsle(int adet) {
70 for (int i = 0; i < adet; i++) {
71 String chatId = bot.messages[i].chat_id;
72 String metin = bot.messages[i].text;
73
74 if (!yetkiliMi(chatId)) { // yabancıya sessiz kal / stay silent to strangers
75 Serial.println("Yetkisiz ID / unauthorized ID: " + chatId);
76 continue;
77 }
78 int at = metin.indexOf('@'); // "/ac@bot_adi" -> "/ac"
79 if (at > 0) metin = metin.substring(0, at);
80
81 if (metin == "/ac") {
82 roleYaz(true);
83 bot.sendMessage(chatId, "Röle AÇIK / relay ON");
84 } else if (metin == "/kapat") {
85 roleYaz(false);
86 bot.sendMessage(chatId, "Röle KAPALI / relay OFF");
87 } else if (metin == "/alarm_ac") {
88 alarmKurulu = true;
89 bot.sendMessage(chatId, "Hareket alarmı kuruldu / motion alarm armed");
90 } else if (metin == "/alarm_kapat") {
91 alarmKurulu = false;
92 bot.sendMessage(chatId, "Hareket alarmı kapatıldı / motion alarm disarmed");
93 } else if (metin == "/durum") {
94 bot.sendMessage(chatId, durumMetni());
95 } else { // /start ve diğer her şey: tuş menüsü / /start and anything else: button menu
96 String klavye = "[[\"/ac\",\"/kapat\"],[\"/alarm_ac\",\"/alarm_kapat\"],[\"/durum\"]]";
97 bot.sendMessageWithReplyKeyboard(chatId, "Komut seç / choose a command", "", klavye, true);
98 }
99 }
100}
101
102void setup() {
103 Serial.begin(115200);
104 pinMode(LED_PIN, OUTPUT);
105 pinMode(ROLE_PIN, OUTPUT);
106 pinMode(PIR_PIN, INPUT);
107 roleYaz(false); // açılışta röle kapalı / relay off at boot
108
109 WiFi.mode(WIFI_STA);
110 WiFi.begin(WIFI_SSID, WIFI_PASS);
111 while (WiFi.status() != WL_CONNECTED) {
112 delay(500);
113 Serial.print(".");
114 }
115 Serial.println(WiFi.localIP());
116
117 client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // api.telegram.org kök sertifikası / root certificate of api.telegram.org
118 bot.maxMessageLength = 4000; // büyük mesajlarda takılmayı önler / prevents hanging on big messages
119 configTime(3 * 3600, 0, "pool.ntp.org"); // Türkiye UTC+3; kendi saat dilimini yaz / Turkey is UTC+3, set your own zone
120
121 // Telegram'daki Menü düğmesi; komutlar küçük harf, Türkçe karakter yok
122 // The Menu button in Telegram; commands are lowercase with no special letters
123 bot.setMyCommands("[{\"command\":\"ac\",\"description\":\"Röleyi aç\"},"
124 "{\"command\":\"kapat\",\"description\":\"Röleyi kapat\"},"
125 "{\"command\":\"alarm_ac\",\"description\":\"Hareket alarmını kur\"},"
126 "{\"command\":\"alarm_kapat\",\"description\":\"Hareket alarmını kapat\"},"
127 "{\"command\":\"durum\",\"description\":\"Durumu göster\"}]");
128
129 // Kart kapalıyken gelen eski komutları atla / skip old commands sent while the board was off
130 while (bot.getUpdates(bot.last_message_received + 1)) { }
131
132 bot.sendMessage(CHAT_ID, "Sistem açıldı / system started " + saat());
133}
134
135void loop() {
136 unsigned long simdi = millis();
137
138 // 1) Hareket alarmı / motion alarm
139 if (alarmKurulu && digitalRead(PIR_PIN) == HIGH && simdi - sonHareket > HAREKET_BEKLEME) {
140 sonHareket = simdi;
141 digitalWrite(LED_PIN, HIGH);
142 bot.sendMessage(CHAT_ID, "Hareket algılandı! / motion detected! " + saat());
143 digitalWrite(LED_PIN, LOW);
144 }
145
146 // 2) Gaz alarmı: ısınmadan sonra, susturulamaz / gas alarm: after warm-up, cannot be muted
147 if (simdi > GAZ_ISINMA) {
148 int gaz = analogRead(GAZ_PIN); // 0-4095
149 if (!gazVar && gaz > GAZ_ESIK) {
150 gazVar = true;
151 bot.sendMessage(CHAT_ID, "GAZ ALARMI! / GAS ALARM! " + String(gaz) + " " + saat());
152 } else if (gazVar && gaz < GAZ_ESIK - GAZ_FARK) {
153 gazVar = false;
154 bot.sendMessage(CHAT_ID, "Gaz normale döndü / gas back to normal: " + String(gaz));
155 }
156 }
157
158 // 3) Telegram komutları / Telegram commands
159 if (simdi - sonKontrol > KONTROL_ARALIGI && WiFi.status() == WL_CONNECTED) {
160 int adet = bot.getUpdates(bot.last_message_received + 1);
161 while (adet) {
162 mesajlariIsle(adet);
163 adet = bot.getUpdates(bot.last_message_received + 1);
164 }
165 sonKontrol = millis();
166 }
167}| Ajuste | Valor | Explicación |
|---|---|---|
| GAZ_ESIK | 1800 | Valor ADC en bruto de 0 a 4095; fíjalo a partir de tu medida en aire limpio |
| GAZ_FARK | 200 | La lectura debe bajar esto por debajo del umbral antes de avisar de "vuelta a la normalidad"; evita avisos que van y vienen en el límite |
| HAREKET_BEKLEME | 60000 ms | Deja al menos 60 segundos entre dos avisos de movimiento |
| GAZ_ISINMA | 120000 ms | El MQ-2 da valores altos mientras se calienta; el gas no se comprueba durante los 2 primeros minutos |
| KONTROL_ARALIGI | 1000 ms | Cada cuánto se pregunta a Telegram por mensajes nuevos |
| bot.maxMessageLength | 4000 | La respuesta más larga que lee la librería; por defecto, 1500 |
Las dos alarmas del código funcionan de forma distinta a propósito. La de movimiento está protegida por un tiempo de espera: un sensor PIR se dispara una y otra vez mientras alguien camina por la habitación, y sin esa espera tu móvil se llenaría de decenas de mensajes por minuto. Como el contador empieza en cero, el primer aviso llega como pronto 60 segundos después de arrancar la placa, lo que coincide además con el minuto aproximado que necesita un sensor PIR para estabilizarse al encenderse. La de gas, en cambio, usa histéresis: avisa una vez cuando la lectura supera el umbral, solo dice "vuelta a la normalidad" cuando baja 200 unidades por debajo y, por seguridad, no se puede silenciar con /alarm_kapat. Telegram tampoco permite mensajes demasiado seguidos: si intentas enviar más de un mensaje por segundo al mismo chat o más de 20 por minuto a un grupo, recibirás el error "Too Many Requests: retry after".
Bot de Telegram con el ESP8266 (NodeMCU)
La misma librería funciona también en las placas ESP8266 NodeMCU y Wemos D1 Mini; la lógica de comandos y alarmas es idéntica y solo cambia la parte de la conexión. Se usa ESP8266WiFi.h en lugar de WiFi.h, el certificado raíz se pasa como X509List a setTrustAnchors() y, como el ESP8266 comprueba las fechas de validez del certificado, hay que obtener la hora por NTP antes de conectar. Sin la hora, todas las conexiones fallan en silencio. El LED integrado del NodeMCU está en el pin D4 y se enciende con LOW, y solo hay una entrada analógica, A0.
1#include <ESP8266WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5#include <time.h>
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
10#define CHAT_ID "123456789"
11
12X509List cert(TELEGRAM_CERTIFICATE_ROOT); // ESP8266'da kök sertifika böyle verilir / how ESP8266 takes the root certificate
13WiFiClientSecure client;
14UniversalTelegramBot bot(BOT_TOKEN, client);
15
16void setup() {
17 Serial.begin(115200);
18 configTime(0, 0, "pool.ntp.org"); // sertifika kontrolü için saat şart / the clock is needed to check the certificate
19 client.setTrustAnchors(&cert);
20
21 WiFi.mode(WIFI_STA);
22 WiFi.begin(WIFI_SSID, WIFI_PASS);
23 while (WiFi.status() != WL_CONNECTED) delay(500);
24
25 time_t simdi = time(nullptr); // NTP saatini bekle / wait for the NTP time
26 while (simdi < 24 * 3600) {
27 delay(100);
28 simdi = time(nullptr);
29 }
30 bot.sendMessage(CHAT_ID, "ESP8266 çevrimiçi / ESP8266 is online");
31}
32
33void loop() {
34}Bot de Telegram con la Raspberry Pi Pico W y MicroPython
La Raspberry Pi Pico W no necesita ninguna librería aparte: con el módulo requests de MicroPython puedes enviar peticiones directamente a la Bot API de Telegram. El código de abajo envía un aviso cuando detecta movimiento, maneja con /ac y /kapat el relé conectado a GP15 y el LED integrado de la placa, y solo responde a tu chat ID. Graba un MicroPython actualizado en la Pico W con Thonny y guarda el código como main.py. El módulo requests de MicroPython cifra la conexión pero no verifica el certificado del servidor, así que su nivel de seguridad es el mismo que setInsecure() en el ESP32.
1import network, time
2import requests
3from machine import Pin
4
5WIFI_SSID = "AgAdi" # Wi-Fi adın / your Wi-Fi name
6WIFI_PASS = "Sifre123" # Wi-Fi şifren / your Wi-Fi password
7TOKEN = "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
8CHAT_ID = 123456789 # sayı olarak yaz / write it as a number
9API = "https://api.telegram.org/bot" + TOKEN
10
11led = Pin("LED", Pin.OUT) # Pico W dahili LED / Pico W onboard LED
12role = Pin(15, Pin.OUT, value=1) # aktif LOW röle kapalı başlar / active-LOW relay starts off
13pir = Pin(16, Pin.IN) # PIR OUT
14
15wlan = network.WLAN(network.STA_IF)
16wlan.active(True)
17wlan.connect(WIFI_SSID, WIFI_PASS)
18while not wlan.isconnected():
19 time.sleep(0.5)
20print("IP:", wlan.ifconfig()[0])
21
22def gonder(metin):
23 try:
24 r = requests.post(API + "/sendMessage", json={"chat_id": CHAT_ID, "text": metin})
25 r.close()
26 except Exception as e:
27 print("Gonderilemedi / send failed:", e)
28
29def yeni_mesajlar(offset):
30 try:
31 r = requests.get(API + "/getUpdates?timeout=0&limit=5&offset=%d" % offset)
32 sonuc = r.json().get("result", [])
33 r.close()
34 return sonuc
35 except Exception as e:
36 print("Okunamadi / read failed:", e)
37 return []
38
39# Kart kapalıyken gelen eski komutları atla / skip old commands sent while the board was off
40offset = 0
41eski = yeni_mesajlar(offset)
42while eski:
43 offset = eski[-1]["update_id"] + 1
44 eski = yeni_mesajlar(offset)
45
46gonder("Pico W çevrimiçi / Pico W is online")
47son_hareket = time.ticks_ms() # ilk 60 sn PIR dengelenir / the PIR settles in the first 60 s
48
49while True:
50 if pir.value() and time.ticks_diff(time.ticks_ms(), son_hareket) > 60000:
51 son_hareket = time.ticks_ms()
52 gonder("Hareket algılandı! / motion detected!")
53
54 for u in yeni_mesajlar(offset):
55 offset = u["update_id"] + 1
56 mesaj = u.get("message", {})
57 if mesaj.get("chat", {}).get("id") != CHAT_ID: # yalnız sana yanıt ver / answer only you
58 continue
59 komut = mesaj.get("text", "").split("@")[0]
60 if komut == "/ac":
61 role.value(0) # aktif LOW: 0 = açık / active LOW: 0 = on
62 led.on()
63 gonder("Röle AÇIK / relay ON")
64 elif komut == "/kapat":
65 role.value(1)
66 led.off()
67 gonder("Röle KAPALI / relay OFF")
68 elif komut == "/durum":
69 gonder("Röle / relay: " + ("AÇIK / ON" if role.value() == 0 else "KAPALI / OFF"))
70 time.sleep(1)Ideas de proyectos con un bot de Telegram y ESP32
- Alarma de puertas y ventanas: con un sensor reed o un sensor PIR, envía un mensaje al móvil cuando haya movimiento sin nadie en casa.
- Cámara de seguridad con fotos: haz una foto con una ESP32-CAM al detectar movimiento y envíala a Telegram; los pasos están en la guía ESP32-CAM y Telegram.
- Control de habitación o invernadero: recibe un aviso cuando la temperatura medida con un DHT11 pase de un límite y consulta el valor actual con /durum.
- Recordatorio de riego: recibe un aviso cuando un sensor de humedad del suelo marque seco y activa la bomba unos segundos con un comando como /sula ("regar").
- Aviso de incendio: combina un sensor de llama con el MQ-2 para vigilar los dos peligros a la vez.
- Sensor de buzón a pilas: deja el ESP32 en deep sleep, despiértalo al abrirse la tapa, envía un único mensaje y vuelve a dormirlo.
- Enchufe remoto: enciende y apaga una lámpara o un ventilador desde cualquier lugar del mundo con /ac y /kapat a través de un relé.
Errores comunes del bot de Telegram con ESP32 y soluciones
| Síntoma o mensaje | Causa | Solución |
|---|---|---|
| sendMessage devuelve false y no hay error en el Monitor Serie | Falta la línea setCACert o setInsecure, el token tiene un espacio o el Wi-Fi no está conectado | Añade la línea del certificado, vuelve a copiar el token y comprueba primero la conexión Wi-Fi |
| "Forbidden: bot can't initiate conversation with a user" en la prueba del navegador | Nunca pulsaste Iniciar en el bot o lo bloqueaste | Abre el bot en Telegram y pulsa Iniciar; si lo bloqueaste, desbloquéalo |
| "Bad Request: chat not found" | Chat ID incorrecto, falta el signo menos en un ID de grupo o el bot fue expulsado del grupo | Vuelve a leer el ID con getUpdates y escribe los ID de grupo con el signo menos |
| "Unauthorized" | El token es incorrecto o se cambió con /revoke | Copia el token actual de BotFather y vuelve a cargar el código |
| Envía mensajes pero no responde a los comandos | Un delay() largo en loop(), un chat ID incorrecto o el @nombre_bot añadido al comando en un grupo | Espera con millis(), corta la parte del @ y revisa la línea "unauthorized ID" del Monitor Serie |
| El bot se quedó mudo tras una foto o un sticker | La librería solo lee los primeros 1500 caracteres de la respuesta; una respuesta larga no se puede interpretar y el mismo mensaje vuelve una y otra vez | Añade la línea bot.maxMessageLength = 4000; en setup() |
| La placa ejecuta comandos antiguos al arrancar | Telegram guarda los mensajes no leídos hasta 24 horas | Al arrancar, lee y descarta los mensajes pendientes |
| "Conflict: terminated by other getUpdates request" | El mismo token funciona a la vez en dos dispositivos o en un script de tu ordenador | Crea un bot distinto en BotFather para cada dispositivo |
| "Too Many Requests: retry after 30" | Se envían mensajes demasiado seguidos | Añade un tiempo de espera; como máximo un mensaje por segundo al mismo chat |
| Al compilar, "ArduinoJson.h: No such file or directory" | La librería ArduinoJson no está instalada | Instala ArduinoJson desde el Gestor de bibliotecas |
| La placa se reinicia al enviar un mensaje | La alimentación no aguanta el pico de corriente al transmitir por Wi-Fi (brownout) | Adaptador de 1 A, cable corto de calidad y un condensador de 100–470 µF entre 3V3 y GND |
Con un token, un chat ID y unas pocas líneas de código, tu ESP32 se ha convertido en un pequeño asistente que te informa de lo que pasa en casa y obedece tus órdenes. Puedes ampliar el mismo montaje con sensores de temperatura, de nivel de agua o de puerta y enviar los avisos a un grupo familiar. En la app BlueGrays encontrarás, paso a paso y con esquemas y código, el sistema de alarma de movimiento con ESP32 que avisa por Telegram con PIR, zumbador y LED, la alarma con detector de gas y humo MQ-2 y los proyectos Wi-Fi con ESP32.
Preguntas frecuentes
¿Un bot de Telegram con ESP32 es gratis?
Sí. Crear un bot en Telegram, usar la Bot API y enviar mensajes es totalmente gratuito y no hay cupo mensual de mensajes. Solo hay límites de velocidad: alrededor de un mensaje por segundo al mismo chat y 20 mensajes por minuto a un grupo. Para proyectos caseros, estos límites sobran.
¿Cómo encuentro mi chat ID de Telegram?
Lo más fácil es pulsar Iniciar en un bot de identificación como @userinfobot; te envía tu número de ID. También puedes escribir a tu propio bot, abrir api.telegram.org/botTOKEN/getUpdates en el navegador y leer el valor id de la sección "chat". Los ID de grupo empiezan con un signo menos.
¿Por qué mi ESP32 no envía mensajes a Telegram?
Las causas más habituales son no haber pulsado nunca Iniciar en el bot, un chat ID incorrecto, un espacio en el token o que falte la línea setCACert o setInsecure en el código. Con las direcciones getMe y sendMessage en el navegador puedes probar el token y el chat ID sin depender del código. Comprueba también que la placa esté conectada a una red Wi-Fi de 2,4 GHz.
¿Otra persona puede controlar mi bot de Telegram?
Los nombres de usuario de los bots son públicos, así que si no añades una comprobación de ID en el código, sí: cualquiera que encuentre el bot puede enviarle órdenes. Comparar el chat ID de cada mensaje con el tuyo e ignorar el resto lo impide, porque Telegram verifica la identidad del remitente. No compartas tampoco el token; si se filtra, cámbialo con /revoke en BotFather.
¿Se puede hacer un bot de Telegram con el ESP8266?
Sí. La librería UniversalTelegramBot también funciona en placas ESP8266 como NodeMCU y Wemos D1 Mini. El certificado raíz se pasa como X509List a setTrustAnchors() y hay que obtener la hora por NTP antes de conectar. Como el ESP8266 tiene menos memoria, el ESP32 se maneja mejor con mensajes largos.
¿El ESP32 puede enviar fotos a Telegram?
Sí. Con la función sendPhotoByBinary() de la librería puedes enviar directamente a Telegram una imagen tomada con una ESP32-CAM. Un ESP32 sin cámara puede enviar del mismo modo una imagen guardada en una tarjeta SD, o la dirección de una imagen de internet con sendPhoto(). Los sistemas que mandan una foto al detectar movimiento están entre los proyectos de seguridad más populares.
Proyectos para hacer después de leer esto
Ver todos los proyectos
LED Parpadeante con ESP32
Enseña el uso básico de la salida GPIO con ESP32. Conseguimos un efecto de parpadeo encendiendo y apagando un LED desde el pin digital. Versión para ESP32 del clásico proyecto blink en Arduino UNO; los comandos básicos de blink en el entorno Arduino son los mismos, pero la distribución de pines y los niveles GPIO de 3,3 V del ESP32 difieren de los del Arduino UNO.

Parpadeo del LED interno del ESP32
Parpadeo del LED azul incorporado en el ESP32 DevKit V1. ¡No se necesitan componentes externos! El primer proyecto que debe probar cuando compra un ESP32. GPIO 2 está conectado al LED interno.

Sistema de Alarma de Movimiento ESP32 (PIR + Buzzer + LED + Telegram)
Un sistema inteligente de alarma de seguridad que activa el buzzer y el LED cuando el sensor PIR detecta movimiento, y envía simultáneamente una notificación instantánea al propietario vía Telegram. Cuenta con un tiempo de espera de 30 segundos para limitar la frecuencia de las alarmas repetidas; este mecanismo no comprueba la validez de las detecciones del sensor PIR ni elimina las falsas detecciones. Ideal para seguridad del hogar, vigilancia de garajes, almacenes o salas de trabajo. Con ESP32

Alarma de detector de gas/humo ESP32 MQ-2
En este proyecto, la detección de gases combustibles (GLP, metano, propano) y humo se realiza mediante un módulo sensor de gas MQ-2; al detectarlos, se emite un sonido de alarma similar a una sirena a través de un zumbador pasivo. La salida digital de umbral (DO) del sensor se conecta al GPIO 4 del ESP32; cuando el gas o el humo superan el umbral, DO pasa a LOW y se activa la alarma. La sensibilidad se ajusta con el potenciómetro del módulo. El zumbador se controla con tone().

Iluminación Inteligente ESP32 (PIR + LDR + Relé + OLED)
¡Convierte tu pasillo, baño o despensa en iluminación inteligente con ESP32! Utilizando un sensor de movimiento PIR, un sensor de luz LDR y un módulo de relé, construimos el escenario clásico "la luz se enciende cuando se detecta movimiento en la oscuridad" — si el ambiente ya está iluminado, el movimiento NO activa la luz, ahorrando energía durante el día. La luz permanece encendida durante 30 segundos; si el movimiento continúa, el temporizador se reinicia y la luz permanece encendida más tiem

Iluminación automática con el sensor de movimiento PIR del ESP32
En este proyecto, utilizaremos el sensor de movimiento PIR HC-SR501 para detectar a las personas que entren en la habitación y encender el LED automáticamente. Controlaremos el tiempo de encendido del LED con millis(); cada lectura HIGH del sensor actualizará el instante del último movimiento, y el LED se apagará cuando hayan transcurrido al menos 10 segundos desde la última lectura HIGH. El código actual también espera 50 ms en cada iteración mediante delay(50), por lo que el bucle de la ap

Conexión Wi-Fi STA con ESP32 (con pantalla LCD)
En este proyecto, conectamos el ESP32 a la red Wi-Fi doméstica en modo Station (STA) de forma segura y tolerante a fallos. Separamos el SSID y la contraseña en el archivo secrets.h para evitar que se filtren a Git, configuramos un mecanismo de reconexión automática y mostramos el nivel de señal RSSI y la dirección IP en una pantalla LCD I2C. El LED interno actúa como indicador de estado.
Más artículos
App BlueGrays
Monta este circuito paso a paso en tu teléfono
Esquema, código y lista de piezas en una sola pantalla. Más de 200 proyectos de Arduino, ESP32 y Pico listos para montar.
Escanea con tu teléfono
Apunta la cámara al código y se abrirá la tienda de apps de tu teléfono.
o ábrela en la tienda

