¿Cómo usar el protocolo de red ESP MESH para construir una red en malla con placas ESP32 y ESP8266?

Libro Guía del Microcontrolador ESP32

Aprende a usar el protocolo de red ESP-MESH para construir una red en malla con placas ESP32 y ESP8266 NodeMCU. ESP-MESH permite que múltiples dispositivos (nodos) se comuniquen entre sí bajo una única red de área local inalámbrica. Es compatible con las placas ESP32 y ESP8266. En esta guía te mostraremos cómo empezar con ESP-MESH usando el core de Arduino.

Este artículo cubre los siguientes temas: introducción a ESP-MESH, ejemplo básico de ESP-MESH (mensajes broadcast) y intercambio de lecturas de sensores usando ESP-MESH (broadcast).

Arduino IDE

Si quieres programar las placas ESP32 y ESP8266 usando Arduino IDE, debes tener instalados los complementos de ESP32 o ESP8266. Si quieres programar el ESP32/ESP8266 usando VS Code + PlatformIO, sigue el tutorial correspondiente.

Introducción a ESP-MESH

Según la documentación de Espressif:

“ESP-MESH es un protocolo de red construido sobre el protocolo Wi-Fi. ESP-MESH permite que numerosos dispositivos (denominados nodos) repartidos por una gran área física (tanto en interiores como en exteriores) se interconecten bajo una única WLAN (Red de Área Local Inalámbrica). ESP-MESH es auto-organizado y auto-reparable, lo que significa que la red puede construirse y mantenerse de forma autónoma.”

Arquitectura de red Wi-Fi tradicional

En una arquitectura de red Wi-Fi tradicional, un único nodo (punto de acceso – normalmente el router) está conectado a todos los demás nodos (estaciones). Cada nodo puede comunicarse con los demás usando el punto de acceso. Sin embargo, esto está limitado a la cobertura Wi-Fi del punto de acceso. Cada estación debe estar en el rango para conectarse directamente al punto de acceso. Esto no ocurre con ESP-MESH.

Arquitectura de red ESP-MESH

Con ESP-MESH, los nodos no necesitan conectarse a un nodo central. Los nodos son responsables de retransmitir las transmisiones de los demás. Esto permite que múltiples dispositivos se repartan por una gran área física. Los nodos pueden auto-organizarse y hablar dinámicamente entre ellos para asegurar que el paquete llegue a su nodo de destino final. Si algún nodo se elimina de la red, es capaz de auto-organizarse para asegurar que los paquetes lleguen a su destino.

La librería painlessMesh

La libería painlessMesh nos permite crear una red en malla con las placas ESP8266 y/o ESP32 de forma sencilla.

“painlessMesh es una red ad-hoc verdadera, lo que significa que no se requiere planificación, controlador central ni router. Cualquier sistema de 1 o más nodos se auto-organizará en una malla completamente funcional. El tamaño máximo de la malla está limitado (creemos) por la cantidad de memoria en el heap que se puede asignar al búfer de sub-conexiones y, por lo tanto, debería ser bastante alto.”

Instalar la librería painlessMesh

Puedes instalar painlessMesh a través del Gestor de Librerías de Arduino. Ve a Herramientas > Gestionar Librerías. Busca “painlessmesh” e instala la librería. Estamos usando la versión 1.4.5.

Esta librería necesita algunas dependencias de otras librerías. Debería aparecer una nueva ventana pidiéndote que instales las dependencias que falten. Selecciona “Install all”. Si esta ventana no aparece, necesitarás instalar las siguientes dependencias manualmente: ArduinoJson (by bblanchon), TaskScheduler, ESPAsyncTCP (ESP8266) y AsyncTCP (ESP32).

Si estás usando PlatformIO, añade las siguientes líneas al archivo platformio.ini para añadir las librerías y cambiar la velocidad del monitor.

Para el ESP32:

monitor_speed = 115200
lib_deps = painlessmesh/painlessMesh @ ^1.4.5
    ArduinoJson
    arduinoUnity
    TaskScheduler
    AsyncTCP

