In this project, we will see how to create a Telegram bot that communicates via MQTT to interact with ESP32 boards.The reason for using MQTT is that it is a very lightweight protocol that can be easily supported by an ESP32. On the other hand, the heavier tasks are handled by the Raspberry Pi, which acts as the MQTT broker as well as the server for the bot.
One of the peculiarities of this project is that it has a Keep-Alive function, which means it monitors whether the ESP32 has disconnected and if it has, it notifies you with a Telegram message.
On the other hand, it has the ability to save the temperature, humidity, and pressure data sent by the BME280 sensor every 15 minutes to a database.
Also, we implemented a Watchdog on the ESP32 for avoid blocking execution.
For added security, only the selected person will be able to send messages to the bot.
Steps on Raspberry Pi:1. Install the service
sudo apt install mosquitto mosquitto-clients
2. Enable the service to start automatically
sudo systemctl enable mosquitto
sudo systemctl start mosquitto
3. Check the IP address
hostname -I → this will be the IP of the MQTT server
4. Verify that the service is running
sudo systemctl status mosquitto
We need to see something like this:
First of all, we need to look for BootFather in Telegram:
The commands to set up are the next ones:
/start
/newbot
Now, we need to copy the HTTP API to paste it in "config.py" file.
Now, let's check our personal ID, for avoiding other people to send data to our personal bot.
The commands to set up are the next ones:
/start
/getid
Steps on ESP32:On the other hand, we need to upload the code to the ESP32. The only things we need to edit in the code are the Wi-Fi SSID of your home, the password, and the IP address of your Raspberry Pi.
As you can see, we have a LED neopixel led, so we can control it.
Final result:import sys
import os
import logging
from telegram_bot import start_bot
from mqtt_client import start_mqtt, send_mqtt_command
logging.basicConfig(filename='bot.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def main():
try:
# Cambiar al directorio solo si existe
if os.path.isdir('/home/alvaro/bot'):
os.chdir('/home/alvaro/bot')
else:
logging.error("Directorio no encontrado. Verifica la ruta.")
sys.exit(1)
# Inicia el bot de Telegram y MQTT
bot = start_bot()
start_mqtt(bot)
while True:
try:
print("\nOpciones:")
print("1 - Encender rojo")
print("2 - Encender blanco")
print("3 - Apagar")
print("4 - Temperatura")
print("5 - Led_arcoiris")
print("99 - Salir")
print("")
option = input("Selecciona una opcin: ")
commands = {
'1': 'led_rojo',
'2': 'led_blanco',
'3': 'led_apagar',
'4': 'temperatura_habitacion',
'5': 'led_arcoiris'
}
if option in commands:
send_mqtt_command(commands[option])
elif option == '99':
logging.info("Saliendo del programa.")
break
else:
print("Opcin no vlida")
except KeyboardInterrupt:
logging.info("Interrupcin por teclado.")
break
except Exception as e:
logging.error("Error en el men principal: %s", e)
except Exception as e:
logging.critical("Error en la inicializacin: %s", e)
if __name__ == '__main__':
main()
import sqlite3
def create_db():
with sqlite3.connect('clima_db.db') as conn:
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS clima_db
(Time TEXT, Location TEXT, Temperature REAL, Humidity REAL, Pressure REAL)''')
conn.commit()
def update_db(timestamp, location, temperature, humidity, pressure):
try:
with sqlite3.connect('clima_db.db') as conn:
c = conn.cursor()
c.execute("INSERT INTO clima_db VALUES (?,?,?,?,?)", (timestamp, location, temperature, humidity, pressure))
conn.commit()
except sqlite3.Error as e:
print(f"Error en la base de datos: {e}")
def delete_db(time, location):
try:
with sqlite3.connect('clima_db.db') as conn:
c = conn.cursor()
c.execute("DELETE FROM clima_db WHERE Time = ? AND Location = ?", (time, location))
conn.commit()
except sqlite3.Error as e:
print(f"Error al eliminar de la base de datos: {e}")
def select_db():
with sqlite3.connect('clima_db.db') as conn:
c = conn.cursor()
c.execute("SELECT * FROM clima_db")
return c.fetchall()
import paho.mqtt.client as mqtt
import config
import time
from threading import Timer, Lock
from db import create_db, update_db
import json
client = mqtt.Client()
last_status_time = time.time()
KEEP_ALIVE_INTERVAL = 60 * 60
lock = Lock() # Para asegurar concurrencia
def check_keep_alive(bot):
with lock:
current_time = time.time()
if current_time - last_status_time > KEEP_ALIVE_INTERVAL:
bot.sendMessage(config.PERSONAL_ID, "El servidor de tu habitacin no conecta")
# Reprograma el temporizador de forma segura
Timer(KEEP_ALIVE_INTERVAL, check_keep_alive, [bot]).start()
def on_connect(client, userdata, flags, rc):
try:
if rc == 0:
print("Conectado exitosamente al broker MQTT")
client.subscribe(config.DATA_TOPIC)
client.subscribe(config.STATUS_TOPIC)
client.subscribe(config.CLIMA_TOPIC)
else:
print(f"Conexin fallida, cdigo de error: {rc}")
except Exception as e:
print(f"Error en on_connect: {e}")
def on_message(client, userdata, msg):
global last_status_time
try:
payload = msg.payload.decode()
if msg.topic == config.DATA_TOPIC:
print(f"Datos recibidos de ESP32: {payload}")
if userdata['bot']:
userdata['bot'].sendMessage(config.PERSONAL_ID, payload)
elif msg.topic == config.STATUS_TOPIC:
print(f"Estado recibido del ESP32: {payload}")
last_status_time = time.time()
elif msg.topic == config.CLIMA_TOPIC:
print(f"Estado de clima recibido: {payload}")
try:
clima_data = json.loads(payload)
temperature = clima_data["temperature"]
humidity = clima_data["humidity"]
pressure = clima_data["pressure"]
create_db()
update_db(int(time.time()), 'Habitacin', temperature, humidity, pressure)
except json.JSONDecodeError as e:
print(f"Error al decodificar JSON: {e}")
except Exception as e:
print(f"Error en on_message: {e}")
def send_mqtt_command(command):
try:
client.publish(config.COMMAND_TOPIC, command)
print(f"Comando enviado: {command}")
except Exception as e:
print(f"Error al enviar comando MQTT: {e}")
def start_mqtt(bot):
try:
client.user_data_set({'bot': bot})
client.on_connect = on_connect
client.on_message = on_message
client.connect(config.MQTT_BROKER, config.MQTT_PORT, 60)
client.loop_start()
print('Cliente MQTT conectado y escuchando.')
Timer(KEEP_ALIVE_INTERVAL, check_keep_alive, [bot]).start()
except Exception as e:
print(f"Error en start_mqtt: {e}")
def stop_mqtt():
try:
client.loop_stop()
client.disconnect()
print("Cliente MQTT desconectado.")
except Exception as e:
print(f"Error al detener MQTT: {e}")
import telepot
import config
from mqtt_client import send_mqtt_command, stop_mqtt
import subprocess
def handle_telegram(msg):
chat_id = msg['chat']['id']
command = msg['text']
# Verificacin de usuario autorizado
if chat_id != config.PERSONAL_ID:
try:
bot.sendMessage(chat_id, 'Usuario no autorizado. Contacta el administrador.')
except Exception as e:
print(f"Error al enviar mensaje de usuario no autorizado: {e}")
return
print(f'Comando recibido: {command}')
#Comandos y sus funciones asociadas
commands = {
'/Led_rojo': lambda: send_mqtt_command('led_rojo'),
'/Led_blanco': lambda: send_mqtt_command('led_blanco'),
'/Led_apagar': lambda: send_mqtt_command('led_apagar'),
'/Led_noche': lambda: send_mqtt_command('led_noche'),
'/Temperatura': lambda: send_mqtt_command('temperatura_habitacion'),
'/Led_arcoiris': lambda: send_mqtt_command('led_arcoiris'),
'/Exit': lambda: shutdown_system(chat_id)
}
if command.startswith('/Brillo_'):
brightness_level = command[1:].lower()
send_mqtt_command(brightness_level)
print(f"Cambiando el brillo a {brightness_level}")
return
if command in commands:
try:
commands[command]()
except Exception as e:
print(f"Error al ejecutar el comando {command}: {e}")
bot.sendMessage(chat_id, 'Hubo un problema ejecutando el comando. Intntalo de nuevo.')
else:
bot.sendMessage(chat_id, 'Comando no reconocido. Intenta de nuevo.')
def shutdown_system(chat_id):
try:
bot.sendMessage(chat_id, 'Sistema detenido. Hasta luego!')
print("Apagando el sistema de forma ordenada...")
stop_mqtt()
subprocess.run(["sudo", "shutdown"], check=True)
except Exception as e:
print(f"Error al intentar apagar el sistema: {e}")
def start_bot():
try:
global bot
bot = telepot.Bot(config.TELEGRAM_TOKEN)
bot.message_loop(handle_telegram)
bot.sendMessage(config.PERSONAL_ID, 'Sistema funcionando ')
lista_de_comandos = '\n'.join(['/Led_rojo', '/Led_blanco', '/Led_apagar', '/Temperatura', 'Led_arcoiris'])
bot.sendMessage(config.PERSONAL_ID, f'Lista de comandos disponibles:\n{lista_de_comandos}')
print('Bot de Telegram iniciado y en espera de comandos.')
return bot
except Exception as e:
print(f"Error al iniciar el bot: {e}")
# config.py
# ID de usuario autorizado para Telegram
PERSONAL_ID = "Your_ID"
# Token de acceso para el bot de Telegram
TELEGRAM_TOKEN = 'API_TOKEN'
# Configuracin de conexin para MQTT
MQTT_BROKER = "Raspberry Pi IP"
MQTT_PORT = 1883
# Temas MQTT
DATA_TOPIC = "esp32/data"
COMMAND_TOPIC = "esp32/commands"
STATUS_TOPIC = "esp32/status"
CLIMA_TOPIC = "esp32/clima"
//Board ESP32-WROOM_DA Module
#include <WiFi.h> //MQTT
#include <PubSubClient.h> //MQTT
#include <FastLED.h> //Neopixel
#include <Wire.h> //BME280
//#include <Adafruit_Sensor.h> //BME280
#include <Adafruit_BME280.h> //BME280
#include <esp_task_wdt.h> //Watchdog
// Configuracin FastLED
#define LED_PIN 27
#define NUM_LEDS 115
#define BRIGHTNESS 64
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
#define BME280_ADDRESS 0x76 //Direccin I2C del BME280
// Crear una instancia del sensor BME280
Adafruit_BME280 bme;
//Configuracin WiFi + MQTT
const char* ssid = "YourSSID";
const char* password = "PASSWORD!";
const char* mqtt_server = "Raspberry_PI_IP"; // IP del broker MQTT
//Crear una instancia para el wifi
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0; // Variable para controlar el tiempo del ltimo mensaje de delay no bloqueante
const long publish_interval = 900000; // 900000 Intervalo para enviar mensajes (15 minutos)
bool firstMessageSent = false; //Variable para controlar que el mensaje de funcionamiento solo se enve una vez
void efectoArcoiris()
{
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CHSV((millis() / 10 + i * 10) % 255, 255, 255);
}
FastLED.show();
}
// Funcin de callback cuando se recibe un mensaje MQTT
void callback(char* topic, byte* payload, unsigned int length)
{
String message;
for (int i = 0; i < length; i++)
{
message += (char)payload[i];
}
Serial.println(message);
if (String(topic) == "esp32/commands")
{
if (message == "temperatura_habitacion")
{
float temperature = bme.readTemperature(); // Leer temperatura en C
float humidity = bme.readHumidity(); // Leer humedad en %
float pressure = bme.readPressure() / 100.0F; // Leer presin en hPa
String payload = String("{\"temperature\":") + temperature + ", \"humidity\":" + humidity + ", \"pressure\":" + pressure + "}";
client.publish("esp32/data", payload.c_str());
Serial.println("Datos enviados: " + payload);
}
else if (message == "LED_OFF")
{
digitalWrite(LED_PIN, LOW); // Apagar LED
Serial.println("LED APAGADO");
}
else if (message == "led_rojo")
{
FastLED.clear();
fill_solid(leds, NUM_LEDS, CRGB::Red);
// leds[1] = CRGB::Green; //El segundo led se enciende en verde
FastLED.show();
}
else if (message == "led_blanco")
{
FastLED.clear();
fill_solid(leds, NUM_LEDS, CRGB::White);
FastLED.show();
}
else if (message == "led_apagar")
{
FastLED.clear();
fill_solid(leds, NUM_LEDS, CRGB::Black);
FastLED.show();
}
else if (message.startsWith("brillo_"))
{
int brillo = message.substring(7).toInt(); // "7" es la posicin del primer dgito
brillo = constrain(brillo, 0, 255); //Limitamos el valor entre 0 y 255
FastLED.setBrightness(brillo);
FastLED.show();
Serial.print("Ajustando brillo a: ");
Serial.println(brillo);
}
else if (message == "led_arcoiris")
{
efectoArcoiris();
}
}
}
// Conectar al WiFi
void setup_wifi()
{
delay(10);
Serial.println();
Serial.print("Conectando a ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi conectado");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
}
void setup_bme280()
{
Serial.println("BME280 sensor test");
Wire.begin(21, 22); // SDA (21), SCL (22) // Iniciar la comunicacin I2C
// Iniciar el sensor BME280
if (!bme.begin(BME280_ADDRESS))
{
Serial.println("No se encontr el sensor BME280, verifica la conexin!");
// while (1); //No queremos que sea bloqueante
}
else
{
Serial.println("Sensor BME280 inicializado correctamente.");
}
}
// Conectar al broker MQTT
void reconnect_MQTT_server()
{
while (!client.connected())
{
Serial.print("Intentando conexin MQTT...");
if (client.connect("ESP32Client"))
{
Serial.println("conectado");
client.subscribe("esp32/commands"); // Suscribirse a comandos
}
else
{
Serial.print("fallo, rc=");
Serial.print(client.state());
Serial.println(" intentar de nuevo en 5 segundos");
delay(5000);
}
}
}
void setup()
{
Serial.begin(115200); //Inicializamos el puerto serie
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
//---- Inicializar los LED ----
pinMode(LED_PIN, OUTPUT);
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(15); //256 max
FastLED.clear();
FastLED.show();
setup_bme280();
// Configurar el Watchdog
esp_task_wdt_config_t wdt_config = {
.timeout_ms = 10000, //Timeout de 10 segundos
.idle_core_mask = 0, //Para monitorear todos los ncleos, puedes usar `1` o `2` para monitorear solo un ncleo
.trigger_panic = true //Reiniciar si el watchdog llega a timeout
};
esp_task_wdt_init(&wdt_config);
esp_task_wdt_add(NULL); // Asociar el watchdog a la tarea actual
}
void loop()
{
if (!client.connected())
{
reconnect_MQTT_server();
}
client.loop();
esp_task_wdt_reset(); //Patear el watchdog
if(!firstMessageSent) //Utilizamos una flag ya que es ms eficiente
{
// Publicamos un mensaje oara saber que funciona correctamente
String payload = String("Cliente Habitacin publicando");
client.publish("esp32/data", payload.c_str());
Serial.println(payload);
firstMessageSent = true;
}
unsigned long now = millis();
if (now - lastMsg > publish_interval) // Verificar si ha pasado el intervalo de tiempo para enviar los datos; es como si fuese un delay pero no bloqueante
{
lastMsg = now;
// Leer los datos del sensor
float temperature = bme.readTemperature(); // Leer temperatura en C
float humidity = bme.readHumidity(); // Leer humedad en %
float pressure = bme.readPressure() / 100.0F; // Leer presin en hPa
// Publicar datos de temperatura, humedad y presin
String payload_clima = String("{\"temperature\":") + temperature + ", \"humidity\":" + humidity + ", \"pressure\":" + pressure + "}";
if (client.publish("esp32/clima", payload_clima.c_str())) {
Serial.println("Datos enviados: " + payload_clima);
}
else {
Serial.println("Error al enviar datos del clima");
}
// Publicar datos de keep alive
String payload_keepalive = String("Habitacion");
if (client.publish("esp32/status", payload_keepalive.c_str())){
Serial.println("Datos enviados: " + payload_keepalive);
}
else{
Serial.println("Error al enviar dato de keep alive");
}
esp_task_wdt_reset(); //Patear el watchdog despus de enviar los datos
}
}
Comments