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.
Descubre más desde CIBERED
Suscríbete y recibe las últimas entradas en tu correo electrónico.