Para el ESP8266:

monitor_speed = 115200
lib_deps = painlessmesh/painlessMesh @ ^1.4.5
    ArduinoJson
    TaskScheduler
    ESPAsyncTCP

Ejemplo básico de ESP-MESH (mensajes broadcast)

Para empezar con ESP-MESH, primero experimentaremos con el ejemplo básico de la librería. Este ejemplo crea una red en malla en la que todas las placas difunden mensajes a todas las demás placas.

Hemos probado este ejemplo con cuatro placas (dos ESP32 y dos ESP8266). Puedes añadir o quitar placas. El código es compatible tanto con las placas ESP32 como con las ESP8266.

Importante: Si estás usando un ESP32, necesitas degradar el complemento de placas ESP32 a la versión 2.0.X. En este momento, la librería painlessMesh no es compatible con la versión 3.X.

Código del ejemplo básico de painlessMesh

Copia el siguiente código en tu Arduino IDE (código de los ejemplos de la librería). El código es compatible tanto con las placas ESP32 como con las ESP8266.

/*
  Rui Santos
  Detalles completos del proyecto en https://RandomNerdTutorials.com/esp-mesh-esp32-esp8266-painlessmesh/
  
  Este es un ejemplo simple que usa la librería painlessMesh: https://github.com/gmag11/painlessMesh/blob/master/examples/basic/basic.ino
*/

#include "painlessMesh.h"

#define   MESH_PREFIX     "whateverYouLike"
#define   MESH_PASSWORD   "somethingSneaky"
#define   MESH_PORT       5555

Scheduler userScheduler; // para controlar tu tarea personal
painlessMesh  mesh;

// Prototipo de usuario
void sendMessage() ; // Prototipo para que PlatformIO no se queje

Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );

void sendMessage() {
  String msg = "Hi from node1";
  msg += mesh.getNodeId();
  mesh.sendBroadcast( msg );
  taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
}

// Necesario para la librería painless
void receivedCallback( uint32_t from, String &msg ) {
  Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
}

void newConnectionCallback(uint32_t nodeId) {
    Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
}

void changedConnectionCallback() {
  Serial.printf("Changed connections\n");
}

void nodeTimeAdjustedCallback(int32_t offset) {
    Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

void setup() {
  Serial.begin(115200);

//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // todos los tipos activados
  mesh.setDebugMsgTypes( ERROR | STARTUP );  // establecer antes de init() para que puedas ver los mensajes de inicio

  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);

  userScheduler.addTask( taskSendMessage );
  taskSendMessage.enable();
}

void loop() {
  // también ejecutará el planificador del usuario
  mesh.update();
}

Antes de subir el código, puedes configurar las variables MESH_PREFIX (es como el nombre de la red MESH) y MESH_PASSWORD (puedes poner lo que quieras). Después, recomendamos que cambies la siguiente línea para cada placa para identificar fácilmente el nodo que envió el mensaje. Por ejemplo, para el nodo 1, cambia el mensaje así:

String msg = "Hi from node 1 ";

Cómo funciona el código

Empieza incluyendo la librería painlessMesh.

Detalles de la MESH

Después, se añaden los detalles de la malla. El MESH_PREFIX se refiere al nombre de la malla. Puedes cambiarlo por lo que quieras. El MESH_PASSWORD, como su nombre indica, es la contraseña de la malla. Puedes cambiarla por lo que quieras. Todos los nodos de la malla deben usar el mismo MESH_PREFIX y MESH_PASSWORD. El MESH_PORT se refiere al puerto TCP en el que quieres que se ejecute el servidor de la malla. El valor por defecto es 5555.

Planificador

Se recomienda evitar el uso de delay() en el código de la red en malla. Para mantener la malla, algunas tareas deben realizarse en segundo plano. Usar delay() detendrá estas tareas y puede hacer que la malla pierda estabilidad o se desmorone. En su lugar, se recomienda usar TaskScheduler para ejecutar tus tareas, que es lo que se usa en painlessMesh internamente. La siguiente línea crea un nuevo Scheduler llamado userScheduler.

