Etiquetas: , ,

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
espnow-network-master-result Crear una red ESP32 con ESP-NOW
  • Esclavo0
espnow-network-slave0-result Crear una red ESP32 con ESP-NOW
  • Esclavo1
espnow-network-slave1-result Crear una red ESP32 con ESP-NOW

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("}"); 
}

Fuentes