Gateway ESPNOW a MQTT por WiFi

Te traigo mi proyecto de Gateway ESPNOW a MQTT bidireccional con todas las explicaciones necesarias para que puedas utilizar el firmware que he escrito para ESP32 para tu propio uso fácilmente.

Un Gateway sencillo que te permite recibir las señales enviadas por diferentes dispositivos con ESPNOW y reenviarlas mediante WiFi como mensajes MQTT.

Desarrollé este Gateway ESPNOW porque tengo varios proyectos con ESP8266 y ESP32 y funcionamiento con baterías, como por ejemplo CO2 Gadget, en los que utilizo el protocolo ESP-NOW para reducir el consumo de batería y aumentar la autonomía y el alcance.

Una de las ventajas de este proyecto es que utiliza un solo ESP32 tanto para ESPNOW como para WiFi . En muchos sitios dicen que no es posible utilizar ESPNow y WiFi simultáneamente y utilizan dos ESP32 conectados a través de puerto serie, uno para ESPNow y otro para WiFi, pero yo lo llevo haciendo desde hace varios meses con un solo ESP32 sin ningún problema.

EL FIRMWARE DEL GATEWAY ESPNOW ESTÁ EN PREPARACIÓN

El firmware del Gateway ESPNOW estará disponible pronto. Sigo trabajando en él, para que cualquier persona lo pueda utilizar con CO2 Gadget, y todavía está recibiendo muchos cambios y funcionalidades.

Si quieres participar en su beta testing, dímelo a través del grupo de Telegram.

¿Qué es ESP-NOW?

ESP-NOW es un protocolo de comunicaciones vía radio que soportan los ESP8266 y ESP32, desarrollado por Espressif, su fabricante, que permite hacer transmisiones de datos muy rápidas (y cuando digo muy rápidas, digo rapidísimas).

Su principal objetivo es sustituir al Wifi para permitir consumos de energía mucho más bajos y con ello mayor autonomía en dispositivos que funcionan con batería al permitir el envío de datos en pocos milisegundos, en lugar de los varios segundos (de 3 a 12 segundos) que se emplearían en realizarlas con Wifi.

Además de su velocidad hay otro ámbito donde sobresale, y es en el alcance que proporciona. Donde una comunicación wifi puede tener un alcance de 25 a 100 metros, con ESP-NOW podemos obtener fácilmente entre 100 y 500 metros de alcance, e incluso más, dependiendo de las condiciones tanto en un caso como en otro.

Si no conoces ESPNOW te recomiendo que leas primero el siguiente artículo en el que te cuento lo que debes saber sobre él:

Puesta en marcha del gateway ESP-NOW a MQTT

Poner en marcha el Gateway ESP-NOW te resultará muy sencillo siguiendo estas instrucciones.

Primero te explicaré lo básico para que pongas el gateway en marcha y después te voy a comentar varias partes del código para que puedas adaptarlo a tus necesidades fácilmente.

Básicamente, la puesta en marcha del gateway consiste en seguir los siguientes pasos:

  • Conseguir el código fuente del gateway
  • Editar un fichero de texto con la configuración de tu red
  • Grabar el firmware en el ESP32

Estoy trabajando en una serie de mejoras que te permitirán, en muchos casos, instalar el Gateway en tu placa con ESP32 directamente desde el navegador, sin tener que compilar ni instalar nada en tu ordenador (como ya puedes hacer con CO2 Gadget), y configurarlo desde una página web servida por el propio Gateway.

Cuando estas funcionalidades estén disponibles, no tendrás que hacer nada de los procesos indicados aquí abajo si no necesitas configurar tus propias estructuras de datos y utilizas las que ya vienen predefinidas con el Gateway.

Conseguir el código fuente del gateway

El código fuente del gateway lo puedes encontrar en GitHub, concretamente en:

[EN PREPARACIÓN SI QUIERES SER BETATESTER CONTACTAME EN EL GRUPO DE TELEGRAM]

Solo tienes que pulsar en el botón «Code» y seleccionar «Download ZIP».

Una vez lo hayas descargado tendrás que descomprimirlo para extraer su contenido.

Editar el fichero de texto con la configuración de tu red