painlessMesh

Se crea un objeto painlessMesh llamado mesh para manejar la red en malla.

Crear tareas

Se crea una tarea llamada taskSendMessage responsable de llamar a la función sendMessage() cada segundo mientras el programa esté en ejecución.

Enviar un mensaje a la malla

La función sendMessage() envía un mensaje a todos los nodos de la red (broadcast). El mensaje contiene el texto “Hi from node 1” seguido del ID del chip de la placa. Para difundir un mensaje, simplemente usa el método sendBroadcast() en el objeto mesh y pasa como argumento el mensaje (msg) que quieres enviar. Cada vez que se envía un nuevo mensaje, el código cambia el intervalo entre mensajes (de uno a cinco segundos).

Funciones de callback de la malla

Después, se crean varias funciones de callback que se llamarán cuando ocurran eventos específicos en la malla:

receivedCallback(): imprime el remitente del mensaje (from) y el contenido del mensaje (msg.c_str()). newConnectionCallback(): se ejecuta cada vez que un nuevo nodo se une a la red. Esta función simplemente imprime el ID del chip del nuevo nodo. Puedes modificar la función para hacer cualquier otra tarea. changedConnectionCallback(): se ejecuta cada vez que cambia una conexión en la red (cuando un nodo se une o sale de la red). nodeTimeAdjustedCallback(): se ejecuta cuando la red ajusta el tiempo, para que todos los nodos estén sincronizados. Imprime el offset.

La función setup()

En el setup(), se inicializa el Monitor Serie. Se eligen los tipos de mensajes de depuración deseados. Se inicializa la malla con los detalles definidos antes. Se asignan todas las funciones de callback a sus eventos correspondientes. Finalmente, se añade la función taskSendMessage al userScheduler. El planificador es responsable de manejar y ejecutar las tareas en el momento adecuado. Finalmente, se habilita taskSendMessage para que el programa empiece a enviar los mensajes a la malla.

Para mantener la malla en funcionamiento, añade mesh.update() al loop().

Demostración

Sube el código proporcionado a todas tus placas. No olvides modificar el mensaje para identificar fácilmente el nodo remitente. Con las placas conectadas a tu ordenador, abre una conexión serie con cada placa. Puedes usar el Monitor Serie, o puedes usar un software como PuTTY y abrir múltiples ventanas para todas las placas.

Deberías ver que todas las placas reciben los mensajes de las demás. Por ejemplo, el Nodo 1 recibe los mensajes de los Nodos 2, 3 y 4. También deberías ver otros mensajes cuando hay cambios en la malla: cuando una placa sale o se une a la red.

Intercambiar lecturas de sensores usando ESP-MESH

En este siguiente ejemplo, intercambiaremos lecturas de sensores entre 4 placas (puedes usar un número diferente de placas). Cada placa recibe las lecturas de las otras placas.

Como ejemplo, intercambiaremos lecturas de un sensor BME280, pero puedes usar cualquier otro sensor.

Componentes necesarios

Para este ejemplo necesitas: 4 placas ESP (ESP32 o ESP8266), 4 sensores BME280, protoboard y cables jumper.

Librería Arduino_JSON

En este ejemplo, intercambiaremos las lecturas de los sensores en formato JSON. Para facilitar el manejo de variables JSON, usaremos la librería Arduino_JSON. Puedes instalar esta librería en el Gestor de Librerías de Arduino IDE. Ve a Programa > Incluir Librería > Gestionar Librerías y busca el nombre de la librería.

Si estás usando VS Code con PlatformIO, incluye las librerías en el archivo platformio.ini según corresponda para ESP32 o ESP8266.

Esquema de conexión

