Alvaro Morales
Published © GPL3+

Telegram MQTT bot + ESP32

This is a Telegram Bot to control everything in your home thanks to a Raspberry Pi and an ESP32

IntermediateWork in progress3 hours124
Telegram MQTT bot + ESP32

Things used in this project

Story

Read more

Code

Main

Python
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()

DataBase

Python
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()

MQTT_client

Python
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}")

Telegram_bot

Python
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}")

Confing

Python
# 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"

ESP32MQTT

C/C++
//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
  }
}

Credits

Alvaro Morales

Alvaro Morales

4 projects • 3 followers

Comments