Controlar ESP8266 con Telegram - Ejecutar Bot en NodeMCU ESP8266

Controlar ESP8266 desde Telegram

Ya te enseñé cómo enviar un mensaje simple desde la NodeMCU ESP8266 a Telegram y cómo enviar las mediciones de temperatura y humedad usando un Bot de Telegram. En este post vamos a ver cómo controlar una ESP8266 desde Telegram, encendiendo y apagando un LED con los comandos enviados.

De este modo vas a poder controlar tu tarjeta de desarrollo NodeMCU ESP8266 desde Telegram enviándole comandos para hacer cosas como girar un motor, encender un LED, un relevador, mostrar un mensaje en la LCD, etcétera; aunque en este ejemplo solo trabajaremos con el LED integrado.

Explicación del algoritmo

Básicamente vamos a alojar un Bot de Telegram en la ESP8266 consumiendo la API de Telegram en la ubicación getUpdates, parseando el JSON resultante y revisando el comando. Recomiendo ver el siguiente vídeo para ver cómo instalar los drivers:

Como la API de Telegram se maneja con peticiones HTTP te recomiendo ver cómo hacer peticiones usando un cliente HTTP:

En resumen vamos a ver si hay actualizaciones para el Bot cada cierto tiempo y cuando encontremos una actualización vamos a revisar el comando recibido. A partir de los datos recibidos vas a poder verificar el id de usuario y el contenido del mensaje.

Recibir comandos desde Bot de Telegram

Comenzamos haciendo la petición a la URL para obtener los mensajes que nuestro Bot haya recibido:

String url = "https://api.telegram.org/bot" + tokenTelegram + "/getUpdates?limit=" + String(limite);
if (ultimoIdDeActualizacion != 0)
{
    url += "&offset=" + String(ultimoIdDeActualizacion + 1);
}

if (!clienteHttp.begin(*clienteWifi, url))
{

    Serial.println("Error con clienteHttp.begin");
}
Serial.println("Haciendo petición a " + url);
clienteHttp.addHeader("Content-Type", "application/json", false, true);
int httpCode = clienteHttp.GET();

Al hacer la petición vamos a obtener un JSON en el cuerpo de la respuesta; mismo que debemos decodificar. Aquí he establecido el límite de memoria en 2048; hay que ajustar este parámetro teniendo en cuenta la cantidad de actualizaciones en el límite que enviamos a la API.

Yo prefiero recibir una actualización por petición y si hay más actualizaciones las reviso en el siguiente paso del loop.

String respuestaDelServidor = clienteHttp.getString();
if (httpCode == HTTP_CODE_OK)
{
Serial.println("Petición OK");
DynamicJsonDocument documentoJson(2048);
DeserializationError errorAlDecodificarJson = deserializeJson(documentoJson, respuestaDelServidor);
if (errorAlDecodificarJson)
{
    Serial.print(F("Error al parsear JSON: "));
    Serial.println(errorAlDecodificarJson.c_str());

    return;
}

Ahora que el JSON está decodificado ya tenemos un arreglo que podemos recorrer usando C++. Extraemos las propiedades verificando que el mensaje recibido sea uno de texto y actuamos en consecuencia:

JsonArray actualizaciones = documentoJson["result"].as<JsonArray>();
for (JsonObject actualizacion : actualizaciones)
{
    int idUsuarioRemitente = actualizacion["message"]["from"]["id"];
    int idActualizacion = actualizacion["update_id"];
    if (actualizacion["message"].containsKey("text"))
    {
        const char *contenidoDelMensajeRecibido = actualizacion["message"]["text"];
        // Imprime los valores en el serial
        Serial.print("Id remitente: ");
        Serial.println(idUsuarioRemitente);
        Serial.print("Contenido del mensaje: ");
        Serial.println(contenidoDelMensajeRecibido);
        if (strcmp(contenidoDelMensajeRecibido, "/encenderLed") == 0)
        {
            encenderLed();
        }
        else if (strcmp(contenidoDelMensajeRecibido, "/apagarLed") == 0)
        {
            apagarLed();
        }
        // Aquí puedes manejar más comandos
    }
    ultimoIdDeActualizacion = idActualizacion;
}

El mensaje enviado por los usuarios al Bot está en contenidoDelMensajeRecibido, podemos usar strcmp para comparar la cadena. En mi ejemplo, si el contenido es /encenderLed invoco a la función encenderLed y lo mismo para apagarlo. Puedes evaluar el contenido del mensaje para actuar según sea necesario.

Controlar ESP8266 con Telegram - Ejecutar Bot en NodeMCU ESP8266
Controlar ESP8266 con Telegram – Ejecutar Bot en NodeMCU ESP8266

Por cierto, es necesario comprobar que la propiedad message tiene a su vez la propiedad text con containsKey; ya que si los usuarios envían una imagen o algo que no sea texto entonces esta propiedad no estará definida.

Funcionamiento de getUpdates

Al invocar a la siguiente URL con una petición GET:

https://api.telegram.org/botTU_TOKEN/getUpdates?limit=1&offset=123456789

Vamos a obtener un JSON parecido al siguiente, mismo que es un objeto con la propiedad ok en true si la petición fue exitosa y también la propiedad result que es un arreglo de tipo Update. Cada propiedad de tipo Update tiene propiedades como message y update_id.

{
  "ok": true,
  "result": [
    {
      "update_id": 123456789,
      "message": {
        "message_id": 1234,
        "from": {
          "id": 123455678,
          "is_bot": false,
          "first_name": "Luis",
          "username": "parzibyte",
          "language_code": "es"
        },
        "chat": {
          "id": 12345656,
          "first_name": "Luis",
          "username": "parzibyte",
          "type": "private"
        },
        "date": 123213123,
        "text": "/apagarLed",
        "entities": [
          {
            "offset": 0,
            "length": 10,
            "type": "bot_command"
          }
        ]
      }
    }
  ]
}

Entonces solo es cuestión de recorrer el result y revisar el message.text para el comando recibido desde Telegram y message.from.id para el id de usuario. La propiedad update_id de cada actualización es importante, pues debemos enviar la última update_id en la siguiente petición (en el parámetro offset) para decirle a Telegram que necesitamos actualizaciones más recientes que ese id.

Otro punto importante es el parámetro limit. Con él le decimos a Telegram que solo queremos recibir una actualización por petición, ya que la memoria de la ESP8266 es limitada, por lo que es mejor parsear el JSON de una actualización a la vez. Obviamente puedes cambiar este parámetro y hacer las pruebas necesarias combinándola con la memoria indicada al crear el DynamicJsonDocument.

Poniendo todo junto

Para decodificar el JSON devuelto por la API de Bot de Telegram vas a necesitar la librería ArduinoJson. Yo utilizo PlatformIO y mi platformio.ini queda así:

[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
lib_deps = 
    bblanchon/ArduinoJson@^6.18.5

El código completo para controlar una NodeMCU ESP8266 desde un Bot de Telegram queda como se ve a continuación, solo asegúrate de colocar correctamente las credenciales para la red Wi-Fi y tu Token de Bot de Telegram.

#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>
#include <string.h>
// Obtenido con el @Botfather
const String tokenTelegram = "";
// Red WiFi para conectarse
const char *NOMBRE_RED_WIFI = "";
const char *CLAVE_RED_WIFI = "";

int ultimoIdDeActualizacion = 0;
int limite = 1;

void encenderLed()
{

    // Sí, para encenderlo le mandas un LOW
    digitalWrite(LED_BUILTIN, LOW);
}

void apagarLed()
{

    // Sí, para apagarlo le mandas un HIGH
    digitalWrite(LED_BUILTIN, HIGH);
}
void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    Serial.begin(9600);
    WiFi.mode(WIFI_STA);
    WiFi.begin(NOMBRE_RED_WIFI, CLAVE_RED_WIFI);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(1000);
        Serial.printf(".");
    }
}