Conecta el sensor BME280 a los pines I2C por defecto del ESP32 o ESP8266. Para el ESP32: VCC a 3,3 V, GND a GND, SCL a GPIO 22, SDA a GPIO 21. Para el ESP8266 NodeMCU: VCC a 3,3 V, GND a GND, SCL a D1 (GPIO 5), SDA a D2 (GPIO 4).

Código – Lecturas de sensores broadcast con ESP-MESH

Sube el siguiente código a cada una de tus placas. Este código lee y difunde las lecturas actuales de temperatura, humedad y presión a todas las placas de la red en malla. Las lecturas se envían como una cadena JSON que también contiene el número de nodo para identificar la placa remitente.

/*
  Rui Santos
  Detalles completos del proyecto en https://RandomNerdTutorials.com/esp-mesh-esp32-esp8266-painlessmesh/
*/

#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "painlessMesh.h"
#include <Arduino_JSON.h>

// Detalles de la MESH
#define   MESH_PREFIX     "RNTMESH" // nombre para tu MESH
#define   MESH_PASSWORD   "MESHpassword" // contraseña para tu MESH
#define   MESH_PORT       5555 // puerto por defecto

// Objeto BME en los pines I2C por defecto
Adafruit_BME280 bme;

// Número para este nodo
int nodeNumber = 2;

// String para enviar a otros nodos con las lecturas de los sensores
String readings;

Scheduler userScheduler; // para controlar tu tarea personal
painlessMesh  mesh;

// Prototipo de usuario
void sendMessage() ; // Prototipo para que PlatformIO no se queje
String getReadings(); // Prototipo para enviar lecturas de sensores

// Crea tareas: para enviar mensajes y obtener lecturas;
Task taskSendMessage(TASK_SECOND * 5 , TASK_FOREVER, &sendMessage);

String getReadings () {
  JSONVar jsonReadings;
  jsonReadings["node"] = nodeNumber;
  jsonReadings["temp"] = bme.readTemperature();
  jsonReadings["hum"] = bme.readHumidity();
  jsonReadings["pres"] = bme.readPressure()/100.0F;
  readings = JSON.stringify(jsonReadings);
  return readings;
}

void sendMessage () {
  String msg = getReadings();
  mesh.sendBroadcast(msg);
}

// Inicia BME280
void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }  
}

// Necesario para la librería painless
void receivedCallback( uint32_t from, String &msg ) {
  Serial.printf("Received from %u msg=%s\n", from, msg.c_str());
  JSONVar myObject = JSON.parse(msg.c_str());
  int node = myObject["node"];
  double temp = myObject["temp"];
  double hum = myObject["hum"];
  double pres = myObject["pres"];
  Serial.print("Node: ");
  Serial.println(node);
  Serial.print("Temperature: ");
  Serial.print(temp);
  Serial.println(" C");
  Serial.print("Humidity: ");
  Serial.print(hum);
  Serial.println(" %");
  Serial.print("Pressure: ");
  Serial.print(pres);
  Serial.println(" hpa");
}

void newConnectionCallback(uint32_t nodeId) {
  Serial.printf("New Connection, nodeId = %u\n", nodeId);
}

void changedConnectionCallback() {
  Serial.printf("Changed connections\n");
}

void nodeTimeAdjustedCallback(int32_t offset) {
  Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

void setup() {
  Serial.begin(115200);
  
  initBME();

  //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // todos los tipos activados
  mesh.setDebugMsgTypes( ERROR | STARTUP );  // establecer antes de init() para que puedas ver los mensajes de inicio

  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);

  userScheduler.addTask(taskSendMessage);
  taskSendMessage.enable();
}

void loop() {
  // también ejecutará el planificador del usuario
  mesh.update();
}

El código es compatible tanto con las placas ESP32 como con las ESP8266.

Cómo funciona el código

Librerías

Empieza incluyendo las librerías requeridas: Adafruit_Sensor y Adafruit_BME280 para interactuar con el sensor BME280; la librería painlessMesh para manejar la red en malla; y Arduino_JSON para crear y manejar cadenas JSON fácilmente.

Detalles de la malla

Inserta los detalles de la malla en las siguientes líneas. MESH_PREFIX se refiere al nombre de la malla. Puedes cambiarlo por lo que quieras. MESH_PASSWORD, como su nombre indica, es la contraseña de la malla. Todos los nodos de la malla deben usar el mismo MESH_PREFIX y MESH_PASSWORD. MESH_PORT se refiere al puerto TCP en el que quieres que se ejecute el servidor de la malla. El valor por defecto es 5555.

BME280

Crea un objeto Adafruit_BME280 llamado bme en los pines por defecto del ESP32 o ESP8266. En la variable nodeNumber inserta el número de nodo para tu placa. Debe ser un número diferente para cada placa. La variable readings se usará para guardar las lecturas que se enviarán a las otras placas.

Planificador

La siguiente línea crea un nuevo Scheduler llamado userScheduler.

painlessMesh

Crea un objeto painlessMesh llamado mesh para manejar la red en malla.

Crear tareas

Crea una tarea llamada taskSendMessage responsable de llamar a la función sendMessage() cada cinco segundos mientras el programa esté en ejecución.

getReadings()

La función getReadings() obtiene las lecturas de temperatura, humedad y presión del sensor BME280 y concatena toda la información, incluyendo el número de nodo, en una variable JSON llamada jsonReadings. La variable jsonReadings se convierte en una cadena JSON usando el método stringify() y se guarda en la variable readings. Esta variable es devuelta por la función.

Enviar un mensaje a la malla

La función sendMessage() envía la cadena JSON con las lecturas y el número de nodo (getReadings()) a todos los nodos de la red (broadcast).

Iniciar el sensor BME280

La función initBME() inicializa el sensor BME280.

Funciones de callback de la malla

Se crean varias funciones de callback que se llamarán cuando ocurra algún evento en la malla:

receivedCallback(): imprime el remitente del mensaje (from) y el contenido del mensaje (msg.c_str()). El mensaje llega en formato JSON, así que podemos acceder a las variables con JSON.parse(). Finalmente, imprime toda la información en el Monitor Serie. newConnectionCallback(): se ejecuta cada vez que un nuevo nodo se une a la red. changedConnectionCallback(): se ejecuta cada vez que cambia una conexión en la red. nodeTimeAdjustedCallback(): se ejecuta cuando la red ajusta el tiempo para que todos los nodos estén sincronizados.

La función setup()

En el setup(), se inicializa el Monitor Serie. Se llama a initBME() para inicializar el sensor BME280. Se eligen los tipos de mensajes de depuración deseados. Se inicializa la malla con los detalles definidos antes. Se asignan todas las funciones de callback a sus eventos correspondientes. Finalmente, se añade taskSendMessage al userScheduler y se habilita para que el programa empiece a enviar los mensajes a la malla. Para mantener la malla en funcionamiento, añade mesh.update() al loop().

Demostración

Después de subir el código a todas tus placas (cada placa con un número de nodo diferente), deberías ver que cada placa está recibiendo los mensajes de las otras placas. Verás las lecturas de los sensores de los nodos 2, 3 y 4 en el Monitor Serie del nodo 1.

Conclusión: lo que has aprendido

Esperamos que te haya gustado esta introducción rápida al protocolo de red ESP-MESH. Puedes echar un vistazo a la librería painlessMesh para más ejemplos.

ESP-MESH es la solución perfecta cuando necesitas cubrir un área grande con múltiples nodos y no quieres depender de un router central. La capacidad de auto-organización y auto-reparación hace que la red sea extremadamente robusta: si un nodo falla, los demás se reorganizan automáticamente para mantener la comunicación. Combinado con el bajo coste de los ESP32 y ESP8266, ESP-MESH te permite construir redes de sensores distribuidas, sistemas de iluminación inteligente o cualquier proyecto IoT que requiera cobertura extensa sin infraestructura compleja.

Vistas: 0

Descubre más desde CIBERED

Suscríbete y recibe las últimas entradas en tu correo electrónico.

Scroll al inicio