En este tutorial, veremos cómo configurar una red de varios ESP32 utilizando el protocolo ESP-NOW. El ESP32 es una placa de desarrollo con WiFi integrado. Por lo tanto, puede conectarse e intercambiar datos con dispositivos conectados a la misma red.
Hardware
- Ordenador
- NodeMCU ESP32 o NodeMCU ESP8266 o Wemos x3
- Cable USB A macho a Mini B macho x3
Descripción ESP-NOW
ESP-NOW es un protocolo de comunicación desarrollado por Espressif que permite la comunicación inalámbrica entre varios dispositivos sin necesidad de una red específica. Permite intercambiar pequeños paquetes de datos a alta velocidad a través de las bandas de frecuencia de 2,4 GHz, a una distancia de hasta 200 metros. Requiere un emparejamiento inicial, pero una vez realizado la comunicación es persistente y se establece sola al arrancar. Una de las grandes ventajas, además de que utiliza una red dedicada, es que una o varias estaciones ESP32 o ESP8266 pueden conectarse al Wifi en paralelo.
Utilizando este protocolo, es posible crear una red de NodeMCUs que se comunican entre sí.
Código
Vamos a establecer una comunicación bidireccional entre varios ESP32 utilizando la librería espnow.h disponible al instalar el gestor de tarjetas. Definiremos y enviaremos una estructura de datos idéntica para todas las tarjetas con el fin de simplificar el código.
Código Maestro
Para que la tarjeta emisora pueda comunicarse con otra tarjeta, necesita su dirección MAC. Puede recuperar esta dirección cuando la tarjeta receptora se inicia a partir de los mensajes enviados mediante la función setup() (Serial.println(WiFi.macAddress());). Vamos a definir una función que se ejecute después de enviar un mensaje para comprobar que la transmisión se ha realizado correctamente.
No olvides cambiar las direcciones de las tarjetas esclavas correspondientes a las tarjetas que estés utilizando.
#include <esp_now.h> #include <WiFi.h> const char nom[10]="Maestro"; uint8_t broadcastAddress[2][6] = { {0x2C, 0xF4, 0x32, 0x15, 0x52, 0x22}, //station0 {0xA0, 0x20, 0xA6, 0x08, 0x20, 0xD9} //station1 };// REPLACE WITH RECEIVER MAC ADDRESS // Structure example to send data // Must match the receiver structure typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; struct_message myData; struct_message dataRcv; unsigned long previousTime=0; // callbacks for sending and receiving data void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print(F("\r\nMaestro packet sent:\t")); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&dataRcv, incomingData, sizeof(dataRcv)); Serial.print("\r\nBytes received: "); Serial.println(len); Serial.print("From slave: "); Serial.println(dataRcv.a); Serial.println(); } void setup() { // Init Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println(F("Error initializing ESP-NOW")); return; } Serial.print(F("Reciever initilized : ")); Serial.println(WiFi.macAddress()); // Define callback functions esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); // Register peer esp_now_peer_info_t peerInfo; peerInfo.channel = 0; peerInfo.encrypt = false; memcpy(peerInfo.peer_addr, broadcastAddress[0], 6); if (esp_now_add_peer(&peerInfo) != ESP_OK){ Serial.println("Failed to add peer"); return; } memcpy(peerInfo.peer_addr, broadcastAddress[1], 6); if (esp_now_add_peer(&peerInfo) != ESP_OK){ Serial.println("Failed to add peer"); return; } } void loop() { if((millis() -previousTime)>5000){ // Set values to send strcpy(myData.a, nom); myData.b = random(1, 20); myData.c = 1.2; myData.e = false; // Send message via ESP-NOW myData.d = "Esclavo0"; esp_err_t result0 = esp_now_send(broadcastAddress[0], (uint8_t *) &myData, sizeof(myData)); myData.d = "Esclavo1"; esp_err_t result1 = esp_now_send(broadcastAddress[1], (uint8_t *) &myData, sizeof(myData)); previousTime=millis(); } }
Código Slave
En el código ESCLAVO, crearemos una función que se ejecuta cuando se recibe un mensaje. Esta función se utiliza para procesar la información recibida. En este ejemplo, mostramos los datos contenidos en la estructura.
El código Esclavo debe modificarse para cada tarjeta esclava de forma que los datos y el identificador sean diferentes.
#include <esp_now.h> #include <WiFi.h> const char nom[10]="Esclavo0"; uint8_t broadcastAddress[] = {0x3C, 0x61, 0x05, 0x30, 0x0A, 0x28};// REPLACE WITH MASTER MAC ADDRESS //{0xDC, 0x4F, 0x22, 0x58, 0xD2, 0xF5} //station0 // Structure example to send data // Must match the receiver structure typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; struct_message dataSent; struct_message dataRcv; unsigned long previousTime=0; // callbacks for sending and receiving data void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\n"+String(nom)+" packet sent:\t"); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&dataRcv, incomingData, sizeof(dataRcv)); Serial.print("\r\nBytes received: "); Serial.println(len); Serial.print("From: "); Serial.println(dataRcv.a); Serial.print("To: "); Serial.println(dataRcv.d); Serial.print("Sensor: "); Serial.println(dataRcv.b); Serial.print("Status: "); Serial.println(dataRcv.c); Serial.println(); } void setup() { // Init Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println(F("Error initializing ESP-NOW")); return; } Serial.print(F("Reciever initialized : ")); Serial.println(WiFi.macAddress()); // Define callback functions esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); // Register peer esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; // Add peer if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println(F("Failed to add peer")); return; } } void loop() { if((millis() -previousTime)>1500){ // Set values to send strcpy(dataSent.a, nom); dataSent.b = random(100, 200); dataSent.c = false; // Send message via ESP-NOW esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &dataSent, sizeof(dataSent)); previousTime=millis(); } }
(Código pour ESP8266 ci-dessous)
Cuando cargues el código por primera vez, fíjate bien en el mensaje de configuración que contiene la dirección MAC del receptor. Debes introducirla en el código del transmisor para garantizar el emparejamiento y la comunicación correctos.
Resultados
Una vez definida la dirección MAC de la tarjeta receptora y cargados los códigos en cada tarjeta, se establece la comunicación y la estructura se envía y descifra correctamente.
- Maestro
- Esclavo0
- Esclavo1
ATENCIÓN: durante este tutorial, una de las tarjetas parecía estar defectuosa y no enviaba ningún mensaje al máster aunque sí lo recibía. A probar y validar con otra tarjeta.
Bonus: Comunicación entre ESP32 y ESP8266
Para integrar las tarjetas ESP8266 en su red ESP-NOW, basta con modificar algunas líneas de código.
Te invito a revisar el tutorial sobre ESP-NOW y ESP8266
Para cambiar de un código ESP32 a un código ESP8266, es necesario cambiar:
- Incluye al principio del código
- Añadir una función esp_now_set_self_role con el rol correcto definido en cada caso
- Modificar los argumentos de la función esp_now_add_peer
- Modify the argument types of the OnDataRecv and OnDataSent functions
#include <espnow.h>//https://github.com/esp8266/Arduino/blob/master/tools/sdk/include/espnow.h #include <ESP8266WiFi.h> const char nom[10]="Esclavo0"; uint8_t broadcastAddress[] = {0x3C, 0x61, 0x05, 0x30, 0x0A, 0x28};// REPLACE WITH MASTER MAC ADDRESS //{0xDC, 0x4F, 0x22, 0x58, 0xD2, 0xF5} //station0 // Structure example to send data // Must match the receiver structure typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; struct_message dataSent; struct_message dataRcv; unsigned long previousTime=0; // callbacks for sending and receiving data void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\n"+String(nom)+" packet sent:\t"); Serial.println(status == 0 ? "Delivery Success" : "Delivery Fail"); } void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&dataRcv, incomingData, sizeof(dataRcv)); Serial.print("\r\nBytes received: "); Serial.println(len); Serial.print("From: "); Serial.println(dataRcv.a); Serial.print("To: "); Serial.println(dataRcv.d); Serial.print("Sensor: "); Serial.println(dataRcv.b); Serial.print("Status: "); Serial.println(dataRcv.c); Serial.println(); } void setup() { // Init Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != 0) { Serial.println(F("Error initializing ESP-NOW")); return; } Serial.print(F("Reciever initialized : ")); Serial.println(WiFi.macAddress()); // Define callback functions esp_now_set_self_role(ESP_NOW_ROLE_SLAVE); esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); // Register peer esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_CONTROLLER, 1, NULL, 0); } void loop() { if((millis() -previousTime)>1500){ // Set values to send strcpy(dataSent.a, nom); dataSent.b = random(100, 200); dataSent.c = false; // Send message via ESP-NOW uint8_t result = esp_now_send(broadcastAddress, (uint8_t *) &dataSent, sizeof(dataSent)); previousTime=millis(); } }
Bonus: Muestra la dirección MAC para copiarla
Es posible mostrar la dirección MAC del Maestro o de los Esclavos en un formato que permita copiarla directamente en broadcastAddress.
void getMacAdress(const uint8_t * mac){ /*for (int i=0; i<6; i++){ if (mac[i]<10) Serial.print(0,HEX),Serial.print(mac[i],HEX); // FF:FF:FF:FF:FF:FF else Serial.print(mac[i],HEX); if(i<5) Serial.print(","); } */ Serial.print("{"); for (int i=0; i<6; i++){ Serial.print("0x"); if (mac[i]<10) Serial.print(0,HEX),Serial.print(mac[i],HEX); // {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF} else Serial.print(mac[i],HEX); if(i<5) Serial.print(","); } Serial.print("}"); }