void loop()
{
    if ((WiFi.status() != WL_CONNECTED))
    {

        Serial.println("No hay WiFi");
        return;
    }
    std::unique_ptr<BearSSL::WiFiClientSecure> clienteWifi(new BearSSL::WiFiClientSecure);

    clienteWifi->setInsecure();

    HTTPClient clienteHttp;

    String url = "https://api.telegram.org/bot" + tokenTelegram + "/getUpdates?limit=" + String(limite);
    if (ultimoIdDeActualizacion != 0)
    {
        url += "&offset=" + String(ultimoIdDeActualizacion + 1);
    }

    if (!clienteHttp.begin(*clienteWifi, url))
    {

        Serial.println("Error con clienteHttp.begin");
    }
    Serial.println("Haciendo petición a " + url);
    clienteHttp.addHeader("Content-Type", "application/json", false, true);
    int httpCode = clienteHttp.GET();
    if (httpCode > 0)
    {
        String respuestaDelServidor = clienteHttp.getString();
        if (httpCode == HTTP_CODE_OK)
        {
            Serial.println("Petición OK");
            DynamicJsonDocument documentoJson(2048);
            DeserializationError errorAlDecodificarJson = deserializeJson(documentoJson, respuestaDelServidor);
            if (errorAlDecodificarJson)
            {
                Serial.print(F("Error al parsear JSON: "));
                Serial.println(errorAlDecodificarJson.c_str());

                return;
            }
            JsonArray actualizaciones = documentoJson["result"].as<JsonArray>();
            for (JsonObject actualizacion : actualizaciones)
            {
                int idUsuarioRemitente = actualizacion["message"]["from"]["id"];
                int idActualizacion = actualizacion["update_id"];
                if (actualizacion["message"].containsKey("text"))
                {
                    const char *contenidoDelMensajeRecibido = actualizacion["message"]["text"];
                    // Imprime los valores en el serial
                    Serial.print("Id remitente: ");
                    Serial.println(idUsuarioRemitente);
                    Serial.print("Contenido del mensaje: ");
                    Serial.println(contenidoDelMensajeRecibido);
                    if (strcmp(contenidoDelMensajeRecibido, "/encenderLed") == 0)
                    {
                        encenderLed();
                    }
                    else if (strcmp(contenidoDelMensajeRecibido, "/apagarLed") == 0)
                    {
                        apagarLed();
                    }
                    // Aquí puedes manejar más comandos
                }
                ultimoIdDeActualizacion = idActualizacion;
            }
        }
        else
        {
            Serial.printf(
                "Petición realizada pero hubo un error en el servidor. Código HTTP: %d. Respuesta: %s\n",
                httpCode,
                respuestaDelServidor.c_str());
        }
    }
    else
    {
        // Petición Ok pero código no es 200
        Serial.printf("Error haciendo petición: %s\n", clienteHttp.errorToString(httpCode).c_str());
    }
    clienteHttp.end();

    // Esperar 10 segundos antes de enviar el siguiente mensaje
    delay(10000);
}

Por cierto, me parece que ya existe una librería completa para ejecutar Bots de Telegram en Arduino y la ESP8266: https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot. Yo preferí hacerlo manualmente y de manera simple para entender el proceso del funcionamiento de getUpdates.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *