fbpixel
Etiquetas: , ,

Neste tutorial, veremos como configurar uma rede de vários ESP32s usando o protocolo ESP-NOW. O ESP32 é uma placa de desenvolvimento com WiFi incorporado. Pode, portanto, ligar-se e trocar dados com dispositivos ligados à mesma rede.

Hardware

  • Computador
  • NodeMCU ESP32 ou NodeMCU ESP8266 ou Wemos x3
  • Cabo USB A macho para Mini B macho x3

Descrição ESP-NOW

O ESP-NOW é um protocolo de comunicação desenvolvido pela Espressif, que permite a comunicação sem fios entre vários dispositivos sem a necessidade de uma rede específica. Permite a troca de pequenos pacotes de dados a alta velocidade nas bandas de frequência de 2,4 GHz, até 200 m de distância. Requer um emparelhamento inicial, mas uma vez efectuado, a comunicação é persistente e estabelece-se logo no arranque. Uma das grandes vantagens, para além do facto de utilizar uma rede dedicada, é que uma ou mais estações ESP32 ou ESP8266 podem ligar-se à rede Wifi em paralelo.

Utilizando este protocolo, é possível criar uma rede de NodeMCUs que comunicam entre si.

Código

Vamos estabelecer uma comunicação bidirecional entre vários ESP32 utilizando a biblioteca espnow.h disponível quando instala o gestor de cartões. Vamos definir e enviar uma estrutura de dados idêntica para todos os cartões, a fim de simplificar o código.

Código Mestre

Para que o cartão emissor possa comunicar com outro cartão, é necessário o seu endereço MAC. É possível obter este endereço quando a placa recetora é iniciada a partir de mensagens enviadas através da função setup() (Serial.println(WiFi.macAddress());). Vamos definir uma função que é executada depois de uma mensagem ter sido enviada para verificar se a transmissão foi bem sucedida.

Não se esqueça de alterar os endereços dos cartões slave correspondentes aos cartões que está a utilizar.

#include <esp_now.h>
#include <WiFi.h>
const char nom[10]="Mestre"; 
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\nMestre 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 = "Escravo0";
    esp_err_t result0 = esp_now_send(broadcastAddress[0], (uint8_t *) &myData, sizeof(myData));
    

    myData.d = "Escravo1";
    esp_err_t result1 = esp_now_send(broadcastAddress[1], (uint8_t *) &myData, sizeof(myData));
    previousTime=millis();
  }
}

Código Slave

No código SLAVE, vamos criar uma função que é executada quando uma mensagem é recebida. Esta função é utilizada para processar a informação recebida. Neste exemplo, exibimos os dados contidos na estrutura.

O código Slave deve ser modificado para cada cartão Slave, de modo a que os dados e o identificador sejam diferentes.

#include <esp_now.h>
#include <WiFi.h>

const char nom[10]="Escravo0"; 
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)

Quando carregar o código pela primeira vez, observe atentamente a mensagem de configuração que contém o endereço MAC do recetor. Deve introduzi-lo no código do transmissor para assegurar o emparelhamento e a comunicação com êxito.

Resultados

Uma vez definido o endereço MAC do cartão recetor e carregados os códigos em cada cartão, a comunicação é estabelecida e a estrutura é enviada e desencriptada corretamente.

  • Mestre
espnow-network-master-result Criar uma rede ESP32 com o ESP-NOW
  • Escravo0
espnow-network-slave0-result Criar uma rede ESP32 com o ESP-NOW
  • Escravo1
espnow-network-slave1-result Criar uma rede ESP32 com o ESP-NOW

ATENÇÃO: durante este tutorial, um dos cartões parecia estar com defeito e não enviava uma mensagem ao mestre, embora a recebesse. A ser testado e validado com outro cartão

Bónus: Comunicação entre o ESP32 e o ESP8266

Para integrar os cartões ESP8266 na sua rede ESP-NOW, basta modificar algumas linhas de código.

Convido-o a rever o tutorial sobre o ESP-NOW e o ESP8266

Para mudar de um código ESP32 para um código ESP8266, é necessário mudar:

  • Inclui no início do código
  • Adicionar uma função esp_now_set_self_role com a função correcta definida em cada caso
  • Modificar os argumentos da função esp_now_add_peer
  • Modificar os tipos de argumentos das funções OnDataRecv e OnDataSent
#include <espnow.h>//https://github.com/esp8266/Arduino/blob/master/tools/sdk/include/espnow.h
#include <ESP8266WiFi.h>

const char nom[10]="Escravo0"; 
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();
  }
}

Bónus: Mostrar o endereço MAC para o copiar

É possível visualizar o endereço MAC do Maestro ou dos Slaves num formato que permita a sua cópia direta para 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("}"); 
}

Fontes