Para hacértelo más sencillo y no tener que modificar el código del gateway, deberás poner los datos que son propios de tu instalación en un fichero de texto que se llame «credentials.h».

Los datos que son propios de tu instalación y que deberás escribir en este fichero son:

  • SSID de tu red wifi
  • El password para acceder a la red
  • La dirección IP de tu servidor MQTT
  • El HOSTNAME del gateway (nombre que quieres dar a este dispositivo en tu red).

Para que no tengas que partir de cero, entre los archivos que has extraído del ZIP encontrarás uno que se llama «credentials.tpl». Es una plantilla que puedes modificar.

Tienes que copiar el fichero «credentials.tpl» como «credentials.h» y editarlo para introducir tus datos (o renombrar el fichero «credentials.tpl» como «credentials.h» y editarlo).

Compilar y grabar el firmware del Gateway ESP-NOW en el ESP32

Solo te queda compilar y cargar el programa en el ESP.

Si estás utilizando Arduino IDE, en el menú «Programa/Subir» o pulsando Ctrl+U.

Si estás usando PlatformIO, en «Project tasks -> Upload and Monitor».

En ambos casos tendrás primero que seleccionar tu placa ESP32 y el puerto serie dónde está conectada. Entiendo que esto ya sabes hacerlo y no voy a detallar el proceso. Si no supieras hacerlo, tienes muchísimos tutoriales en internet o puedes pedir ayuda en el grupo de Telegram de eMariete.

Ten en cuenta que el gateway ESP-NOW no se puede instalar en una placa con ESP8266 porque no permite la comunicación mediante ESP-NOW y WiFi simultáneamente. Tienes que instalarlo en un ESP32. No lo confundas con los clientes, que pueden ser ESP8266 sin ningún problema, la limitación está solo en el Gateway.

Actualizar el firmware del Gateway ESP-NOW en el ESP-32

El gateway dispone de actualizaciones inalámbricas OTA (Over The Air). Esto te permite actualizar el firmware sin tenerte que conectar físicamente al dispositivo.

Al ser este gateway un dispositivo de «conectar y olvidar», es habitual colocarlo escondido o en sitios con difícil acceso. En estos casos la actualización OTA es especialmente útil, ya que podremos actualizar el firmware mediante su conexión wifi directamente desde el Arduino IDE (o desde PlatformIO).

El código del Gateway ESP-NOW (cómo funciona)

En ocasiones, especialmente si vas a utilizar el gateway con tus propios proyectos, tendrás que modificar el código del gateway para adaptarlo a tus necesidades. Los proyectos de eMariete están soportados directamente y no es necesario que modifiques nada, aunque te invito a hacerlo para adaptarlo aún más a tus gustos y preferencias.

A continuación, te voy a dar algunas pistas para que te sea más sencillo modificar el código y adaptarlo a tus necesidades.

La estructura de datos

De cara a adaptar el gateway a transmisiones con diferentes datos (no se envían los mismos datos, lógicamente, desde un termómetro que desde un sensor de presencia o un medidor de CO2) es importante que comprendas el formato de los mensajes que se envían por ESP-NOW para que puedas crear nuevos formatos de mensaje.

Cuando el sensor (vamos a llamar sensor al emisor que genera los datos y que queremos recibir en el gateway y reenviar mediante MQTT) envía datos por ESP-NOW, lo hace enviando un mensaje (un conjunto que bytes) que sigue una estructura determinada.

Por ejemplo, el medidor de CO2 de ultra bajo consumo de eMariete (en preparación) envía en cada mensaje un paquete de datos con una estructura como esta:

  typedef struct struct_message
  {
    int id;
    float temp;
    float hum;
    uint16_t co2;
    float battery;
    int readingId;
    int command = cmdNone;
    uint16_t parameter = 0;
  } struct_message;

Se trata de una estructura que contiene ocho datos diferentes.

Si te fijas podrás ver que tenemos varios tipos de datos:

  • Datos de apoyo a la transmisión, como son id (que contiene el número de unidad que envía los datos, para que el gateway pueda diferenciar de qué unidad vienen los datos), readingId (que es un número secuencial que se incrementa con cada transmisión y que sirve para que el gateway sepa si se han perdido paquetes).
  • Datos de medidas, como temp, hum, co2 y battery (la medida de la temperatura, la humedad, la concentración de CO2 y el voltaje de la batería).
  • Datos de los comandos procesados: qué comando ha recibido el sensor desde el gateway y con qué parámetro (más sobre esto después).

