En este tutorial, veremos cómo configurar dos ESP32 para establecer comunicación utilizando el protocolo ESP-NOW. El ESP32 es una placa de desarrollo que integra Bluetooth y WiFi. Por lo tanto, puede conectarse e intercambiar datos con dispositivos conectados a la misma red.
Hardware
- Ordenador
- NodeMCU ESP32 x2
- Cable USB A macho a Mini B macho x2
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,4GHz, con una separación de hasta 200m. Requiere un emparejamiento inicial, pero una vez realizado, la comunicación es persistente y se establece cuando se pone en marcha el ESP32. Una de las grandes ventajas, además de que utiliza una red dedicada, es que una o varias estaciones ESP32 pueden conectarse al Wifi en paralelo.
EPS-NOW también se puede utilizar para comunicarse entre varias tarjetas ESP32 y ESP8266. Basta con adaptar el código al tipo de tarjeta.
Código
Para probar la comunicación ESP-NOW entre dos ESP32, utilizamos la librería esp_now.h disponible al instalar el gestor de tarjetas. Vamos a definir y enviar una estructura de datos entre la tarjeta maestra y la tarjeta esclava. Esta estructura debe ser idéntica entre las dos tarjetas para que los datos se entiendan correctamente.
Código transmisor
Para que la tarjeta emisora se comunique con otra tarjeta ESP32, 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(). Vamos a definir una función que se ejecuta después de enviar un mensaje para comprobar si la transmisión se ha realizado correctamente.
#include <esp_now.h>// https://github.com/espressif/esp-idf/blob/master/components/esp_wifi/include/esp_now.h #include <WiFi.h> uint8_t broadcastAddress[] = {0xA4, 0xCF, 0x12, 0x9A, 0x19, 0xAC};// 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; // callback when data is sent void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print(F("\r\n Master packet sent:\t")); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } 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("Transmitter initialized : ")); Serial.println(WiFi.macAddress()); // Define Send function esp_now_register_send_cb(OnDataSent); // 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() { // Set values to send strcpy(myData.a, "data type char"); myData.b = random(1, 20); myData.c = 1.2; myData.d = "hello"; myData.e = false; // Send message via ESP-NOW esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); if (result == ESP_OK) { Serial.println(F("Sent with success")); } else { Serial.println(F("Error sending the data")); } delay(1000); }
N.B.: En algunas versiones, este código no funciona. Hay dos soluciones posibles si se encuentra con un problema: o
- colocar memset(&peerInfo, 0, sizeof(peerInfo)); después de la declaración peerInfo
- declarar esp_now_peer_info_t peerInfo antes de loop()
Código receptor
En el código del receptor, 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.
#include <esp_now.h>// https://github.com/espressif/esp-idf/blob/master/components/esp_wifi/include/esp_now.h #include <WiFi.h> // Structure example to receive data // Must match the sender structure typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; struct_message myData; // callback when data is received void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&myData, incomingData, sizeof(myData)); Serial.print("Bytes received: "); Serial.println(len); Serial.print("Char: "); Serial.println(myData.a); Serial.print("Int: "); Serial.println(myData.b); Serial.print("Float: "); Serial.println(myData.c); Serial.print("String: "); Serial.println(myData.d); Serial.print("Bool: "); Serial.println(myData.e); Serial.println(); } void setup() { // Initialize 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("Error initializing ESP-NOW"); return; } Serial.print(F("Receiver initialized : ")); Serial.println(WiFi.macAddress()); // Define receive function esp_now_register_recv_cb(OnDataRecv); } void loop() { }
Cuando cargues el código por primera vez, fíjate bien en el mensaje de configuración que contiene la dirección MAC. Debe introducirla en el código del transmisor para que el emparejamiento se realice correctamente y la comunicación funcione.
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.
Bonus: Comunicación bidireccional entre dos ESP32s
Hemos visto cómo enviar datos de una tarjeta a otra utilizando el protocolo ESP-NOW. Ahora vamos a ver cómo, modificando ligeramente los códigos, podemos obtener una comunicación bidireccional.
Código Transceiver
#include <esp_now.h> #include <WiFi.h> uint8_t broadcastAddress[] = {0xA4, 0xCF, 0x12, 0x9A, 0x19, 0xAC};// REPLACE WITH THE OTHER TRANSCEIVER 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; char dataRcv[15]; // callbacks for sending and receiving data void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\nMaster 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); 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("Transceiver 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() { // Set values to send strcpy(myData.a, "from Master"); myData.b = random(1, 20); myData.c = 1.2; myData.d = "hello"; myData.e = false; // Send message via ESP-NOW esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); delay(1000); }
N.B.: Para obtener el mismo código para el emisor y el receptor, utilizamos la misma estructura de datos para los mensajes enviados y recibidos. Es posible definir estructuras diferentes en función del origen del mensaje. No obstante, hay que tener en cuenta que la tarjeta receptora debe conocer la estructura del mensaje recibido para poder descifrarlo.
Resultados
Para la estación 0, definimos el mensaje
strcpy(dataSent.a, "Master"); dataSent.b = random(100, 200); dataSent.c = false;
Y para la estación 1:
strcpy(dataSent.a, "Slave"); dataSent.b = random(10, 20); dataSent.c = false;