Si ahora quisiéramos añadir una estructura de datos para ser utilizada por un nodo termómetro, que no mide nada más que la temperatura, podríamos crear una estructura de datos como esta:

  typedef struct struct_message
  {
    int id;
    float temp;
    float battery;
  } struct_message;

Esta estructura tiene solo tres datos diferentes: un dato «id» (para que el gateway sepa qué sensor lo ha enviado), un dato «temp» (que contiene la temperatura) y un dato «battery» (que contiene el voltaje de la batería del sensor).

¿Hubiéramos podido utilizar la estructura que utilizábamos para los medidores de CO2 para el termómetro y de esta forma no hubiéramos necesitado modificar el código? La respuesta es sí, podíamos haberlo hecho, simplemente dejando los datos que no necesitábamos a 0 (hum, co2, readingId, command y parameter).

¿Y entonces, por qué complicarnos la vida con una estructura de datos distinta para el termómetro? Para optimizar la transmisión y de esta forma disminuir el consumo energético y mejorar la autonomía.

Con este cambio hemos reducido el envío de datos de 22 bytes a tan solo 10 bytes. Esto supone haber mejorado la autonomía del sensor alrededor de un 50% (de 90 días a 180 días con la misma batería. [Pendiente de realizar nuevas pruebas y mediciones para poner datos reales y unas gráficas que los apoyen]

Optimizando la estructura de datos

Como hemos visto en el ejemplo anterior, de la estructura de datos para el termómetro, cada byte que enviemos en el mensaje ESP-NOW cuenta y reducirá la autonomía del sensor.

Es importante que los mensajes estén bien optimizados. Por ejemplo, podríamos optimizar la estructura de mensajes del medidor de CO2 de esta manera:

En lugar de utilizar este formato de mensaje

  typedef struct struct_message
  {
    int id;
    float temp;
    float hum;
    uint16_t co2;
    float battery;
    int readingId;
    int command = cmdNone;
    uint16_t parameter = 0;
  } struct_message;

Utilizar este otro:

  typedef struct struct_message
  {
    byte id;
    int temp; // temp * 100;
    byte hum;
    uint16_t co2;
    byte battery;
  } struct_message;

El primer mensaje ocupa 22 bytes, el segundo ocupa solamente 7 bytes. Menos de un tercio de tiempo de transmisión, lo que probablemente pueda incrementar la autonomía entre un 25 y un 50%.

Para ello, lo único que hemos hecho ha sido:

  • Eliminar los datos superfluos que no son imprescindibles para un medidor de CO2
  • Optimizar el tamaño de los datos

Hemos dedicado un byte en vez de dos al id. Con esto hemos reducido el número de sensores de 65536 a 256 pero ¿Realmente tienes más de 256 sensores?

Hemos reducido la temperatura de 4 a 2 bytes, simplemente multiplicándola por 100 para eliminar los decimales sin perder precisión. En el receptor la dividiremos por 100 y volveremos a tener los decimales.

Hemos reducido la humedad de 4 a 2 bytes, redondeando el dato para eliminar los decimales que no suelen ser necesarios cuando se mide humedad (y si los quisiéramos mantener podríamos hacerlo multiplicando por 100, como hemos hecho con la temperatura, y seguirían siendo dos bytes).

Hemos reducido la batería de 4 a 1 byte, enviando el porcentaje en vez del voltaje (si quisiéramos enviar el voltaje en un solo byte también podríamos: por ejemplo, podríamos aplicar en el emisor batería = (voltaje * 100) – 255 y en el receptor voltaje = (bateria * 100 + 255); esto nos permitiría transmitir voltajes de entre 2.55V y 5.1V con dos decimales utilizando un solo byte).

Otra vuelta de tuerca a la estructura de datos

Acabamos de ver un buen ejemplo de optimización, pero podríamos optimizar mucho más…

¿Por qué ceñirnos a los tipos de datos oficiales de Arduino o C++ y a hacer grupos de 1 byte (8 bits)?

Podríamos estructurar y optimizar la información a nivel de bit individual, lo que nos permitiría ahorrar bastante espacio.

  typedef struct struct_message
  {
    byte id:4;
    int temp; // temp * 100;
    byte hum:7;
    uint16_t co2:11;
    byte battery:7;
  } struct_message;

Aquí, por ejemplo, para el id hemos dedicado 4 bits, lo que nos permite tener hasta 16 sensores, que en la mayor parte de casos será suficiente y nos ahorramos 4 bits (y si queremos más le podemos dar 5 bits y podríamos tener 32 sensores y seguir ahorrando 3 bits).

Con 11 bits para el CO2 podríamos enviar valores de hasta 2047 ppm, ahorrándonos 5 bits (y si queremos más le podemos dar 12 bits y podríamos enviar datos de hasta 4095 ppm y seguiríamos ahorrando 4 bits).

Lo importante es que hemos reducido los datos enviados de 56 bits (7 bytes) a solo 50 (un 10% de ahorro). Desde la estructura original hemos reducido ya de 176 bits a solo 50 ¡más de un 70% de ahorro!

Y todavía nos queda espacio para optimizar… ¿y si en vez de usar 16 bits para enviar la temperatura no enviamos un número que sea, por ejemplo, los incrementos de 0.5ºC desde -30ºC? Si le dedicamos 8 bits podríamos enviar la temperatura desde -30ºC hasta 98ºC con una precisión de 0.5ºC ahorrando 8 bits más de los 50.

¿Y si en vez de usar 7 bits para la batería no enviamos los incrementos de 100mV desde 3V? el 12 sería 4.2V y solo necesitaríamos 4 bits, ahorrando otros 4.

¿Y si enviamos un solo dato cada vez? CO2 cada minuto, Temperatura cada 5 minutos (¿para qué la queremos cada minuto en condiciones normales?), la humedad cada 10 minutos y la batería cada 6 horas? Solo necesitaríamos añadir a la estructura dos bits para indicar cuál de los cuatro datos se está enviando. Estaríamos enviando mensajes de solo 20 bits, ¡un ahorro de casi un 88%!

  typedef struct struct_message
  {
    byte id:4;
    int dato;
  } struct_message;

Como ves las posibilidades de optimización son enormes.

Yo no he optimizado mucho la estructura de datos a propósito, para que sea fácil de entender el código para cualquiera.

¿Cómo reenviar mensajes ESP-NOW a MQTT?

Para que el gateway reenvíe a través de MQTT los mensajes que recibe por ESP-NOW, básicamente tiene que hacer lo siguiente:

  • Identificar el mensaje recibido como un mensaje que debe reenviar
  • Extraer los datos de la estructura de datos ESP-NOW (struct_message)
  • Crear el topic y el payload del mensaje a enviar
  • Publicar el mensaje MQTT

Tienes que tener en cuenta que, este gateway ha sido diseñado para optimizar el envío de datos no críticos (se puede perder alguno de vez en cuando sin problemas) y conseguir que los sensores estén despiertos en mínimo tiempo posible, por ese motivo el envío de datos del sensor al gateway no dispone de ningún sistema que asegure que el dato es recibido.

Para aplicaciones más críticas de las que yo he necesitado sería necesario establecer un protocolo de acuse de recibo y reenvío de la información.

Hay que tener en cuenta que cuanto más robusto y más opciones y refinamientos tenga el protocolo más sobrecarga de datos tendrá, con lo que las transmisiones serán más largas y los dispositivos tendrán que estar despiertos más tiempo (esperando mensajes de respuesta, por ejemplo).

En mi caso, si estoy enviando datos de temperatura o CO2 cada minuto, por ejemplo, no me importa que algún mensaje que otro se pierda, ya se actualizará con el siguiente mensaje un minuto más tarde, no es crítico (la experiencia me ha dicho que son muy pocos los que se pierden).

¿Cómo reenviar mensajes MQTT a ESP-NOW?

Para que el gateway reenvíe los mensajes que recibe por MQTT a través de ESP-NOW a un sensor, tiene que hacer lo siguiente:

  • Suscribirse a los topic MQTT que quiera reenviar
  • Cuando recibe un mensaje MQTT extraer los datos del payload
  • Con los datos extraídos, copiar dichos datos a la estructura de datos ESP-NOW (struct_message)
  • Enviar el mensaje ESP-Now en el momento justo en el que el sensor esté despierto y recibiendo.

Como podrás imaginar, enviar datos mediante ESP-NOW a un sensor que normalmente está dormido, se despierta unos pocos milisegundos y se vuelve a dormir no es fácil.

El principal problema es que no sabemos cuándo el sensor está despierto.

Normalmente no tenemos en el sensor un reloj en tiempo real preciso (que nos podría ayudar a sincronizar los envíos).

Lo más que podemos hacer es esperar a que el sensor se comunique con el gateway y mantener el sensor despierto unos milisegundos escuchando; es inmediatamente después de recibir cuando el gateway puede enviar el mensaje, asumiendo que el sensor todavía estará despierto y escuchando.

El sistema de envío de datos de MQTT a ESP-NOW no es perfecto, tal y como está implementado, y espero mejorarlo con el tiempo:

  • Es difícil asegurarse de que el sensor está despierto y recibiendo en el momento en el que el gateway envía su mensaje.
  • No se ha implementado un sistema de acuse de recibo por parte del sensor para asegurarse de que no se pierdan mensajes.
  • No podemos enviar mensajes al sensor en cualquier momento, solo cuando nos envíe un mensaje.
  • Si dejamos el sensor despierto demasiado tiempo a la espera de recibir un hipotético mensaje desperdiciaremos un montón de energía. Si lo dejamos despierto demasiado poco tiempo tendremos problemas de sincronización.

Ampliaciones y mejoras del gateway ESP-NOW

He tratado de programar este gateway de una forma muy sencilla, para que cualquier aficionado con un poquito de idea de programación pueda entender el código y modificarlo con relativa facilidad para implementar cualquier otro tipo de pasarela.

Entre las pasarelas que podrías incluir en el gateway, tendrías:

  • Grabación directa de datos en BBDD InfluxDB, MySQL, MariaDB y similares.
  • Envío de notificaciones y alertas mediante email, Telegram, Pushover, etc.
  • Integración en cualquier sistema de control, domótica, inmótica, PLC, etc.
  • Cualquier otro tipo de implementación RF, con el hardware RF necesario (LoRa, LoRaWan, BLE, BLE Mesh, Bluetooth Smart, etc.).

Mejoras en el sistema de datos y estructura ESP-NOW

Una mejora importante con la que todavía no me he puesto (porque no la he necesitado) sería un sistema de envío de datos ESP-NOW más flexible, que permita estructuras flexibles (que solo haya que definir en el emisor o sensor) y que incluya, de alguna forma, la configuración MQTT.

El principal problema es la optimización del tamaño de los datos, para evitar que en los mensajes ESP-NOW viaje demasiada información (por ejemplo, evitar que el sensor envíe con cada paquete el topic MQTT y formato del payload MQTT dónde debe ser reenviado).

Te invito a discutir la posible implementación en el grupo de Telegram de eMariete o, si la desarrollas por tu cuenta, hacer un pull request para valorar incluirla en la versión pública del gateway.

5 comentarios en «Gateway ESPNOW a MQTT por WiFi»

  1. Hello, I would like to thank you for an excellent article and good explanations of efficient data formats. I have done some similar work and I freqently use ‘bitfields’ to store system flags for example. Bit fileds could also be used to make very compact data structurs where only 4 or 5 bits of data can represent, for example, a temperature.
    I would be very keen to do some beta testing of your code if it is available. I am only a hobbyist programmer so my interest is only personal. If the code is available could you advise me where it can be downloaded.
    Thanks
    Bob

    Responder
  2. Hello Mariete,

    I would like to implement your solution, but I am strugling with the first step:
    Basically, the implementation of the gateway consists of the following steps:
    «Get the source code of the gateway». Is there a possibility for sharing it?

    It is base on switching between esp-now and standard wifi on the gateway side for sending the MQTT message?

    Kind regards,

    Ibis

    Responder

Deja un comentario