IoT

ESP-NOW: Comunicação Sem-Fio Entre ESP32s

Eletrogate 27 de abril de 2023

Introdução

Projetos que necessitem que ESP32s comuniquem entre si são recorrentes. Por exemplo: um ESP32 de uma central de automação que recebe dados de outros ESP32s espalhados pela casa. Para essa comunicação, pode se utilizar o protocolo ESP-NOW, que será abordado neste post.


Materiais Necessários para os Projetos de ESP-NOW: Comunicação Sem-Fio Entre ESP32s

Para este post, serão utilizados os seguintes materiais:

cta_cart

Adicionalmente, também será necessária uma rede sem fio provida por um roteador com Internet para conexão com o ESP32.


Sobre o ESP-NOW

O ESP-NOW é um protocolo rápido desenvolvido pela Espressif (fabricante do ESP32), que permite a vários dispositivos ESP32 e ESP8266 se comunicarem sem usar WiFi. O protocolo é semelhante à conectividade sem fio de 2,4 GHz de baixa potência, geralmente implantada em mouses sem fio. Assim, o emparelhamento entre os dispositivos é necessário antes de sua comunicação. Após o pareamento, a conexão é segura e ponto a ponto, sem necessidade de os ESP32s afirmarem que reconheceram uns aos outros e estão prontos para iniciarem a comunicação (handshake).

O ESP-NOW aplica a tecnologia IEEE802.11 (tecnologia de redes locais sem-fio, mais conhecida como WiFi), juntamente com a tecnologia de criptografia CCMP (mecanismo aprimorado de encapsulamento criptográfico de dados projetado para confidencialidade de dados), realizando assim uma solução de comunicação.

Diferentemente dos protocolos WiFi tradicionais, no protocolo ESP-NOW, as cinco primeiras camadas superiores no OSI (modelo de rede de computador referência da ISO dividido em camadas de funções) são simplificadas para uma camada no ESP-NOW, portanto, os dados não precisam passar pela camada física, camada de enlace de dados, camada de rede, camada de transporte. Isso reduz o atraso causado pela perda de pacotes em rede congestionada e leva a um tempo de resposta rápido.

Características do ESP-NOW

O protocolo ESP-NOW ocupa menos recursos de CPU e flash em comparação com o WiFi. O modo de transmissão de dados do ESP-NOW é flexível, incluindo unicast e broadcast, e suporta conexão unidirecional, bidirecional e multidirecional entre dispositivos.

Após ligar, os dispositivos podem transmitir dados e controlar outros dispositivos emparelhados diretamente “sem qualquer conexão sem fio”, e a velocidade de resposta é em milissegundos. Quando o dispositivo se conecta a um roteador ou funciona como um Ponto de Acesso, ele também pode realizar uma comunicação rápida e estável pelo ESP-NOW, além de manter uma conexão estável através do ESP-NOW mesmo se o roteador estiver com defeito ou a rede estiver instável.

Além disso, o protocolo ESP-NOW permite que seja realizada comunicação de longa distância estável, mesmo que os dispositivos estejam separados por paredes e até pisos.

Vantagens e Desvantagens do uso de ESP-NOW

O ESP-NOW suporta os seguintes recursos:

  • Comunicação unicast (endereçamento para um pacote feito a um único destino) criptografada e não criptografada;
  • Dispositivos pares criptografados e não criptografados mistos;
  • Pode ser transportado uma carga útil de até 250 bytes;
  • Após o pareamento inicial entre dispositivos, não é necessário realizar handshake. Ou seja, não é necessário realizar o processo em que dois ou mais ESPs afirmam que reconheceram uns aos outros e estão prontos para iniciar a comunicação. Isso significa que se uma placa ESP perder energia, ela automaticamente irá se conectar ao seu par para continuar a comunicação;
  • A função de retorno de chamada de envio pode ser definida para informar ao aplicativo um sucesso ou uma falha na transmissão.

A tecnologia ESP-NOW também tem as seguintes limitações:

  • Pares criptografados limitados. No máximo, são suportados:
    • 10 pares criptografados no modo Station;
    • 6 pares criptografados no modo SoftAP ou SoftAP + Station;
  • Múltiplos pares não criptografados são suportados, no entanto, o número total de dispositivos deve ser inferior a 20, incluindo pares criptografados;
  • A carga é limitada a 250 bytes.

Compatibilidade de recursos

O uso do protocolo ESP-NOW é compatível com o WiFi e também com o Bluetooth Low Energy e suporta as séries de microcontroladores ESP8266, ESP32, ESP32-S e ESP32-C.

Processo de Uso

Para qualquer comunicação ESP-NOW entre ESP32s, é necessário saber o endereço MAC do ESP32 ao qual será feito o envio de dados, deste modo é que se sabe para qual dispositivo enviará os dados. Como cada ESP32 possui um endereço MAC único, este é o identificador de cada placa para enviar dados para ela usando o ESP-NOW.

Para efetuar a comunicação ESP-NOW, é feito o seguinte processo:

  1. Inicializar a função ESPNOW: Os dados do ESP-NOW devem ser transmitidos após o WiFi ser iniciado, portanto, é recomendável iniciar o WiFi antes de inicializar o ESP-NOW e interromper o Wi-Fi após a desinicialização do ESP-NOW;
  2. Definir a função de retorno de chamada do envio (para ESP32 remetente): A função de retorno de chamada de envio pode ser usada para informar o sucesso ou a falha da transmissão. Como a função de retorno de chamada de envio é executada a partir de uma tarefa WiFi de alta prioridade, não é permitido fazer operações demoradas na função de retorno de chamada. Em vez disso, trate as operações demoradas fora da função de retorno de chamada do envio.
    Observe os seguintes pontos ao usar a função de envio de retorno de chamada:

    • Para comunicação unicast:
      • Se a camada de aplicação não receber o pacote, mas a função callback retornar “sucesso”, pode ser causado por:
        • ataques de dispositivo não autorizado;
        • erro de configuração de chave criptografada;
        • perda de pacotes na camada de aplicação.
      • Se a camada de aplicativo recebeu o pacote, mas a função de retorno de chamada retorna falha, pode ser causado por:
        • O canal está ocupado e o ACK não é recebido (ACK é a abreviação de “acknowledgement” é um código de comunicação enviado por uma unidade receptora a uma estação transmissora, com o objetivo de confirmar que está pronta para receber um pacote de dados ou que o pacote enviado chegou sem erros).
    • Para comunicação multicast (comunicação broadcast também incluída):
      • Se a função callback retornar “sucesso”, significa que o pacote foi enviado com sucesso.
      • Se a função callback retornar “falha”, significa que o pacote não foi enviado com sucesso
  3. Defina a função de retorno de chamada do recebimento (para ESP32 destinatário) – somente se o ESP32 irá recepcionar dados: A função de retorno de chamada de recebimento pode ser usada para informar a camada de aplicativo que o pacote enviado pelo ESP32 remetente foi recebido. A função de retorno de chamada de recebimento retornará o endereço MAC do ESP32 remetente e a carga útil do pacote. Como a função de retorno de chamada de envio também é executada a partir de uma tarefa WiFi de alta prioridade, não é permitido fazer operações demoradas na função de retorno de chamada. Em vez disso, trate as operações demoradas fora da função de retorno de chamada do envio.
  4. Adicionar o dispositivo à lista de dispositivos emparelhados: Isso deve ser feito antes de enviar qualquer dado para este dispositivo. Os dados para emparelhar os dispositivos incluem, mas não se limitam à:
    • Endereço MAC do destinatário;
    • A faixa do canal de dispositivos emparelhados, que pode ser entre 0 a 14. Se o canal for definido como 0, os dados serão enviados no canal atual. Caso contrário, o canal deve ser definido como o canal no qual o dispositivo destinatário está;
    • Valor booleano que indica se os dados do ESPNOW que este par envia/recebe são criptografados ou não;
  5. Envio de mensagem para o dispositivo destinatário através do ESP-NOW: Como a carga de dados é limitada à 250 bytes, se houver muitos dados do ESP-NOW para enviar, envie cada pacote de dados com tamanho menor ou igual à 250 bytes de dados de cada vez até fazer o envio de todos os dados necessários. Observe que um intervalo muito curto entre o envio de dois pacotes de dados ESP-NOW pode levar à desordem do envio da função de retorno de chamada. Portanto, é recomendável enviar os próximos dados do ESP-NOW após o retorno da função de retorno de chamada do envio anterior.

Referência da API ESP-NOW

Principais estruturas, definições e enumerações

  • struct esp_now_peer_info: Estrutura que armazena parâmetros de informações de dispositivos destinatários ESP-NOW.
    • Principais membros Públicos:
      • uint8_t peer_addr[ESP_NOW_ETH_ALEN]: um array de 6 bytes que representa o endereço MAC do dispositivo destinatário ESP-NOW;
      • uint8_t channel: Canal WiFi que o dispositivo destinatário usa para enviar e receber dados do ESP-NOW. O valor padrão é 0, que significa que o ESP-NOW irá escolher o canal automaticamente;
      • bool encrypt: Informa se os dados do ESP-NOW que o dispositivo destinatário envia e recebe devem ser criptografados ou não.

 

  • typedef struct esp_now_peer_info esp_now_peer_info_t: Definição do tipo esp_now_peer_info que armazena parâmetros de informações de dispositivos destinatários ESP-NOW.

 

  • typedef void (*esp_now_send_cb_t)(const uint8_t *mac_addr, esp_now_send_status_t status): Definição de tipo para função de retorno de chamada (callback) de envio de dados ESP-NOW, que é chamada quando uma mensagem é enviada com sucesso ou falha na transmissão via ESP-NOW.
    • Parâmetros:
      • mac_addr: um ponteiro para o endereço MAC do dispositivo receptor da mensagem;
      • status: status do envio de dados ESP-NOW (sucesso ou falha), sendo retornado uma resposta do tipo Enumeração esp_now_send_status_t.
    • Funcionalidade: Essa função de retorno de chamada é útil para verificar se a mensagem foi entregue com sucesso ao dispositivo receptor e tomar medidas apropriadas em caso de falha na transmissão, como tentar reenviar a mensagem ou exibir uma mensagem de erro para o usuário

 

  • typedef void (*esp_now_recv_cb_t)(const uint8_t * mac, const uint8_t *data, int data_len): Definição de tipo para função de retorno de chamada (callback) que é chamada sempre que um pacote é recebido por meio da comunicação ESP-NOW.
    • Parâmetros:
      • const uint8_t * mac: um ponteiro para um array de 6 bytes que contém o endereço MAC do dispositivo remetente do pacote recebido;
      • data: um ponteiro para um array de bytes que contém o buffer de dados do pacote recebido;
      • data_len: um inteiro que representa o tamanho dos dados recebidos em bytes.
    • Funcionalidade: Essa função de retorno de chamada é utilizada para processar os dados recebidos e tomar as ações apropriadas com base no conteúdo dos dados e no endereço MAC do dispositivo remetente.

 

  • enum esp_now_send_status_t: Essa enumeração define os possíveis valores de status de envio de dados do ESPNOW após uma tentativa de envio de mensagem.
    • Valores:
      • enumerator ESP_NOW_SEND_SUCCESS: indica que o envio da mensagem foi concluído com sucesso, ou seja, a mensagem foi enviada com êxito para o destinatário especificado;
      • enumerator ESP_NOW_SEND_FAIL: indica que o envio da mensagem falhou por algum motivo. Isso pode acontecer, por exemplo, se o destinatário estiver inativo ou fora do alcance do sinal.

Principais funções

  • esp_err_t esp_now_init(void): função utilizada para inicializar a função ESP-NOW. Ela retorna um valor do tipo esp_err_t, que indica se a inicialização foi concluída com sucesso ou se houve algum erro.
    • Parâmetros: Nenhum.
    • Retorno:
      • ESP_OK: indica que a inicialização da função ESP-NOW foi concluída com sucesso, ou seja, o módulo está pronto para ser usado;
      • ESP_ERR_ESPNOW_INTERNAL: indica um erro interno da função ESP-NOW, que pode ser causado por diversos motivos, como falha na comunicação com o hardware ou configuração incorreta do módulo. É recomendado tentar reiniciar o dispositivo e executar a inicialização novamente. Se o problema persistir, é possível verificar se a biblioteca ESP-NOW está atualizada e se a configuração do módulo está correta. Além disso, é possível consultar a documentação da biblioteca ou buscar ajuda na comunidade para encontrar soluções para problemas específicos.

 

  • esp_err_t esp_now_deinit(void): função da biblioteca ESP-NOW utilizada para finalizar a função ESP-NOW. Ela retorna um valor do tipo esp_err_t, que indica se a finalização foi concluída com sucesso ou se houve algum erro.
    • Parâmetros: Nenhum.
    • Retorno:
      • ESP_OK: indica que a finalização do módulo ESP-NOW foi concluída com sucesso, ou seja, o módulo foi finalizado sem problemas. Isso significa que o módulo não está mais em uso e que a memória associada a ele foi liberada. Caso seja necessário, é possível chamar a função esp_now_init(void) novamente para reinicializar o módulo ESP-NOW.
    • Observação: É importante lembrar que a finalização do módulo ESP-NOW pode afetar o funcionamento de outras partes do programa que dependem dele. Portanto, a finalização do módulo deve ser realizada com cuidado e apenas quando realmente necessário.

 

  • esp_err_t esp_now_register_recv_cb(esp_now_recv_cb_t cb): esta função é utilizada para registrar uma função de callback a ser chamada quando o dispositivo receber um pacote ESP-NOW. Ela retorna um valor que indica se a operação foi concluída com sucesso ou se houve algum erro.
    • Parâmetros:
      • cb: ponteiro para a função de retorno de chamada de recebimento de dados ESPNOW.
    • Retorno:
      • ESP_OK: indica que a operação foi concluída com sucesso e que a função de callback foi registrada com êxito;
      • ESP_ERR_ESPNOW_NOT_INIT: indica que o módulo ESP-NOW ainda não foi inicializado. Nesse caso, é necessário chamar a função esp_now_init(void) antes de registrar a função de callback;
      • ESP_ERR_ESPNOW_INTERNAL: indica que ocorreu um erro interno na biblioteca ESP-NOW, que pode ser causado por diversos motivos, como falha na comunicação com o hardware ou configuração incorreta do módulo. Nesse caso, é recomendado tentar reiniciar o dispositivo e executar a operação novamente.

 

  • esp_err_t esp_now_register_send_cb(esp_now_send_cb_t cb): registra um callback (função de retorno de chamada) para ser chamado quando um pacote de dados é enviado usando o ESP-NOW. O retorno da função é uma variável do tipo “esp_err_t”, que indica o status da operação.
    • Parâmetros:
      • cb: ponteiro para a função de retorno de chamada que deve ser registrada.
    • Retorno:
      • ESP_OK: a operação foi concluída com êxito;
      • ESP_ERR_ESPNOW_NOT_INIT: a biblioteca ESP-NOW ainda não foi inicializada;
      • ESP_ERR_ESPNOW_INTERNAL: um erro interno ocorreu na biblioteca ESP-NOW.

 

  • esp_err_t esp_now_add_peer(const esp_now_peer_info_t *peer): adiciona um novo dispositivo (peer) à lista de dispositivos conhecidos. O retorno da função é uma variável do tipo “esp_err_t”, que indica o status da operação.
    • Parâmetros:
      • peer: ponteiro para uma estrutura do tipo esp_now_peer_info_t que contém as informações do novo dispositivo.
    • Retorno:
      • ESP_OK: o novo dispositivo foi adicionado com sucesso à lista de dispositivos conhecidos;
      • ESP_ERR_ESPNOW_NOT_INIT: a biblioteca ESP-NOW ainda não foi inicializada;
      • ESP_ERR_ESPNOW_ARG: algum dos argumentos passados para a função é inválido;
      • ESP_ERR_ESPNOW_FULL: a lista de dispositivos conhecidos já está cheia e não é possível adicionar um novo dispositivo;
      • ESP_ERR_ESPNOW_NO_MEM: a biblioteca ESP-NOW não tem memória suficiente para adicionar um novo dispositivo;
      • ESP_ERR_ESPNOW_EXIST: o dispositivo já está na lista de dispositivos conhecidos.

 

  • esp_err_t esp_now_send(const uint8_t *peer_addr, const uint8_t *data, size_t len): envia um pacote de dados para um dispositivo específico na rede ESP-NOW. O retorno da função é uma variável do tipo “esp_err_t”, que indica o status da operação.
    • Parâmetros:
      • peer_addr: um ponteiro para um array de 6 bytes que contém o endereço MAC do dispositivo de destino (Se peer_addr for NULL, os dados serão enviados para todos os pares adicionados à lista de pares);
      • data: um ponteiro para um array de bytes que contém os dados a serem enviados;
      • len: um valor inteiro que indica o tamanho dos dados a serem enviados (O comprimento máximo dos dados deve ser menor que 250 bytes).
    • Retorno:
      • ESP_OK: o pacote de dados foi enviado com sucesso;
      • ESP_ERR_ESPNOW_NOT_INIT: a biblioteca ESP-NOW ainda não foi inicializada;
      • ESP_ERR_ESPNOW_INTERNAL: ocorreu um erro interno na biblioteca ESP-NOW;
      • ESP_ERR_ESPNOW_ARG: algum dos argumentos passados para a função é inválido;
      • ESP_ERR_ESPNOW_NO_MEM: a biblioteca ESP-NOW não tem memória suficiente para enviar o pacote de dados, quando isso acontecer, você pode demorar um pouco antes de enviar os próximos dados;
      • ESP_ERR_ESPNOW_NOT_FOUND: esse erro ocorre quando o endereço MAC apontado por peer_addr não corresponde a nenhum dispositivo conhecido na rede ESP-NOW. Isso pode acontecer se o dispositivo de destino não estiver dentro do alcance ou se não estiver executando o código ESP-NOW apropriado;
      • ESP_ERR_ESPNOW_IF: esse erro ocorre quando há um problema com a interface Wi-Fi ou ESP-NOW. Por exemplo, pode ocorrer se a interface Wi-Fi não estiver configurada corretamente ou se a inicialização do ESP-NOW falhar.

 

Todas as funções, estruturas, definições e enumerações podem ser consultadas na documentação, acessada em docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html

Nota de Aviso: É importante lembrar que qualquer função de callback deve ser rápida e não deve realizar operações demoradas ou bloqueantes, já que ela é chamada em uma interrupção de hardware.


Obtendo Endereço MAC do ESP32

Para saber o endereço MAC do ESP32, carregue o seguinte sketch no ESP32:

Nota: Não é necessário nenhum hardware extra conectado ao ESP32.

/******************************************************************************
                        ESP-NOW: Comunicação entre ESP32s
                      Sketch Obtendo endereço MAC do ESP32

                          Criado em 08 de Dezembro de 2022
                                por Michel Galvão

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Inclusão das bibliotecas
#include <WiFi.h>

void setup() {
  Serial.begin(115200); // configurada a taxa de transferência em 115200 bits por segundo para a transmissão serial
  WiFi.mode(WIFI_MODE_STA); // configura o WIFi para o Modo de estação WiFi
  
  Serial.print("Endereço MAC: ");
  Serial.println(WiFi.macAddress()); // retorna o endereço MAC do dispositivo

  while(1); // loop infinito
}

void loop() {}

Explicação do Sketch

Este sketch usa a biblioteca WiFi.h para se conectar a uma rede WiFi e exibir o endereço MAC do dispositivo na porta serial.

Na função setup(), a taxa de transmissão serial é configurada para 115200 bits por segundo com o comando Serial.begin(115200). Em seguida, a configuração do WiFi é definida para o modo estação com WiFi.mode(WIFI_MODE_STA).

A próxima linha de código exibe o endereço MAC do dispositivo na porta serial com a instrução Serial.print("Endereço MAC: "); Serial.println(WiFi.macAddress());.

Por fim, o programa entra em um loop infinito com o comando while(1) na última linha da função setup(), enquanto a função loop() está vazia.

Após o upload do sketch para o ESP32, abra o monitor serial (Ctrl + Shift + M, na IDE Arduino) e visualize e guarde o endereço MAC da placa ESP32.


Comunicação Unidirecional com Mestre Enviando para um Escravo

Uma rede de comunicação unidirecional com mestre enviando para um escravo é aquela em que o mestre envia dados para um único escravo, sem que haja retorno de dados do escravo para o mestre pelo mesmo canal de comunicação.

Nesse modo de comunicação, o mestre pode enviar informações específicas para o escravo, como um comando para ligar ou desligar um dispositivo. Por exemplo, em um sistema de monitoramento de temperatura em um data center, o mestre pode enviar comandos para um dispositivo escravo, como um termostato, para ajustar a temperatura em uma sala específica.

Neste exemplo, será enviado uma estrutura com variáveis de tipos diferentes utilizando a Comunicação Unidirecional com mestre enviando para escravo.

Para funcionar os sketches abaixo, primeiro faça o upload do sketch de obtenção de endereço MAC na placa ESP32 escravo, onde o código está disponível no tópico Obtendo endereço MAC do ESP32. Com o endereço MAC obtido, substitua o valor da variável slaveMacAddress pelo valor do MAC obtido acrescido do prefixo 0x à cada intervalo do MAC (Como exemplo, a formatação do endereço MAC 00:1A:2B:3C:4D:5E seria {0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E}).

Software e Hardware

Esquemático do Mestre

Sketch do Mestre

/******************************************************************************
         Comunicação Unidirecional com mestre enviando para um escravo
                              Sketch Mestre
                   Criado em 26 de Fevereiro de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

#include <LiquidCrystal_I2C.h>  // Inclui a biblioteca LiquidCrystal_I2C para controle do display LCD I2C
#include <esp_now.h>            // Inclui a biblioteca esp_now para o uso do protocolo de comunicação ESP-NOW
#include <Wire.h>               // Inclui a biblioteca Wire para a comunicação I2C
#include <WiFi.h>               // Inclui a biblioteca WiFi para configuração da rede sem fio

const uint8_t pinledVermelho = 13;  // Define o pino do LED vermelho como 13
const uint8_t pinledVerde = 12;     // Define o pino do LED verde como 12

const uint8_t pinSda = 14;  // Define o pino SDA como 14 para a comunicação I2C
const uint8_t pinScl = 27;  // Define o pino SCL como 27 para a comunicação I2C

uint8_t slaveMacAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };  // Define o endereço MAC do dispositivo escravo. Coloque o endereço MAC de sua placa aqui

struct DataStruct {  // Define a estrutura DataStruct para troca de informações
  int integerData;   // Variável inteira integerData
  float floatData;   // Variável de ponto flutuante floatData
  bool booleanData;  // Variável booleana booleanData
};

esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

// Declara a estrutura dataToSend e atribui valores iniciais às variáveis
DataStruct dataToSend = {
  .integerData = 0,
  .floatData = 0,
  .booleanData = false
};

LiquidCrystal_I2C lcd(0x27, 16, 2);  // Declara o objeto lcd para controle do display LCD I2C

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {  // Função chamada quando os dados são enviados
  if (status == ESP_NOW_SEND_SUCCESS) {                                   // Se o envio foi bem sucedido, ...
    digitalWrite(pinledVerde, HIGH);                                      // Acende o LED verde
    digitalWrite(pinledVermelho, LOW);                                    // Apaga o LED vermelho
    lcd.setCursor(0, 0);                                                  // Define a posição do cursor do display
    lcd.print("Envio OK        ");                                        // Escreve a mensagem no display
    lcd.setCursor(0, 1);                                                  // Define a posição do cursor do display
    String valores = "I:";                                                // Cria uma string com o valor "I:"
    if (dataToSend.integerData < 10 && dataToSend.integerData >= 0) {     // Se o valor inteiro for menor que 10 e maior ou igual a 0
      valores += "0";                                                     // Adiciona um 0 à string
    }
    valores += dataToSend.integerData;                             // Adiciona o valor inteiro à string
    valores += " F:";                                              // Adiciona o valor "F:" à string
    if (dataToSend.floatData < 10 && dataToSend.floatData >= 0) {  // Se o valor de ponto flutuante for menor que 10 e maior ou igual a 0
      valores += "0";                                              // Adiciona um 0 à string
    }
    valores += dataToSend.floatData;    // Adiciona o valor de ponto flutuante à string valores
    valores += " B:";                   // Adiciona o valor "B:" à string valores
    valores += dataToSend.booleanData;  // Adiciona o valor booleano à string valores
    lcd.print(valores);                 // exibe no dispaly lcd o conteúdo va variável valores

    dataToSend.integerData++;                                            // Incrementa o valor inteiro
    dataToSend.floatData = random(-9, 99) + (float)random(-9, 99) / 99;  // Gera um valor de ponto flutuante aleatório
    dataToSend.booleanData = random(0, 2);                               // Gera um valor booleano aleatório

    // Se o valor inteiro ultrapassar 99, retorna para 0
    if (dataToSend.integerData > 99) {
      dataToSend.integerData = 0;  // o valor de dataToSend.integerData volta à ser Zero
    }
  } else {
    digitalWrite(pinledVerde, LOW);      // Apaga o LED verde
    digitalWrite(pinledVermelho, HIGH);  // Acende o LED vermelho
    lcd.setCursor(0, 0);                 // Define a posição do cursor do display
    lcd.print("Falha no Envio  ");       // Escreve a mensagem no display
  }
}

void setup() {
  Wire.begin(pinSda, pinScl);  // Inicia a comunicação I2C com os pinos SDA e SCL definidos anteriormente
  lcd.init();                  // Inicializa o display
  lcd.backlight();             // Acende o backlight do display

  pinMode(pinledVerde, OUTPUT);       // Define o pino do LED verde como saída
  pinMode(pinledVermelho, OUTPUT);    // Define o pino do LED vermelho como saída
  digitalWrite(pinledVermelho, LOW);  // Apaga o LED vermelho
  digitalWrite(pinledVerde, LOW);     // Apaga o LED verde

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("   Comunicacao  ");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("     ESPNOW     ");  // Escreve a mensagem no display
  delay(1200);                    // Espera por 1,2 segundos
  lcd.clear();                    // Limpa o display

  lcd.setCursor(0, 0);    // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("Mestre /");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);    // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("escravo");   // Escreve a mensagem no display
  delay(1000);            // Espera por 1 segundo
  lcd.clear();            // Limpa o display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("Este dispositivo");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("eh Mestre");         // Escreve a mensagem no display
  delay(1000);                    // Espera por 1 segundo
  lcd.clear();                    // Limpa o display

  WiFi.disconnect();    // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_STA);  // Define o modo WiFi como Station (cliente)

  if (esp_now_init() != ESP_OK) {        // Inicializa o ESP-NOW e verifica se há erros
    lcd.setCursor(0, 0);                 // Define a posição do cursor do display para a primeira linha e primeira coluna
    lcd.print("Inicializacao ");         // Escreve a mensagem no display
    lcd.setCursor(0, 1);                 // Define a posição do cursor do display para a segunda linha e primeira coluna
    lcd.print("ESP-NOW com Erro");       // Escreve a mensagem no display
    digitalWrite(pinledVermelho, HIGH);  // Acende o LED vermelho
    delay(2500);                         // Espera por 2,5 segundos
    ESP.restart();                       // Reinicia o dispositivo
  }

  esp_now_register_send_cb(OnDataSent);  // Registra a função de callback que é chamada quando os dados são enviados

  memcpy(peerInfo.peer_addr, slaveMacAddress, 6);  // Copia o endereço MAC do escravo para a estrutura peerInfo
  peerInfo.channel = 0;                            // Define o canal de comunicação como 0 na estrutura peerInfo
  peerInfo.encrypt = false;                        // Define a encriptação como desativada na estrutura peerInfo

  // Tenta adicionar o escravo à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o escravo, exibe mensagem de falha no display, acende LED vermelho e reinicia o dispositivo
    lcd.setCursor(0, 0);
    lcd.print("Falha ao");
    lcd.setCursor(0, 1);
    lcd.print("adicionar peer");
    digitalWrite(pinledVermelho, HIGH);
    delay(2500);
    ESP.restart();
  }
}

void loop() {

  esp_now_send(slaveMacAddress, (uint8_t *)&dataToSend, sizeof(DataStruct));  // Envie os dados para o endereço MAC do dispositivo escravo

  delay(1000);  // Espere 1 segundo antes de enviar novamente
}

O código usa a biblioteca LiquidCrystal_I2C para controlar o display LCD I2C, a biblioteca esp_now para usar o protocolo de comunicação ESP-NOW, a biblioteca Wire para comunicação I2C e a biblioteca WiFi para configuração da rede sem fio.

As variáveis pinledVermelho e pinledVerde são usadas para definir o pino do LED vermelho e verde, respectivamente, enquanto pinSda e pinScl são usados para definir os pinos SDA e SCL, que são usados para a comunicação I2C. O endereço MAC do dispositivo escravo é definido em slaveMacAddress.

O código cria a estrutura DataStruct para troca de informações, que contém três variáveis: integerData, floatData e booleanData. A função OnDataSent é chamada quando os dados são enviados e lida a estrutura de dados enviada.

A variável dataToSend é usada para armazenar os dados para serem enviados, com valores iniciais atribuídos às variáveis. O código também usa o objeto lcd para controlar o display LCD I2C e exibir informações.

Quando os dados são enviados com sucesso, o LED verde é aceso e o LED vermelho é apagado. As informações sobre os dados enviados são exibidas no display LCD I2C. As variáveis integerData, floatData e booleanData são atualizadas com novos valores aleatórios gerados. Se o valor inteiro ultrapassar 99, ele retorna a zero.

Esquemático do Escravo

Sketch do Escravo

/******************************************************************************
         Comunicação Unidirecional com mestre enviando para um escravo
                              Sketch Escravo
                   Criado em 26 de Fevereiro de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Inclusão de bibliotecas
#include <LiquidCrystal_I2C.h>  // Biblioteca para utilizar um display de LCD com comunicação I2C
#include <esp_now.h>            // Biblioteca para utilizar o protocolo de comunicação ESP-NOW
#include <Wire.h>               // Biblioteca para comunicação I2C
#include <WiFi.h>               // Biblioteca para conectar em redes Wi-Fi

// Definição dos pinos utilizados
const uint8_t pinledVermelho = 13;  // LED vermelho conectado ao pino 13
const uint8_t pinledVerde = 12;     // LED verde conectado ao pino 12
const uint8_t pinSda = 14;          // Pino SDA para comunicação I2C
const uint8_t pinScl = 27;          // Pino SCL para comunicação I2C

// Estrutura de dados para troca de informações
struct DataStruct {
  int integerData;   // Dado do tipo inteiro
  float floatData;   // Dado do tipo float
  bool booleanData;  // Dado do tipo booleano
};

// Variável para armazenar o tempo da última recepção de dados
unsigned long temporizadorUltimaRecepcao = 0;

// Variável para armazenar os dados recebidos
DataStruct dataStruct;

// Objeto para utilizar o display de LCD de 16 colunas, 2 linhas e conectado no endereço I2C 0x27
LiquidCrystal_I2C lcd(0x27, 16, 2);

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
  memcpy(&dataStruct, incomingData, sizeof(dataStruct));  // Copia os dados recebidos para a estrutura de dados
  temporizadorUltimaRecepcao = millis();                  // Atualiza a variável de tempo da última recepção

  // Liga o LED verde e desliga o LED vermelho
  digitalWrite(pinledVerde, HIGH);
  digitalWrite(pinledVermelho, LOW);

  // Atualiza o display de LCD com informações sobre os dados recebidos
  lcd.setCursor(0, 0);
  lcd.print("Obtido ");
  lcd.print(len);
  lcd.print(" Bytes ");
  lcd.setCursor(0, 1);
  String valores = "I:";
  if (dataStruct.integerData < 10 && dataStruct.integerData >= 0) {// Se o valor inteiro for menor que 10 e maior ou igual a 0, ...
    valores += "0"; // Adiciona o caractere "0" à string "valores"
  }
  valores += dataStruct.integerData;
  valores += " F:";
  if (dataStruct.floatData < 10 && dataStruct.floatData >= 0) { // Se o valor de ponto flutuante for menor que 10 e maior ou igual a 0, ...
    valores += "0";; // Adiciona o caractere "0" à string "valores"
  }
  valores += dataStruct.floatData;
  valores += " B:";
  valores += dataStruct.booleanData;
  valores += " ";
  lcd.print(valores);
}

void setup() {
  Wire.begin(pinSda, pinScl);  // Inicia a comunicação I2C do display LCD I2C conectado nos pinos informados

  lcd.init();       // Inicia o display de LCD
  lcd.backlight();  // Liga a luz de fundo do display LCD

  // Configura os pinos dos LEDs como saídas e inicialmente desliga ambos
  pinMode(pinledVerde, OUTPUT);
  pinMode(pinledVermelho, OUTPUT);
  digitalWrite(pinledVermelho, LOW);
  digitalWrite(pinledVerde, LOW);

  // Mensagem de inicialização no display de LCD
  lcd.setCursor(0, 0);
  lcd.print("   Comunicacao  ");
  lcd.setCursor(0, 1);
  lcd.print("     ESPNOW     ");
  delay(1200);
  lcd.clear();

  // Mensagem do modo de configuração mestre/escravo no display de LCD
  lcd.setCursor(0, 0);
  lcd.print("Mestre /");
  lcd.setCursor(0, 1);
  lcd.print("escravo");
  delay(1000);
  lcd.clear();

  // Mensagem informando que este dispositivo é escravo
  lcd.setCursor(0, 0);
  lcd.print("Este dispositivo");
  lcd.setCursor(0, 1);
  lcd.print("eh Escravo");
  delay(1000);
  lcd.clear();

  // Desconecta de alguma conexão WiFi anterior e define o modo estação (STA)
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);

  // Inicia a biblioteca ESP-NOW e, caso ocorra algum erro, reinicia o dispositivo
  if (esp_now_init() != ESP_OK) {
    lcd.setCursor(0, 0);
    lcd.print("Inicializacao   ");
    lcd.setCursor(0, 1);
    lcd.print("ESP-NOW com Erro");
    digitalWrite(pinledVermelho, HIGH);
    delay(2500);
    ESP.restart();
  }

  // Registra a função OnDataRecv como a função a ser chamada quando receber dados via ESP-NOW
  esp_now_register_recv_cb(OnDataRecv);

  // Define o tempo atual como o temporizador da última recepção de dados
  temporizadorUltimaRecepcao = millis();
}

void loop() {  // Função loop() que executa repetidamente enquanto a placa estiver ligada

  // Verifica se o tempo desde a última recepção é maior que 2 segundos
  if (millis() - temporizadorUltimaRecepcao > 2000) {
    digitalWrite(pinledVermelho, HIGH);  // Liga o LED vermelho indicando que a recepção ultrapassou o tempo limite
    digitalWrite(pinledVerde, LOW); // Desliga o LED verde
    lcd.clear();// Limpa o display de LCD
    lcd.setCursor(0, 0);

    // Mostra mensagem de tempo limite ultrapassado no display de LCD
    lcd.print("Tempo limite de ");
    lcd.setCursor(0, 1);
    lcd.print("recepcao ultrap.");
    delay(1000); // Aguarda 1 segundo antes de continuar a execução
  }
}

A biblioteca LiquidCrystal_I2C e Wire é incluída para controlar um display LCD de 16×2 colunas e linhas com comunicação I2C. Além disso, as bibliotecas ESP-NOW e WiFi também são incluídas.

O código define alguns pinos para uso: pinledVermelho (pino 13) e pinledVerde (pino 12) para leds, e pinSda (pino 14) e pinScl (pino 27) para comunicação I2C. Uma estrutura de dados chamada DataStruct é criada para armazenar os dados recebidos.

Quando dados são recebidos via ESP-NOW, a função OnDataRecv é chamada. Essa função copia os dados recebidos para a estrutura de dados DataStruct, atualiza a variável de tempo temporizadorUltimaRecepcao e faz algumas outras ações, como ligar o LED verde e atualizar o display LCD com informações sobre os dados recebidos.

Na função setup, a comunicação I2C é iniciada, o display LCD é iniciado e a luz de fundo do display é ligada. Os pinos dos LEDs são configurados como saídas e inicialmente desligados. O display LCD é inicializado e uma mensagem de inicialização é exibida. Uma mensagem é exibida para informar que o dispositivo é escravo.

A função loop() contém uma verificação de tempo que compara o tempo atual com o tempo da última recepção. Se o tempo desde a última recepção for maior que 2 segundos, o LED vermelho é ligado e o LED verde é desligado. Além disso, o display LCD é limpo e uma mensagem é exibida indicando que o tempo limite de recepção foi ultrapassado. Após a exibição da mensagem, a execução é pausada por 1 segundo antes de continuar.

Demonstração do Funcionamento


Comunicação Unidirecional com um Mestre Enviando para dois Escravos

Uma rede de comunicação unidirecional com mestre enviando para dois escravos é aquela em que o mestre envia dados para dois escravos, sem que haja retorno de dados dos escravos para o mestre pelo mesmo canal de comunicação. O dispositivo mestre também pode enviar para dados para mais de dois escravos, se limitando à quantidade de dispositivos máximos oferecidos pela tecnologia ESP-NOW, mas neste exemplo, como exemplificação, só irá ser utilizado dois escravos. Nesse modo de comunicação, o mestre pode enviar as mesmas informações para ambos os escravos ao mesmo tempo, ou enviar dados diferentes para cada um dos escravos. Para enviar dados sequencialmente, o mestre deve enviar cada mensagem separadamente com esp_now_send(). Neste exemplo, como exemplificação, só irá ser utilizado o modo de envio de dados simultâneo.

Por exemplo, em um sistema de automação residencial, o mestre pode enviar comandos para dois dispositivos escravos, como acender as luzes em duas salas diferentes, sem que haja necessidade de enviar os comandos individualmente para cada dispositivo (simultâneo). Ou o mestre pode enviar comandos para duas persianas eletrônicas em quartos diferentes em ordem de prioridade, primeiro, por exemplo, para a persiana do quarto de um bebê, e depois para a persiana do quarto dos pais (sequencial).

Software e Hardware

Esquemático do Mestre

Sketch do Mestre

/******************************************************************************
         Comunicação Unidirecional com mestre enviando para dois escravos
                              Sketch Mestre
                   Criado em 28 de Fevereiro de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

#include <LiquidCrystal_I2C.h>  // Inclui a biblioteca LiquidCrystal_I2C para controle do display LCD I2C
#include <esp_now.h>            // Inclui a biblioteca esp_now para o uso do protocolo de comunicação ESP-NOW
#include <Wire.h>               // Inclui a biblioteca Wire para a comunicação I2C
#include <WiFi.h>               // Inclui a biblioteca WiFi para configuração da rede sem fio

const uint8_t pinledVermelho = 13;  // Define o pino do LED vermelho como 13
const uint8_t pinledVerde = 12;     // Define o pino do LED verde como 12

const uint8_t pinSda = 14;  // Define o pino SDA como 14 para a comunicação I2C
const uint8_t pinScl = 27;  // Define o pino SCL como 27 para a comunicação I2C

uint8_t slave1MacAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };  // Define o endereço MAC do dispositivo escravo 1. Coloque o endereço MAC de sua placa 1 aqui
uint8_t slave2MacAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };  // Define o endereço MAC do dispositivo escravo 2. Coloque o endereço MAC de sua placa 2 aqui

struct DataStruct {  // Define a estrutura DataStruct para troca de informações
  int value;         // Variável inteira value
};

esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

DataStruct dataToSend;  // Declara a estrutura dataToSend

LiquidCrystal_I2C lcd(0x27, 16, 2);  // Declara o objeto lcd para controle do display LCD I2C

// Função para formatar um número inteiro como uma string
String formatarNumero(int number) {
  // Inicializa uma variável temporária como uma string vazia
  String temp = "";

  // Se o número fornecido for menor que 10, adiciona um "0" à string temporária
  if (number < 10) {
    temp = "0";
  }

  // Adiciona o número à string temporária
  temp += number;

  // Retorna a string formatada
  return temp;
}

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {  // Função chamada quando os dados são enviados
  if (memcmp(mac_addr, slave1MacAddress, 6) == 0) {
    if (status == ESP_NOW_SEND_SUCCESS) {  // Se o envio foi bem sucedido, ...
      lcd.setCursor(0, 0);                 // Define a posição do cursor do display
      lcd.print("   OK! slave1:");
      lcd.print(formatarNumero(dataToSend.value));  // Escreve o valor no display
    } else {
      lcd.setCursor(0, 0);  // Define a posição do cursor do display
      lcd.print("FALHA! slave1   ");
    }
  } else if (memcmp(mac_addr, slave2MacAddress, 6) == 0) {
    if (status == ESP_NOW_SEND_SUCCESS) {  // Se o envio foi bem sucedido, ...
      lcd.setCursor(0, 1);
      lcd.print("   OK! slave2:");
      lcd.print(formatarNumero(dataToSend.value));  // Escreve o valor no display
    } else {
      lcd.setCursor(0, 1);  // Define a posição do cursor do display
      lcd.print("FALHA! slave2   ");
    }
  }
}

void setup() {
  Wire.begin(pinSda, pinScl);  // Inicia a comunicação I2C com os pinos SDA e SCL definidos anteriormente
  lcd.init();                  // Inicializa o display
  lcd.backlight();             // Acende o backlight do display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("   Comunicacao  ");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("     ESPNOW     ");  // Escreve a mensagem no display
  delay(1200);                    // Espera por 1,2 segundos
  lcd.clear();                    // Limpa o display

  lcd.setCursor(0, 0);      // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("Mestre /");    // Escreve a mensagem no display
  lcd.setCursor(0, 1);      // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("2 escravos");  // Escreve a mensagem no display
  delay(1000);              // Espera por 1 segundo
  lcd.clear();              // Limpa o display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("Este dispositivo");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("eh Mestre");         // Escreve a mensagem no display
  delay(1000);                    // Espera por 1 segundo
  lcd.clear();                    // Limpa o display

  WiFi.disconnect();    // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_STA);  // Define o modo WiFi como Station (cliente)

  if (esp_now_init() != ESP_OK) {   // Inicializa o ESP-NOW e verifica se há erros
    lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
    lcd.print("Inicializacao ");    // Escreve a mensagem no display
    lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
    lcd.print("ESP-NOW com Erro");  // Escreve a mensagem no display
    delay(2500);                    // Espera por 2,5 segundos
    ESP.restart();                  // Reinicia o dispositivo
  }

  esp_now_register_send_cb(OnDataSent);  // Registra a função de callback que é chamada quando os dados são enviados

  peerInfo.channel = 0;                             // Define o canal de comunicação como 0 na estrutura peerInfo
  peerInfo.encrypt = false;                         // Define a encriptação como desativada na estrutura peerInfo
  memcpy(peerInfo.peer_addr, slave1MacAddress, 6);  // Copia o endereço MAC do escravo 1 para a estrutura peerInfo

  // Tenta adicionar o escravo 1 à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o escravo 1, exibe mensagem de falha no display e reinicia o dispositivo
    lcd.setCursor(0, 0);
    lcd.print("Falha ao");
    lcd.setCursor(0, 1);
    lcd.print("adicionar peer1");
    delay(2500);
    ESP.restart();
  }

  memcpy(peerInfo.peer_addr, slave2MacAddress, 6);  // Copia o endereço MAC do escravo 2 para a estrutura peerInfo
  // Tenta adicionar o escravo 2 à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o escravo 2, exibe mensagem de falha no display e reinicia o dispositivo
    lcd.setCursor(0, 0);
    lcd.print("Falha ao");
    lcd.setCursor(0, 1);
    lcd.print("adicionar peer2");
    delay(2500);
    ESP.restart();
  }

  // Preparando os dados a serem enviados
  dataToSend.value = 0;
}

void loop() {

  esp_now_send(0, (uint8_t *)&dataToSend, sizeof(DataStruct));  // Envie os dados para todos os escravos cadastrados. Isso é definido através do primeiro argumento com valor 0
  delay(1000);                                                  // Espere 1 segundo antes de enviar novamente

  dataToSend.value++;  // Incrementa o valor de dataToSend.value

  // Se o valor inteiro ultrapassar 10, retorna para 0
  if (dataToSend.value > 10) {
    dataToSend.value = 0;  // o valor de dataToSend.value volta à ser Zero
  }
}

Nesse sketch, o Arduino funciona como o mestre e envia informações simultâneas para dois dispositivos escravos, cada um identificado pelo seu endereço MAC exclusivo. O sketch também utiliza um display LCD I2C para mostrar informações sobre o status da comunicação.

A estrutura “DataStruct” é utilizada para enviar os dados através do protocolo ESP-NOW e a estrutura “esp_now_peer_info_t” é utilizada para registrar informações sobre os dispositivos escravos na rede ESP-NOW.

A função “formatarNumero” é utilizada para formatar um número inteiro como uma string de dois dígitos para ser exibido no display.

A função OnDataSent é chamada automaticamente pelo protocolo de comunicação ESP-NOW sempre que um pacote de dados é enviado pelo mestre para um dispositivo escravo. Esta recebe como parâmetros o endereço MAC do dispositivo que recebeu os dados e o status do envio (ESP_NOW_SEND_SUCCESS para envio bem-sucedido ou ESP_NOW_SEND_FAIL para envio falhado).

Dentro da função, é verificado qual dispositivo escravo recebeu os dados comparando o endereço MAC recebido com os endereços MAC dos dispositivos escravos previamente definidos no código. Em seguida, a função verifica se o envio foi bem-sucedido e atualiza o display LCD com uma mensagem indicando se o envio foi bem-sucedido ou falhou.

No caso de um envio bem-sucedido, a função também escreve o valor enviado no display LCD junto com a mensagem de sucesso.

Na seção “setup” do código, é iniciada a comunicação I2C nos pinos SDA e SCL especificados, e o display LCD I2C é inicializado e ligado. Em seguida, é exibida uma mensagem de inicialização no display, informando que a comunicação ESP-NOW está sendo configurada.

Na seção “loop” do código, um valor é atribuído à variável “dataToSend.value” e enviado para os dois dispositivos escravos simultaneamente através da função “esp_now_send” com o primeiro parâmetro sendo 0. Quando a transmissão é bem sucedida, a função “OnDataSent” é chamada para exibir uma mensagem de confirmação no display LCD I2C. Se a transmissão falhar, uma mensagem de erro é exibida no display.

Esquemático do Escravo 1

Sketch do Escravo 1

/******************************************************************************
         Comunicação Unidirecional com mestre enviando para dois escravos
                              Sketch Escravo 1
                   Criado em 27 de Fevereiro de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Inclusão de bibliotecas
#include <LiquidCrystal_I2C.h>  // Biblioteca para utilizar um display de LCD com comunicação I2C
#include <esp_now.h>            // Biblioteca para utilizar o protocolo de comunicação ESP-NOW
#include <Wire.h>               // Biblioteca para comunicação I2C
#include <WiFi.h>               // Biblioteca para conectar em redes Wi-Fi

// Definição dos pinos utilizados
const uint8_t pinledVermelho = 13;  // LED vermelho conectado ao pino 13
const uint8_t pinledVerde = 12;     // LED verde conectado ao pino 12
const uint8_t pinSda = 14;          // Pino SDA para comunicação I2C
const uint8_t pinScl = 27;          // Pino SCL para comunicação I2C

// Estrutura de dados para troca de informações
struct DataStruct {
  int value;  // Dado do tipo inteiro
};

// Variável para armazenar o tempo da última recepção de dados
unsigned long temporizadorUltimaRecepcao = 0;

// Variável para armazenar os dados recebidos
DataStruct dataStruct;

// Objeto para utilizar o display de LCD de 16 colunas, 2 linhas e conectado no endereço I2C 0x27
LiquidCrystal_I2C lcd(0x27, 16, 2);

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
  memcpy(&dataStruct, incomingData, sizeof(dataStruct));  // Copia os dados recebidos para a estrutura de dados
  temporizadorUltimaRecepcao = millis();                  // Atualiza a variável de tempo da última recepção

  // Liga o LED verde e desliga o LED vermelho
  digitalWrite(pinledVerde, HIGH);
  digitalWrite(pinledVermelho, LOW);

  // Atualiza o display de LCD com informações sobre os dados recebidos
  lcd.setCursor(0, 0);
  lcd.print("Obtido ");
  lcd.print(len);
  lcd.print(" Bytes ");
  lcd.setCursor(0, 1);
  lcd.print("value: ");

  // Se o valor inteiro for menor que 10, adicionar um zero na frente
  if (dataStruct.value < 10) {
    lcd.print("0");
  }

  // Imprimir o valor inteiro na tela
  lcd.print(dataStruct.value);

  // Limpar a tela para garantir que não haja caracteres antigos
  lcd.print("       ");
}

void setup() {
  Wire.begin(pinSda, pinScl);  // Inicia a comunicação I2C do display LCD I2C conectado nos pinos informados

  lcd.init();       // Inicia o display de LCD
  lcd.backlight();  // Liga a luz de fundo do display LCD

  // Configura os pinos dos LEDs como saídas e inicialmente desliga ambos
  pinMode(pinledVerde, OUTPUT);
  pinMode(pinledVermelho, OUTPUT);
  digitalWrite(pinledVermelho, LOW);
  digitalWrite(pinledVerde, LOW);

  // Mensagem de inicialização no display de LCD
  lcd.setCursor(0, 0);
  lcd.print("   Comunicacao  ");
  lcd.setCursor(0, 1);
  lcd.print("     ESPNOW     ");
  delay(1200);
  lcd.clear();

  // Mensagem do modo de configuração mestre/escravo no display de LCD
  lcd.setCursor(0, 0);
  lcd.print("Mestre /");
  lcd.setCursor(0, 1);
  lcd.print("2 escravos");
  delay(1000);
  lcd.clear();

  // Mensagem informando que este dispositivo é escravo
  lcd.setCursor(0, 0);
  lcd.print("Este dispositivo");
  lcd.setCursor(0, 1);
  lcd.print("eh Escravo 1");
  delay(1000);
  lcd.clear();

  // Desconecta de alguma conexão WiFi anterior e define o modo estação (STA)
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);

  // Inicia a biblioteca ESP-NOW e, caso ocorra algum erro, reinicia o dispositivo
  if (esp_now_init() != ESP_OK) {
    lcd.setCursor(0, 0);
    lcd.print("Inicializacao   ");
    lcd.setCursor(0, 1);
    lcd.print("ESP-NOW com Erro");
    digitalWrite(pinledVermelho, HIGH);
    delay(2500);
    ESP.restart();
  }

  // Registra a função OnDataRecv como a função a ser chamada quando receber dados via ESP-NOW
  esp_now_register_recv_cb(OnDataRecv);

  // Define o tempo atual como o temporizador da última recepção de dados
  temporizadorUltimaRecepcao = millis();
}

void loop() {  // Função loop() que executa repetidamente enquanto a placa estiver ligada

  // Verifica se o tempo desde a última recepção é maior que 2 segundos
  if (millis() - temporizadorUltimaRecepcao > 2000) {
    digitalWrite(pinledVermelho, HIGH);  // Liga o LED vermelho indicando que a recepção ultrapassou o tempo limite
    digitalWrite(pinledVerde, LOW);      // Desliga o LED verde
    lcd.clear();                         // Limpa o display de LCD    

    // Mostra mensagem de tempo limite ultrapassado no display de LCD
    lcd.setCursor(0, 0);
    lcd.print("Tempo limite de ");
    lcd.setCursor(0, 1);
    lcd.print("recepcao ultrap.");
    delay(1000);  // Aguarda 1 segundo antes de continuar a execução
  }
}

Nesse exemplo, o código é para um dispositivo escravo, que recebe dados de um dispositivo mestre e os exibe em um display de LCD e acende um LED verde indicando que os dados foram recebidos com sucesso.

O código utiliza algumas bibliotecas, incluindo a “LiquidCrystal_I2C.h” para controlar o display de LCD e a “esp_now.h” para utilizar o protocolo de comunicação ESP-NOW. Também são definidos alguns pinos que serão utilizados, como os pinos para os LEDs e os pinos SDA e SCL para a comunicação I2C.

O dispositivo escravo tem uma estrutura de dados chamada “DataStruct” com um único campo do tipo inteiro chamado “value”. Quando o dispositivo mestre envia dados para o dispositivo escravo, ele copia esses dados para essa estrutura de dados e atualiza uma variável que armazena o tempo da última recepção de dados.

Quando a função “OnDataRecv” é chamada, ela copia os dados recebidos para a estrutura de dados “DataStruct” e atualiza a variável “temporizadorUltimaRecepcao” com o tempo atual. Em seguida, a função acende o LED verde e desliga o LED vermelho. Ela também atualiza o display de LCD com informações sobre os dados recebidos, como o número de bytes e o valor do campo “value”.

O setup é responsável pela configuração inicial do dispositivo, incluindo a configuração do display de LCD, dos LEDs e da comunicação ESP-NOW. Ele também exibe mensagens no display de LCD indicando o modo de configuração mestre/escravo e que o dispositivo é o escravo número 1. Em caso de erro durante a inicialização da comunicação ESP-NOW, o dispositivo reinicia.

No loop, é verificado se o tempo desde a última recepção é maior que 2 segundos e se o tempo de inatividade da recepção for maior, o LED vermelho é ligado, indicando que a recepção ultrapassou o tempo limite. Além disso, o LED verde é desligado e o display de LCD mostra uma mensagem indicando que o tempo limite de recepção foi ultrapassado.

Esquemático do Escravo 2

Sketch do Escravo 2
/******************************************************************************
         Comunicação Unidirecional com mestre enviando para dois escravos
                              Sketch Escravo 2
                   Criado em 27 de Fevereiro de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Inclusão de bibliotecas
#include <Adafruit_SSD1306.h>  // Biblioteca para utilizar um display de OLED
#include <esp_now.h>           // Biblioteca para utilizar o protocolo de comunicação ESP-NOW
#include <Wire.h>              // Biblioteca para comunicação I2C
#include <WiFi.h>              // Biblioteca para conectar em redes Wi-Fi

// Definição dos pinos utilizados
const uint8_t pinledVermelho = 12;  // LED vermelho conectado ao pino 13
const uint8_t pinledVerde = 14;     // LED verde conectado ao pino 12
const uint8_t pinSda = 27;          // Pino SDA para comunicação I2C
const uint8_t pinScl = 26;          // Pino SCL para comunicação I2C

// Estrutura de dados para troca de informações
struct DataStruct {
  int value;  // Dado do tipo inteiro
};

// Variável para armazenar o tempo da última recepção de dados
unsigned long temporizadorUltimaRecepcao = 0;

// Variável para armazenar os dados recebidos
DataStruct dataStruct;

// Cria uma instância da classe Adafruit_SSD1306 com as dimensões de 128x64 pixels,
// utilizando a interface I2C (Wire) e com o endereço padrão do display (-1).
Adafruit_SSD1306 display(128, 64, &Wire, -1);

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
  memcpy(&dataStruct, incomingData, sizeof(dataStruct));  // Copia os dados recebidos para a estrutura de dados
  temporizadorUltimaRecepcao = millis();                  // Atualiza a variável de tempo da última recepção

  // Liga o LED verde e desliga o LED vermelho
  digitalWrite(pinledVerde, HIGH);
  digitalWrite(pinledVermelho, LOW);


  // Limpa a tela do display OLED e define a cor do texto como branco
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);


  // Atualiza o display de LCD com informações sobre os dados recebidos
  display.setCursor(0, 0);
  display.setTextSize(2);
  display.print("Obtido ");
  display.print(len);
  display.println(" B");
  display.print("value: ");


  // Condição para verificar se o valor inteiro é menor que 10
  if (dataStruct.value < 10) {
    // Se sim, exibe um zero à esquerda na tela
    display.print("0");
  }

  // Imprime o valor da variável 'dataStruct.value' na tela OLED
  display.println(dataStruct.value);
  // Atualiza a tela OLED para exibir as mudanças
  display.display();
}

void setup() {
  Wire.begin(pinSda, pinScl);  // Inicia a comunicação I2C do display LCD I2C conectado nos pinos informados

  // Inicia a comunicação com o display OLED SSD1306
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // Se a alocação do display falhar, ...
    Serial.begin(115200);                            // Inicia a comunicação serial com uma taxa de 115200 bps
    Serial.println("SSD1306 allocation failed");     // Imprime uma mensagem de erro na porta serial
    delay(10000);                                    // Espera 10 segundos
    ESP.restart();                                   // Reinicia o dispositivo ESP
  }

  display.clearDisplay();  // Limpa o display do OLED.

  // Configura os pinos dos LEDs como saídas e inicialmente desliga ambos
  pinMode(pinledVerde, OUTPUT);
  pinMode(pinledVermelho, OUTPUT);
  digitalWrite(pinledVermelho, LOW);
  digitalWrite(pinledVerde, LOW);

  // Mensagem de inicialização no display de LCD
  display.setCursor(0, 0);
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.println("   Comunicacao  ");
  display.println("     ESPNOW     ");
  display.display();
  delay(1200);
  display.clearDisplay();


  // Mensagem do modo de configuração mestre/escravo no display de LCD
  display.setCursor(0, 0);
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.println("   Mestre /");
  display.println("  2 escravos");
  display.display();
  delay(1000);
  display.clearDisplay();

  // Mensagem informando que este dispositivo é escravo
  display.setCursor(0, 0);
  display.println("   Este dispositivo");
  display.println("     eh Escravo 2");
  display.display();
  delay(1000);
  display.clearDisplay();

  // Desconecta de alguma conexão WiFi anterior e define o modo estação (STA)
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);


  // Inicia a biblioteca ESP-NOW e, caso ocorra algum erro, reinicia o dispositivo
  if (esp_now_init() != ESP_OK) {
    display.setCursor(0, 0);
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.println("   Inicializacao");
    display.println("  ESP-NOW com Erro");
    digitalWrite(pinledVermelho, HIGH);
    display.display();
    delay(2500);
    ESP.restart();
  }

  // Registra a função OnDataRecv como a função a ser chamada quando receber dados via ESP-NOW
  esp_now_register_recv_cb(OnDataRecv);

  // Define o tempo atual como o temporizador da última recepção de dados
  temporizadorUltimaRecepcao = millis();
}

void loop() {  // Função loop() que executa repetidamente enquanto a placa estiver ligada

  // Verifica se o tempo desde a última recepção é maior que 2 segundos
  if (millis() - temporizadorUltimaRecepcao > 2000) {
    digitalWrite(pinledVermelho, HIGH);   // Liga o LED vermelho indicando que a recepção ultrapassou o tempo limite
    digitalWrite(pinledVerde, LOW);       // Desliga o LED verde
    display.clearDisplay();               // Limpa o display do OLED
    display.setCursor(0, 0);              // Posiciona o cursor no início do display
    display.setTextSize(1);               // Define o tamanho do texto do display
    display.setTextColor(SSD1306_WHITE);  // Define a cor do texto do display

    // Mostra mensagem de tempo limite ultrapassado no display de OLED
    display.println("Tempo limite de ");
    display.println("recepcao ultrapassado");
    display.display();  // Exibe a mensagem no display
    delay(1000);        // Aguarda 1 segundo antes de continuar a execução
  }
}

Esse é um sketch para um dispositivo escravo que usa o protocolo de comunicação ESP-NOW para receber dados enviados por um dispositivo mestre. O objetivo é exibir os dados recebidos em um display OLED e acender um LED verde indicando que houve uma transmissão bem-sucedida.

Na seção “Inclusão de bibliotecas”, são importadas as bibliotecas necessárias para o código funcionar corretamente: Adafruit_SSD1306 para o display OLED, esp_now para o protocolo de comunicação e Wire para a comunicação I2C.

Em seguida, são definidos os pinos usados pelo dispositivo, como o LED vermelho, o LED verde e os pinos SDA e SCL para a comunicação I2C.

A estrutura de dados DataStruct é criada com um único campo de tipo inteiro chamado value, para armazenar os dados recebidos.

Depois, há uma variável temporizadorUltimaRecepcao que armazena o tempo da última recepção de dados e a variável dataStruct que armazena os dados recebidos. Essas variáveis serão usadas para verificar se houve alguma transmissão de dados e atualizar o display OLED com as informações recebidas.

O objeto display é criado usando a biblioteca Adafruit_SSD1306 para controlar o display OLED. A comunicação I2C é configurada usando os pinos SDA e SCL.

A função OnDataRecv é chamada sempre que os dados são recebidos via ESP-NOW. Nessa função, os dados recebidos são copiados para a estrutura de dados dataStruct, a variável temporizadorUltimaRecepcao é atualizada e o LED verde é ligado e o vermelho é desligado. Em seguida, o display OLED é limpo e atualizado com informações sobre os dados recebidos.

No setup, a comunicação I2C é iniciada usando os pinos SDA e SCL e a comunicação com o display OLED SSD1306 é iniciada. Se a alocação do display falhar, uma mensagem de erro é impressa na porta serial e o dispositivo é reiniciado. Os pinos dos LEDs são configurados como saída e ambos são desligados. Em seguida, mensagens de inicialização são exibidas no display OLED.

No loop, o dispositivo fica em espera para receber dados. Se a transmissão for bem-sucedida, a função OnDataRecv é chamada para atualizar as informações recebidas no display OLED e acender o LED verde. Se não houver nenhuma transmissão nos últimos 2 segundos, o LED verde é desligado e o vermelho é aceso, indicando que não houve comunicação.

Demonstração do Funcionamento


Comunicação Unidirecional com um Escravo Recebendo de dois Mestres

A comunicação unidirecional com um escravo recebendo dados de dois mestres é aquela em que um único escravo recebe dados de dois mestres diferentes, sem que haja retorno de dados do escravo para os mestres pelo mesmo canal de comunicação.

Nesse modo de comunicação, o escravo pode receber informações específicas de cada mestre, permitindo que o dispositivo execute ações específicas para cada mestre. Por exemplo, em um sistema de controle de tráfego em uma cidade, um semáforo pode receber dados de dois mestres diferentes: um mestre responsável pelo controle do fluxo de veículos e outro mestre responsável pelo controle de pedestres.

Dessa forma, cada mestre pode enviar comandos específicos para o semáforo, permitindo que o dispositivo ajuste o tempo de sinalização de acordo com as necessidades de cada mestre. Essa comunicação unidirecional com um escravo recebendo dados de dois mestres é útil em situações em que diferentes dispositivos precisam receber informações específicas de diferentes fontes, sem que haja conflito entre os dados recebidos.

Software e Hardware

Esquemático do Mestre 1

Sketch do Mestre 1

/******************************************************************************
        Comunicação Unidirecional com dois mestres enviando para um escravo
                              Sketch Mestre 1
                   Criado em 27 de Fevereiro de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Inclusão de bibliotecas
#include <LiquidCrystal_I2C.h>  // Biblioteca para utilizar um display de LCD com comunicação I2C
#include <esp_now.h>            // Biblioteca para utilizar o protocolo de comunicação ESP-NOW
#include <Wire.h>               // Biblioteca para comunicação I2C
#include <WiFi.h>               // Biblioteca para conectar em redes Wi-Fi

// Definição dos pinos utilizados
const uint8_t pinledVermelho = 13;  // LED vermelho conectado ao pino 13
const uint8_t pinledVerde = 12;     // LED verde conectado ao pino 12
const uint8_t pinSda = 14;          // Pino SDA para comunicação I2C
const uint8_t pinScl = 27;          // Pino SCL para comunicação I2C

// Estrutura de dados para troca de informações
struct DataStruct {
  int value;  // Dado do tipo inteiro
};

const uint8_t slaveMacAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };  // Define o endereço MAC do dispositivo escravo. Coloque o endereço MAC de sua placa aqui

esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

DataStruct dataToSend;  // Declara a estrutura dataToSend

// Objeto para utilizar o display de LCD de 16 colunas, 2 linhas e conectado no endereço I2C 0x27
LiquidCrystal_I2C lcd(0x27, 16, 2);

// Função para formatar um número inteiro como uma string
String formatarNumero(int number) {
  // Inicializa uma variável temporária como uma string vazia
  String temp = "";

  // Se o número fornecido for menor que 10, adiciona um "0" à string temporária
  if (number < 10) {
    temp = "0";
  }

  // Adiciona o número à string temporária
  temp += number;

  // Retorna a string formatada
  return temp;
}

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  if (status == ESP_NOW_SEND_SUCCESS) {  // Se o envio foi bem sucedido, ...
    lcd.setCursor(0, 0);                 // Define a posição do cursor do display
    lcd.print("Envio OK:");
    lcd.print(formatarNumero(dataToSend.value));  // Escreve o valor no display
    lcd.print("     ");                           // Limpar a tela para garantir que não haja caracteres antigos
    // Acionamento dos LEDs para indicar sucesso no envio
    digitalWrite(pinledVerde, HIGH);
    digitalWrite(pinledVermelho, LOW);
  } else {
    lcd.setCursor(0, 0);  // Define a posição do cursor do display
    lcd.print("FALHA no envio  ");
    digitalWrite(pinledVerde, LOW);
    digitalWrite(pinledVermelho, HIGH);
  }
}

void setup() {
  Wire.begin(pinSda, pinScl);  // Inicia a comunicação I2C do display LCD I2C conectado nos pinos informados

  lcd.init();       // Inicia o display de LCD
  lcd.backlight();  // Liga a luz de fundo do display LCD

  // Configura os pinos dos LEDs como saídas e inicialmente desliga ambos
  pinMode(pinledVerde, OUTPUT);
  pinMode(pinledVermelho, OUTPUT);
  digitalWrite(pinledVermelho, LOW);
  digitalWrite(pinledVerde, LOW);

  // Mensagem de inicialização no display de LCD
  lcd.setCursor(0, 0);
  lcd.print("   Comunicacao  ");
  lcd.setCursor(0, 1);
  lcd.print("     ESPNOW     ");
  delay(1200);
  lcd.clear();

  // Mensagem do modo de configuração mestre/escravo no display de LCD
  lcd.setCursor(0, 0);
  lcd.print("2 Mestres /");
  lcd.setCursor(0, 1);
  lcd.print("1 escravo");
  delay(1000);
  lcd.clear();

  // Mensagem informando que este dispositivo é escravo
  lcd.setCursor(0, 0);
  lcd.print("Este dispositivo");
  lcd.setCursor(0, 1);
  lcd.print("eh Mestre 1");
  delay(1000);
  lcd.clear();

  // Desconecta de alguma conexão WiFi anterior e define o modo estação (STA)
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);

  // Inicia a biblioteca ESP-NOW e, caso ocorra algum erro, reinicia o dispositivo
  if (esp_now_init() != ESP_OK) {
    lcd.setCursor(0, 0);
    lcd.print("Inicializacao   ");
    lcd.setCursor(0, 1);
    lcd.print("ESP-NOW com Erro");
    digitalWrite(pinledVermelho, HIGH);
    delay(2500);
    ESP.restart();
  }

  esp_now_register_send_cb(OnDataSent);  // Registra a função de callback que é chamada quando os dados são enviados

  peerInfo.channel = 0;                            // Define o canal de comunicação como 0 na estrutura peerInfo
  peerInfo.encrypt = false;                        // Define a encriptação como desativada na estrutura peerInfo
  memcpy(peerInfo.peer_addr, slaveMacAddress, 6);  // Copia o endereço MAC do escravo para a estrutura peerInfo
  // Tenta adicionar o escravo à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o escravo, exibe mensagem de falha no display e reinicia o dispositivo
    lcd.setCursor(0, 0);
    lcd.print("Falha ao        ");
    lcd.setCursor(0, 1);
    lcd.print("adicionar peer  ");
    delay(2500);
    ESP.restart();
  }
}

void loop() {  // Função loop() que executa repetidamente enquanto a placa estiver ligada

  esp_now_send(slaveMacAddress, (uint8_t *)&dataToSend, sizeof(DataStruct));  // Envie os dados para todos os escravos cadastrados. Isso é definido através do primeiro argumento com valor 0
  delay(1000);                                                                // Espere 1 segundo antes de enviar novamente
  dataToSend.value++;                                                         // Incrementa o valor de dataToSend.value

  // Se o valor inteiro ultrapassar 10, retorna para 0
  if (dataToSend.value > 10) {
    dataToSend.value = 0;  // o valor de dataToSend.value volta à ser Zero
  }
}

Neste código, o dispositivo mestre 1 envia dados para o dispositivo escravo através do endereço MAC definido. Além disso, é exibido no display LCD se houve falha ou sucesso no envio dos dados.

No início do código, as bibliotecas necessárias são incluídas, e os pinos usados ​​são definidos. Há a definição da estrutura DataStruct usada para enviar dados e o endereço MAC do dispositivo escravo que será usado na rede ESPNOW.

O display LCD I2C é inicializado com o endereço e as dimensões necessárias. Há a configuração dos pinos dos LEDs, a mensagem de inicialização do display e, em seguida, uma mensagem indicando o modo de configuração mestre/escravo e qual dispositivo é o mestre 1.

A função “formatarNumero” é utilizada para formatar o valor de um número inteiro como uma string.

A função OnDataSent é definida para ser executada quando os dados são enviados via ESP-NOW. Se o envio for bem-sucedido, o valor é escrito no display e os LEDs verde e vermelho são acionados, indicando que o envio foi bem-sucedido ou não, respectivamente.

No setup, a comunicação I2C é iniciada, o display é inicializado e a luz de fundo é ligada. O restante do código é dedicado à configuração da comunicação ESP-NOW. O peerInfo é preenchido com o endereço MAC do dispositivo escravo e ele é adicionado à lista de dispositivos da rede ESPNOW.

No loop, a cada execução, o programa envia uma estrutura de dados (DataStruct) para o escravo cadastrado na rede, utilizando a função esp_now_send(). A estrutura de dados contém um valor inteiro (dataToSend.value),incrementado em uma unidade a cada execução do loop. Além disso, há um delay de 1 segundo antes de enviar novamente os dados para os escravos. E, se o valor inteiro da estrutura de dados ultrapassar 10, ele é reiniciado para 0, utilizando uma estrutura de controle if.

Esquemático do Mestre 2

Sketch do Mestre 2

/******************************************************************************
        Comunicação Unidirecional com dois mestres enviando para um escravo
                              Sketch Mestre 2
                   Criado em 27 de Fevereiro de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Inclusão de bibliotecas
#include <Adafruit_SSD1306.h>  // Biblioteca para utilizar um display de OLED
#include <esp_now.h>           // Biblioteca para utilizar o protocolo de comunicação ESP-NOW
#include <Wire.h>              // Biblioteca para comunicação I2C
#include <WiFi.h>              // Biblioteca para conectar em redes Wi-Fi

// Definição dos pinos utilizados
const uint8_t pinledVermelho = 12;  // LED vermelho conectado ao pino 13
const uint8_t pinledVerde = 14;     // LED verde conectado ao pino 12
const uint8_t pinSda = 27;          // Pino SDA para comunicação I2C
const uint8_t pinScl = 26;          // Pino SCL para comunicação I2C

// Estrutura de dados para troca de informações
struct DataStruct {
  int value;  // Dado do tipo inteiro
};

const uint8_t slaveMacAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };  // Define o endereço MAC do dispositivo escravo. Coloque o endereço MAC de sua placa aqui

esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

DataStruct dataToSend;  // Declara a estrutura dataToSend

// Cria uma instância da classe Adafruit_SSD1306 com as dimensões de 128x64 pixels,
// utilizando a interface I2C (Wire) e com o endereço padrão do display (-1).
Adafruit_SSD1306 display(128, 64, &Wire, -1);

// Função para formatar um número inteiro como uma string
String formatarNumero(int number) {
  // Inicializa uma variável temporária como uma string vazia
  String temp = "";

  // Se o número fornecido for menor que 10, adiciona um "0" à string temporária
  if (number < 10) {
    temp = "0";
  }

  // Adiciona o número à string temporária
  temp += number;

  // Retorna a string formatada
  return temp;
}

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  if (status == ESP_NOW_SEND_SUCCESS) {  // Se o envio foi bem sucedido, ...

    // Limpa a tela do display OLED e define a cor do texto como branco
    display.clearDisplay();
    display.setTextColor(SSD1306_WHITE);

    // Atualiza o display de OLED com informações sobre os dados enviados
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(2);
    display.println("Envio OK ");
    display.println();
    display.print(formatarNumero(dataToSend.value));
    display.display();

    // Acionamento dos LEDs para indicar sucesso no envio
    digitalWrite(pinledVerde, HIGH);
    digitalWrite(pinledVermelho, LOW);
  } else {
    // Limpa a tela do display OLED e define a cor do texto como branco
    display.clearDisplay();
    display.setTextColor(SSD1306_WHITE);

    // Atualiza o display de OLED com informações sobre os dados enviados
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(2);
    display.println("Insucesso");
    display.display();

    // Acionamento dos LEDs para indicar sucesso no envio
    digitalWrite(pinledVerde, LOW);
    digitalWrite(pinledVermelho, HIGH);
  }
}

void setup() {
  Wire.begin(pinSda, pinScl);  // Inicia a comunicação I2C do display OLED I2C conectado nos pinos informados

  // Inicia a comunicação com o display OLED SSD1306
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // Se a alocação do display falhar, ...
    Serial.begin(115200);                            // Inicia a comunicação serial com uma taxa de 115200 bps
    Serial.println("SSD1306 allocation failed");     // Imprime uma mensagem de erro na porta serial
    delay(10000);                                    // Espera 10 segundos
    ESP.restart();                                   // Reinicia o dispositivo ESP
  }

  display.clearDisplay();  // Limpa o display do OLED.

  // Configura os pinos dos LEDs como saídas e inicialmente desliga ambos
  pinMode(pinledVerde, OUTPUT);
  pinMode(pinledVermelho, OUTPUT);
  digitalWrite(pinledVermelho, LOW);
  digitalWrite(pinledVerde, LOW);

  // Mensagem de inicialização no display de OLED
  display.setCursor(0, 0);
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.println("   Comunicacao  ");
  display.println("     ESPNOW     ");
  display.display();
  delay(1200);
  display.clearDisplay();


  // Mensagem do modo de configuração mestre/escravo no display de OLED
  display.setCursor(0, 0);
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.println(" 2 Mestre /");
  display.println(" 1 escravo  ");
  display.display();
  delay(1000);
  display.clearDisplay();

  // Mensagem informando que este dispositivo é escravo
  display.setCursor(0, 0);
  display.println("   Este dispositivo");
  display.println("     eh Mestre 2");
  display.display();
  delay(1000);
  display.clearDisplay();

  // Desconecta de alguma conexão WiFi anterior e define o modo estação (STA)
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);


  // Inicia a biblioteca ESP-NOW e, caso ocorra algum erro, reinicia o dispositivo
  if (esp_now_init() != ESP_OK) {
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.println("   Inicializacao");
    display.println("  ESP-NOW com Erro");
    digitalWrite(pinledVermelho, HIGH);
    display.display();
    delay(2500);
    ESP.restart();
  }

  esp_now_register_send_cb(OnDataSent);  // Registra a função de callback que é chamada quando os dados são enviados

  peerInfo.channel = 0;                            // Define o canal de comunicação como 0 na estrutura peerInfo
  peerInfo.encrypt = false;                        // Define a encriptação como desativada na estrutura peerInfo
  memcpy(peerInfo.peer_addr, slaveMacAddress, 6);  // Copia o endereço MAC do escravo para a estrutura peerInfo
  // Tenta adicionar o escravo à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o escravo, exibe mensagem de falha no display e reinicia o dispositivo
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println("Falha ao        ");
    display.println("adicionar peer  ");
    display.display();
    delay(2500);
    ESP.restart();
  }
}

void loop() {  // Função loop() que executa repetidamente enquanto a placa estiver ligada

  esp_now_send(slaveMacAddress, (uint8_t *)&dataToSend, sizeof(DataStruct));  // Envie os dados para todos os escravos cadastrados. Isso é definido através do primeiro argumento com valor 0
  delay(1000);                                                                // Espere 1 segundo antes de enviar novamente
  dataToSend.value--;                                                         // Decrementa o valor de dataToSend.value

  // Se o valor inteiro ultrapassar 10, retorna para 0
  if (dataToSend.value < 0) {
    dataToSend.value = 10;  // o valor de dataToSend.value volta à ser Dez
  }
}

Neste código, o dispositivo mestre 2 envia dados para o dispositivo escravo através do endereço MAC definido. Além disso, é exibido no display OLED se houve falha ou sucesso no envio dos dados.

A primeira parte do código é a inclusão de bibliotecas, como a biblioteca para utilizar um display de OLED, a biblioteca para utilizar o protocolo de comunicação ESP-NOW, a biblioteca para comunicação I2C e a biblioteca para conectar em redes Wi-Fi.

Em seguida, são definidos os pinos utilizados no sketch, o LED vermelho, conectado ao pino 13; o LED verde, conectado ao pino 12 e os pinos SDA (27) e SCL (26), para comunicação I2C.

A estrutura DataStruct é definida para a troca de informações, contendo apenas um campo do tipo inteiro. O endereço MAC do dispositivo escravo é definido em slaveMacAddress. É criada uma estrutura esp_now_peer_info_t, utilizada para registrar informações sobre o dispositivo peer que será adicionado à rede ESP-NOW. A instância da classe Adafruit_SSD1306 também é criada para controlar o display OLED conectado.

A função formatarNumero é definida para formatar um número inteiro como uma string. Já OnDataSent é chamada quando dados são recebidos via ESP-NOW. Ele atualiza o display OLED e aciona os LEDs conforme o status do envio.

Na função setup(), a comunicação I2C é iniciada com o display OLED, o display é inicializado e os pinos dos LEDs são configurados como saídas. Em seguida, é adicionado o dispositivo peer à rede ESP-NOW, com seu endereço MAC definido anteriormente. A função esp_now_register_send_cb é chamada para registrar a função OnDataSent como callback que será executada quando os dados forem enviados via ESP-NOW.

No loop, a cada execução, o programa envia uma estrutura de dados (DataStruct) para o escravo cadastrado na rede, utilizando a função esp_now_send(). A estrutura de dados contém um valor inteiro (dataToSend.value), que é decrementado em uma unidade a cada execução do loop. Além disso, há um delay de 1 segundo antes de enviar novamente os dados para os escravos. E, se o valor inteiro da estrutura de dados ficar menor que 0, ele é reiniciado para 10, utilizando uma estrutura de controle if.

Esquemático do Escravo

Sketch do Escravo

/******************************************************************************
        Comunicação Unidirecional com dois mestres enviando para um escravo
                              Sketch Escravo
                   Criado em 28 de Fevereiro de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

#include <LiquidCrystal_I2C.h>  // Inclui a biblioteca LiquidCrystal_I2C para controle do display LCD I2C
#include <esp_now.h>            // Inclui a biblioteca esp_now para o uso do protocolo de comunicação ESP-NOW
#include <Wire.h>               // Inclui a biblioteca Wire para a comunicação I2C
#include <WiFi.h>               // Inclui a biblioteca WiFi para configuração da rede sem fio

const uint8_t pinSda = 14;  // Define o pino SDA como 14 para a comunicação I2C
const uint8_t pinScl = 27;  // Define o pino SCL como 27 para a comunicação I2C

struct DataStruct {  // Define a estrutura DataStruct para troca de informações
  int value;         // Variável inteira value
};

const uint8_t macAddrMestre1[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Endereço MAC do dispositivo mestre 1. Coloque o endereço MAC de sua placa aqui
const uint8_t macAddrMestre2[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Endereço MAC do dispositivo mestre 2. Coloque o endereço MAC de sua placa aqui

esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

DataStruct dadosMestre1;  // Declara a estrutura dadosMestre1
DataStruct dadosMestre2;  // Declara a estrutura dadosMestre2

LiquidCrystal_I2C lcd(0x27, 16, 2);  // Declara o objeto lcd para controle do display LCD I2C

// Função para formatar um número inteiro como uma string
String formatarNumero(int number) {
  // Inicializa uma variável temporária como uma string vazia
  String temp = "";

  // Se o número fornecido for menor que 10, adiciona um "0" à string temporária
  if (number < 10) {
    temp = "0";
  }

  // Adiciona o número à string temporária
  temp += number;

  // Retorna a string formatada
  return temp;
}

void OnDataRecv(const uint8_t *mac_addr, const uint8_t *incomingData, int len) {  // Função chamada quando os dados são recebidos
  if (memcmp(mac_addr, macAddrMestre1, 6) == 0) {
    memcpy(&dadosMestre1, incomingData, sizeof(DataStruct));  // Copia os dados recebidos para a estrutura de dados
    lcd.setCursor(0, 0);                                      // Define a posição do cursor do display
    lcd.print("mestre1: ");
    lcd.print(formatarNumero(dadosMestre1.value));  // Escreve o valor no display
    lcd.print("     ");                             // Limpar a tela para garantir que não haja caracteres antigos
  } else if (memcmp(mac_addr, macAddrMestre2, 6) == 0) {
    memcpy(&dadosMestre2, incomingData, sizeof(DataStruct));  // Copia os dados recebidos para a estrutura de dados
    lcd.setCursor(0, 1);
    lcd.print("mestre2: ");
    lcd.print(formatarNumero(dadosMestre2.value));  // Escreve o valor no display
    lcd.print("     ");                             // Limpar a tela para garantir que não haja caracteres antigos
  }
}

void setup() {
  Wire.begin(pinSda, pinScl);  // Inicia a comunicação I2C com os pinos SDA e SCL definidos anteriormente
  lcd.init();                  // Inicializa o display
  lcd.backlight();             // Acende o backlight do display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("   Comunicacao  ");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("     ESPNOW     ");  // Escreve a mensagem no display
  delay(1200);                    // Espera por 1,2 segundos
  lcd.clear();                    // Limpa o display

  lcd.setCursor(0, 0);       // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("2 Mestres /");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);       // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("1 escravo");    // Escreve a mensagem no display
  delay(1000);               // Espera por 1 segundo
  lcd.clear();               // Limpa o display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("Este dispositivo");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("eh Escravo");        // Escreve a mensagem no display
  delay(1000);                    // Espera por 1 segundo
  lcd.clear();                    // Limpa o display

  WiFi.disconnect();    // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_STA);  // Define o modo WiFi como Station (cliente)
  Serial.begin(115200);
  Serial.println(WiFi.macAddress());

  if (esp_now_init() != ESP_OK) {   // Inicializa o ESP-NOW e verifica se há erros
    lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
    lcd.print("Inicializacao ");    // Escreve a mensagem no display
    lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
    lcd.print("ESP-NOW com Erro");  // Escreve a mensagem no display
    delay(2500);                    // Espera por 2,5 segundos
    ESP.restart();                  // Reinicia o dispositivo
  }

  esp_now_register_recv_cb(OnDataRecv);  // Registra a função de callback que é chamada quando os dados são recebidos
}

void loop() {
  delay(1);  // Delay de 1 milissegundo
}

Neste código, os dois dispositivos mestres enviam dados para o dispositivo escravo, que os exibe em um display LCD I2C.

A biblioteca LiquidCrystal_I2C é incluída para controlar o display LCD I2C, esp_now.h é incluído para o uso do protocolo de comunicação ESP-NOW e a biblioteca Wire é incluída para a comunicação I2C.

A estrutura DataStruct é definida para troca de informações e composta por uma variável inteira chamada value. São declaradas duas variáveis do tipo DataStruct, dadosMestre1 e dadosMestre2, para armazenar os dados recebidos de cada mestre.

A função OnDataRecv é chamada sempre que dados são recebidos. Ela verifica o endereço MAC do dispositivo que enviou os dados e copia-os para a variável correspondente (dadosMestre1 ou dadosMestre2). Em seguida, a mensagem recebida é exibida no display LCD.

A função setup é executada apenas uma vez no início do código. Ela inicia a comunicação I2C com os pinos SDA e SCL especificados, inicializa o display LCD e acende o backlight. Em seguida, exibe mensagens de inicialização no display.

A função loop não é usada neste código porque a lógica está toda incluída na função OnDataRecv. Portanto, após a inicialização, o dispositivo escravo aguarda os dados serem recebidos pelos mestres e os exibe no display LCD.

Demonstração do Funcionamento


Comunicação Bidirecional com dois ESP32s

A comunicação bidirecional com dois ESP32s é aquela em que dois dispositivos ESP32 podem trocar informações entre si. Isso permite que ambos os dispositivos enviem e recebam dados um do outro.

Nesse modo de comunicação, cada dispositivo pode enviar e receber informações específicas do outro dispositivo. Por exemplo, em um sistema de controle de acesso a um prédio, um ESP32 pode ser responsável por ler cartões de acesso, enquanto o outro pode ser responsável por controlar a abertura das portas.

Dessa forma, cada dispositivo pode enviar comandos específicos para o outro dispositivo e receber informações sobre o estado atual do sistema. Essa comunicação bidirecional entre dois ESP32s é útil em situações em que diferentes dispositivos precisam trocar informações entre si para coordenar suas ações e tomar decisões conjuntas.

Software e Hardware

Esquemático do ESP32 1

Sketch do ESP32 1

/******************************************************************************
                    Comunicação Bidirecional com dois ESP32s
                              Sketch ESP32 1
                      Criado em 08 de Março de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

#include <LiquidCrystal_I2C.h>  // Inclui a biblioteca LiquidCrystal_I2C para controle do display LCD I2C
#include <esp_now.h>            // Inclui a biblioteca esp_now para o uso do protocolo de comunicação ESP-NOW
#include <Wire.h>               // Inclui a biblioteca Wire para a comunicação I2C
#include <WiFi.h>               // Inclui a biblioteca WiFi para configuração da rede sem fio

const uint8_t pinSda = 14;  // Define o pino SDA como 14 para a comunicação I2C
const uint8_t pinScl = 27;  // Define o pino SCL como 27 para a comunicação I2C

uint8_t macAddress2Esp32[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };  // Define o endereço MAC do outro ESP32. Coloque o endereço da sua placa aqui

struct DataStruct {  // Define a estrutura DataStruct para troca de informações
  int value;         // Variável inteira value
};

DataStruct dataSend;  // Declara a estrutura dataSend
DataStruct dataRecv;  // Declara a estrutura dataRecv

bool recvJaExecutado = false;

esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

LiquidCrystal_I2C lcd(0x27, 16, 2);  // Declara o objeto lcd para controle do display LCD I2C

// Variável para armazenar o tempo da última recepção de dados
unsigned long temporizadorUltimaRecepcao = 0;

// Função para formatar um número inteiro como uma string
String formatarNumero(int number) {
  // Inicializa uma variável temporária como uma string vazia
  String temp = "";
  // Se o número fornecido for menor que 10, adiciona um "0" à string temporária
  if (number < 10) {
    temp = "0";
  }
  // Adiciona o número à string temporária
  temp += number;
  // Retorna a string formatada
  return temp;
}

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {  // Função chamada quando os dados são enviados
  if (status == ESP_NOW_SEND_SUCCESS) {                                   // Se o envio foi bem sucedido, ...
    dataSend.value++;                                                     // Incrementa o valor inteiro

    // Se o valor inteiro ultrapassar 10, retorna para 0
    if (dataSend.value > 10) {
      dataSend.value = 0;  // o valor de dataSend.value volta à ser Zero
    }
  }
}

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
  memcpy(&dataRecv, incomingData, sizeof(dataRecv));  // Copia os dados recebidos para a estrutura de dados
  recvJaExecutado = true;                             // Define a variável recvJaExecutado como verdadeira para indicar que a função OnDataRecv já foi executada alguam vez
  temporizadorUltimaRecepcao = millis();              // Atualiza a variável de tempo da última recepção
}


void setup() {
  Wire.begin(pinSda, pinScl);  // Inicia a comunicação I2C com os pinos SDA e SCL definidos anteriormente
  lcd.init();                  // Inicializa o display
  lcd.backlight();             // Acende o backlight do display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("   Comunicacao  ");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("     ESPNOW     ");  // Escreve a mensagem no display
  delay(1200);                    // Espera por 1,2 segundos
  lcd.clear();                    // Limpa o display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("  Bidirecional  ");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("ESP32 <-> ESP32 ");  // Escreve a mensagem no display
  delay(1000);                    // Espera por 1 segundo
  lcd.clear();                    // Limpa o display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("Este dispositivo");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("eh ESP32 1");        // Escreve a mensagem no display
  delay(1000);                    // Espera por 1 segundo
  lcd.clear();                    // Limpa o display

  WiFi.disconnect();    // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_STA);  // Define o modo WiFi como Station (cliente)

  if (esp_now_init() != ESP_OK) {   // Inicializa o ESP-NOW e verifica se há erros
    lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
    lcd.print("Inicializacao ");    // Escreve a mensagem no display
    lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
    lcd.print("ESP-NOW com Erro");  // Escreve a mensagem no display
    delay(2500);                    // Espera por 2,5 segundos
    ESP.restart();                  // Reinicia o dispositivo
  }

  esp_now_register_send_cb(OnDataSent);  // Registra a função de callback que é chamada quando os dados são enviados
  esp_now_register_recv_cb(OnDataRecv);  // Registra a função de callback que é chamada quando os dados são recebidos

  memcpy(peerInfo.peer_addr, macAddress2Esp32, 6);  // Copia o endereço MAC do outro ESP32 para a estrutura peerInfo
  peerInfo.channel = 0;                             // Define o canal de comunicação como 0 na estrutura peerInfo
  peerInfo.encrypt = false;                         // Define a encriptação como desativada na estrutura peerInfo

  // Tenta adicionar o outro ESP32 à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o outro ESP32, exibe mensagem de falha no display e reinicia o dispositivo
    lcd.setCursor(0, 0);
    lcd.print("Falha ao");
    lcd.setCursor(0, 1);
    lcd.print("adicionar peer");
    delay(2500);
    ESP.restart();
  }
  // Define o tempo atual como o temporizador da última recepção de dados
  temporizadorUltimaRecepcao = millis();
}

void loop() {

  esp_now_send(macAddress2Esp32, (uint8_t *)&dataSend, sizeof(DataStruct));  // Envie os dados para o endereço MAC do outro ESP32

  // Verifica se o tempo desde a última recepção é maior que 2 segundos
  if (millis() - temporizadorUltimaRecepcao > 2000) {
    // Mostra mensagem de tempo limite ultrapassado no display de LCD
    lcd.setCursor(0, 1);
    lcd.print("Falha recepcao  ");
  } else {
    if (recvJaExecutado) {  // se a função já foi executada alguam vez, ...
      // Atualiza o display de LCD com informações sobre os dados recebidos
      lcd.setCursor(0, 1);
      lcd.print("Recebido:");
      lcd.print(formatarNumero(dataRecv.value));
      lcd.print("     ");
    } else {  // se não, não mostra nada (na primeira vez de execução do programa)
      lcd.setCursor(0, 1);
      lcd.print("                ");
    }
  }

  static int valuePrevious;  // variável estática que armazena o último valor de dataSend.value

  if (dataSend.value == valuePrevious) {  // se o valor atual de dataSend.value for igual à valuePrevious, mostra falha no envio
    lcd.setCursor(0, 0);                  // Define a posição do cursor do display
    lcd.print("Falha no Envio  ");        // Escreve a mensagem de falha no display
  } else {
    lcd.setCursor(0, 0);                        // Define a posição do cursor do display
    lcd.print("Enviado :");                     // Escreve a mensagem no display
    lcd.print(formatarNumero(dataSend.value));  // exibe no dispaly lcd o conteúdo da variável dataSend.value
    lcd.print("     ");
    valuePrevious = dataSend.value;  // atualiza o valor de valuePrevious com o último valor de dataSend.value
  }

  delay(1000);  // Espera 1 segundo
}

Neste sketch, o ESP32 envia dados e também recebe dados do ESP32 2.

O código começa importando as bibliotecas requeridas para o uso do ESP-NOW e do display LCD I2C. Também configura o pino SDA e SCL para a conexão I2C e o endereço MAC do outro ESP32.

A estrutura DataStruct é criada para guardar as informações que serão compartilhadas entre os dispositivos. A variável bool recvJaExecutado é usada para checar se a função OnDataRecv já rodou alguma vez.

A função OnDataSent é executada quando os dados são transmitidos, aumentando o valor de dataSend. Se o valor passar de 10, ele volta para zero.

A função OnDataRecv é executada quando dados são obtidos via ESP-NOW. Ela copia os dados obtidos para a estrutura de dados e coloca a variável recvJaExecutado como verdadeira. Além disso, atualiza a variável de tempo da última obtenção.

Na função setup, a conexão I2C é iniciada e o display LCD é configurado e limpo. Também mostra algumas mensagens na tela.

Na função loop, é enviado a variável dataSend para o outro ESP32 usando o endereço MAC dele e a função esp_now_send. A variável dataSend é uma estrutura de dados que contém um valor numérico. Também é verificado se já se passaram mais de 2 segundos desde a última vez que recebeu dados do outro ESP32. Se sim, mostra uma mensagem de “Falha recepção” no display LCD. Se não, verifica se a função OnDataRecv já foi executada alguma vez (indicando que recebeu dados). Então, se sim, mostra o valor recebido na variável dataRecv no display LCD. Se não, mostra uma linha em branco no display LCD.

Também no loop, é criado uma variável estática chamada valuePrevious, que guarda o último valor enviado na variável dataSend, para comparar se esse valor é o mesmo valor atual de dataSend. Se forem iguais, mostra uma mensagem de “Falha no Envio” no display LCD. Se forem diferentes, mostra o valor atual de dataSend no display LCD e atualiza o valor de valuePrevious com ele. Então, através da função delay, é feita uma espera de 1 segundo antes de repetir o loop.

Esquemático do ESP32 2

Sketch do ESP32 2

/******************************************************************************
                    Comunicação Bidirecional com dois ESP32s
                              Sketch ESP32 2
                      Criado em 08 de Março de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

#include <LiquidCrystal_I2C.h>  // Inclui a biblioteca LiquidCrystal_I2C para controle do display LCD I2C
#include <esp_now.h>            // Inclui a biblioteca esp_now para o uso do protocolo de comunicação ESP-NOW
#include <Wire.h>               // Inclui a biblioteca Wire para a comunicação I2C
#include <WiFi.h>               // Inclui a biblioteca WiFi para configuração da rede sem fio

const uint8_t pinSda = 14;  // Define o pino SDA como 14 para a comunicação I2C
const uint8_t pinScl = 27;  // Define o pino SCL como 27 para a comunicação I2C

uint8_t macAddress1Esp32[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };  // Define o endereço MAC do outro ESP32. Insira aqui o endereço MAC de sua placa

struct DataStruct {  // Define a estrutura DataStruct para troca de informações
  int value;         // Variável inteira value
};

DataStruct dataSend;  // Declara a estrutura dataSend
DataStruct dataRecv;  // Declara a estrutura dataRecv

bool recvJaExecutado = false;

esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

LiquidCrystal_I2C lcd(0x27, 16, 2);  // Declara o objeto lcd para controle do display LCD I2C

// Variável para armazenar o tempo da última recepção de dados
unsigned long temporizadorUltimaRecepcao = 0;

// Função para formatar um número inteiro como uma string
String formatarNumero(int number) {
  // Inicializa uma variável temporária como uma string vazia
  String temp = "";
  // Se o número fornecido for menor que 10, adiciona um "0" à string temporária
  if (number < 10) {
    temp = "0";
  }
  // Adiciona o número à string temporária
  temp += number;
  // Retorna a string formatada
  return temp;
}

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {  // Função chamada quando os dados são enviados
  if (status == ESP_NOW_SEND_SUCCESS) {                                   // Se o envio foi bem sucedido, ...
    dataSend.value--;                                                     // Decrementa o valor inteiro

    // Se o valor inteiro for menor que 0, retorna para 10
    if (dataSend.value < 0) {
      dataSend.value = 10;  // o valor de dataSend.value volta à ser Dez
    }
  }
}

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
  memcpy(&dataRecv, incomingData, sizeof(dataRecv));  // Copia os dados recebidos para a estrutura de dados
  recvJaExecutado = true;                             // Define a variável recvJaExecutado como verdadeira para indicar que a função OnDataRecv já foi executada alguam vez
  temporizadorUltimaRecepcao = millis();              // Atualiza a variável de tempo da última recepção
}


void setup() {
  Wire.begin(pinSda, pinScl);  // Inicia a comunicação I2C com os pinos SDA e SCL definidos anteriormente
  lcd.init();                  // Inicializa o display
  lcd.backlight();             // Acende o backlight do display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("   Comunicacao  ");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("     ESPNOW     ");  // Escreve a mensagem no display
  delay(1200);                    // Espera por 1,2 segundos
  lcd.clear();                    // Limpa o display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("  Bidirecional  ");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("ESP32 <-> ESP32 ");  // Escreve a mensagem no display
  delay(1000);                    // Espera por 1 segundo
  lcd.clear();                    // Limpa o display

  lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
  lcd.print("Este dispositivo");  // Escreve a mensagem no display
  lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
  lcd.print("eh ESP32 2");        // Escreve a mensagem no display
  delay(1000);                    // Espera por 1 segundo
  lcd.clear();                    // Limpa o display

  WiFi.disconnect();    // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_STA);  // Define o modo WiFi como Station (cliente)

  if (esp_now_init() != ESP_OK) {   // Inicializa o ESP-NOW e verifica se há erros
    lcd.setCursor(0, 0);            // Define a posição do cursor do display para a primeira linha e primeira coluna
    lcd.print("Inicializacao ");    // Escreve a mensagem no display
    lcd.setCursor(0, 1);            // Define a posição do cursor do display para a segunda linha e primeira coluna
    lcd.print("ESP-NOW com Erro");  // Escreve a mensagem no display
    delay(2500);                    // Espera por 2,5 segundos
    ESP.restart();                  // Reinicia o dispositivo
  }

  esp_now_register_send_cb(OnDataSent);  // Registra a função de callback que é chamada quando os dados são enviados
  esp_now_register_recv_cb(OnDataRecv);  // Registra a função de callback que é chamada quando os dados são recebidos

  memcpy(peerInfo.peer_addr, macAddress1Esp32, 6);  // Copia o endereço MAC do outro ESP32 para a estrutura peerInfo
  peerInfo.channel = 0;                             // Define o canal de comunicação como 0 na estrutura peerInfo
  peerInfo.encrypt = false;                         // Define a encriptação como desativada na estrutura peerInfo

  // Tenta adicionar o outro ESP32 à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o outro ESP32, exibe mensagem de falha no display e reinicia o dispositivo
    lcd.setCursor(0, 0);
    lcd.print("Falha ao");
    lcd.setCursor(0, 1);
    lcd.print("adicionar peer");
    delay(2500);
    ESP.restart();
  }
  // Define o tempo atual como o temporizador da última recepção de dados
  temporizadorUltimaRecepcao = millis();
  Serial.begin(115200);
}

void loop() {

  esp_now_send(macAddress1Esp32, (uint8_t *)&dataSend, sizeof(DataStruct));  // Envie os dados para o endereço MAC do outro ESP32

  // Verifica se o tempo desde a última recepção é maior que 2 segundos
  if (millis() - temporizadorUltimaRecepcao > 2000) {
    // Mostra mensagem de tempo limite ultrapassado no display de LCD
    Serial.println(dataRecv.value);
    lcd.setCursor(0, 1);
    lcd.print("Falha recepcao  ");
  } else {
    if (recvJaExecutado) {  // se a função já foi executada alguam vez, ...
      // Atualiza o display de LCD com informações sobre os dados recebidos
      lcd.setCursor(0, 1);
      lcd.print("Recebido:");
      lcd.print(formatarNumero(dataRecv.value));
      lcd.print("     ");
    } else {  // se não, não mostra nada (na primeira vez de execução do programa)
      lcd.setCursor(0, 1);
      lcd.print("                ");
    }
  }

  static int valuePrevious;  // variável estática que armazena o último valor de dataSend.value

  if (dataSend.value == valuePrevious) {  // se o valor atual de dataSend.value for igual à valuePrevious, mostra falha no envio
    lcd.setCursor(0, 0);                  // Define a posição do cursor do display
    lcd.print("Falha no Envio  ");        // Escreve a mensagem de falha no display
  } else {
    lcd.setCursor(0, 0);                        // Define a posição do cursor do display
    lcd.print("Enviado :");                     // Escreve a mensagem no display
    lcd.print(formatarNumero(dataSend.value));  // exibe no dispaly lcd o conteúdo da variável dataSend.value
    lcd.print("     ");
    valuePrevious = dataSend.value;  // atualiza o valor de valuePrevious com o último valor de dataSend.value
  }

  delay(1000);  // Espera 1 segundo
}

Este sketch faz o mesmo processo que o sketch do ESP32 1. A única diferença é de que em vez de incrementar a variável dataSend.value, é feito o decremento da variável dataSend.value (linha 53) e também é feita a verificação de que de se o valor inteiro for menor que 0, ele deve retornar para 10 (linhas 56 a 58).

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {  // Função chamada quando os dados são enviados
  if (status == ESP_NOW_SEND_SUCCESS) {                                   // Se o envio foi bem sucedido, ...
    dataSend.value--;                                                     // Decrementa o valor inteiro

    // Se o valor inteiro for menor que 0, retorna para 10
    if (dataSend.value < 0) {
      dataSend.value = 10;  // o valor de dataSend.value volta à ser Dez
    }
  }
}

Demonstração do Funcionamento


Comunicação Multidirecional com três ESP32s

A comunicação multidirecional com ESP32s é aquela em que vários dispositivos ESP32 podem trocar informações entre si. Isso permite que cada dispositivo envie e receba dados de todos os outros dispositivos na rede. Nesse modo de comunicação, cada dispositivo pode enviar e receber informações específicas dos outros dispositivos.

Por exemplo, em um sistema de monitoramento ambiental, vários ESP32s podem ser responsáveis por medir diferentes parâmetros como temperatura, umidade e pressão atmosférica em diferentes locais. Dessa forma, cada dispositivo pode enviar seus dados para os outros dispositivos e receber informações sobre o estado atual do ambiente. Essa comunicação multidirecional entre ESP32s é útil em situações em que diferentes dispositivos precisam trocar informações entre si para compartilhar dados e conhecimento.

Neste exemplo, será utilizado três ESP32s para demonstrar a comunicação multidirecional, mas pode ser utilizado mais, de acordo com o limite de dispositivos em uma rede esp-now.

Para realizar a comunicação multidirecional com ESP32s, é preciso configurar cada dispositivo como um par (peer) da rede esp-now e definir os endereços MAC dos outros dispositivos que farão parte da rede. Além disso, é preciso definir as funções de envio e recebimento dos dados para cada dispositivo e criar as estruturas de dados que serão transmitidas pela rede. Por fim, é preciso definir as rotinas de callback que serão executadas quando os dados forem enviados ou recebidos pelos dispositivos.

Software e Hardware

Esquemático do ESP32 1

É utilizado somente a placa ESP32 sem nenhum hardware adicional.

Sketch do ESP32 1

/******************************************************************************
                    Comunicação Multidirecional com três ESP32s
                              Sketch ESP32 1
                      Criado em 08 de Março de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

#include <esp_now.h>  // Inclui a biblioteca esp_now para o uso do protocolo de comunicação ESP-NOW
#include <WiFi.h>     // Inclui a biblioteca WiFi para configuração da rede sem fio

uint8_t macAddress2Esp32[] = { 0x10, 0x52, 0x1C, 0x68, 0x90, 0xFC };  // Define o endereço MAC do segundo ESP32. Coloque o endereço da sua placa aqui
uint8_t macAddress3Esp32[] = { 0xA4, 0xE5, 0x7C, 0x46, 0xFF, 0x1C };  // Define o endereço MAC do terceiro ESP32. Coloque o endereço da sua placa aqui

struct DataStruct {  // Define a estrutura DataStruct para troca de informações
  int value;         // Variável inteira value
};

DataStruct dataRecv2;  // Declara a estrutura dataRecv2 para armazenar dados recebidos da placa 2
DataStruct dataRecv3;  // Declara a estrutura dataRecv3 para armazenar dados recebidos da placa 3

DataStruct dataSend2;  // Declara a estrutura dataSend2 para armazenar dados enviados da placa 2
DataStruct dataSend3;  // Declara a estrutura dataSend3 para armazenar dados enviados da placa 3


esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

// Função para formatar um número inteiro como uma string
String formatarNumero(int number) {
  // Inicializa uma variável temporária como uma string vazia
  String temp = "";
  // Se o número fornecido for menor que 10, adiciona um "0" à string temporária
  if (number < 10) {
    temp = "0";
  }
  // Adiciona o número à string temporária
  temp += number;
  // Retorna a string formatada
  return temp;
}

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {  // Função chamada quando os dados são enviados
  if (memcmp(mac_addr, macAddress2Esp32, 6) == 0)                         // É comparado os primeiros 6 bytes do endereço MAC recebido com os primeiros
  // 6 bytes do endereço MAC ESP32 2 usando a função, e se os endereços MAC forem iguais,
  // o valor retornado será zero e o bloco de código dentro do if será executado.
  {
    Serial.print("̯ Enviado de 1 para 2 : ");
    if (status == ESP_NOW_SEND_SUCCESS) {  // Se o envio foi bem sucedido, ...
      Serial.println(formatarNumero(dataSend2.value)); // Converte o valor de dataSend2.value para uma string formatada e envia para a porta serial
      dataSend2.value++; // Incrementa o valor de dataSend2.value para o próximo envio
      
      // Verifica se o valor de dataSend2.value ultrapassou o limite de 10 e, caso sim, retorna para 0
      if (dataSend2.value > 10) {
        dataSend2.value = 0;
      }
    } else {
      Serial.println("??"); // Não foi enviado com sucesso
    }
  } else if (memcmp(mac_addr, macAddress3Esp32, 6) == 0)  // se o endereço MAC recebido for igual ao endereço MAC do ESP32 3, ...
  {
    Serial.print("̯ Enviado de 1 para 3 : ");
    if (status == ESP_NOW_SEND_SUCCESS) {  // Se o envio foi bem sucedido, ...
      Serial.println(formatarNumero(dataSend3.value)); // Converte o valor de dataSend3.value para uma string formatada e envia para a porta serial
      dataSend3.value--; // Decrementa o valor de dataSend3.value para o próximo envio
      
      // Verifica se o valor de dataSend3.value é menor do que 0 e, caso sim, retorna para 10
      if (dataSend3.value < 0) {
        dataSend3.value = 10;
      }
    } else {
      Serial.println("??"); // Não foi enviado com sucesso
    }
  }
}

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
  if (memcmp(mac, macAddress2Esp32, 6) == 0) {
    memcpy(&dataRecv2, incomingData, sizeof(dataRecv2));  // Copia os dados recebidos para a estrutura de dados dataRecv2
    
    // Mostra na Serial o dado recebido
    Serial.print("«╣ Recebido de 2 para 1: ");
    Serial.println(formatarNumero(dataRecv2.value));

  } else if (memcmp(mac, macAddress3Esp32, 6) == 0) {
    memcpy(&dataRecv3, incomingData, sizeof(dataRecv3));  // Copia os dados recebidos para a estrutura de dados dataRecv3
        
    // Mostra na Serial o dado recebido 
    Serial.print("«╣ Recebido de 3 para 1: ");
    Serial.println(formatarNumero(dataRecv3.value));
  }
}

void setup() {
  delay(2000);
  Serial.begin(115200); // Inicia a comunicação serial com uma taxa de 115200 bits por segundo
  Serial.println("\n\nComunicação ESP-NOW multidirecional:\n\n    ╔» ESP32 1 «═══» ESP32 2 «╗\n    ╚═══════» ESP32 3 «═══════╝\n\n   Este dispositivo é o ESP32 1\n\n"); // Imprime uma mensagem na porta serial para indicar o início da comunicação ESP-NOW multidirecional

  WiFi.disconnect();    // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_STA);  // Define o modo WiFi como Station (cliente)

  if (esp_now_init() != ESP_OK) {  // Inicializa o ESP-NOW e verifica se há erros
    Serial.println("Inicialização ESP-NOW com Erro. O dispositivo reiniciará em "); // Se houver um erro, imprime uma mensagem na porta serial
    
    // Loop de contagem regressiva para reiniciar o dispositivo
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }

    // Imprime uma mensagem de reinicialização e reinicia o dispositivo
    Serial.println("Reiniciando ESP32");
    ESP.restart();
  }

  esp_now_register_send_cb(OnDataSent);  // Registra a função de callback que é chamada quando os dados são enviados
  esp_now_register_recv_cb(OnDataRecv);  // Registra a função de callback que é chamada quando os dados são recebidos

  memcpy(peerInfo.peer_addr, macAddress2Esp32, 6);  // Copia o endereço MAC do ESP32 2 para a estrutura peerInfo
  peerInfo.channel = 0;                             // Define o canal de comunicação como 0 na estrutura peerInfo
  peerInfo.encrypt = false;                         // Define a encriptação como desativada na estrutura peerInfo

  // Tenta adicionar o ESP32  2 à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o ESP32 2, exibe mensagem de falha no display e reinicia o dispositivo
    Serial.println("Falha ao adicionar peer 2. O dispositivo reiniciará em ");
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }
    Serial.println("Reiniciando ESP32");
    ESP.restart();
  }

  memcpy(peerInfo.peer_addr, macAddress3Esp32, 6);  // Copia o endereço MAC do ESP32 3 para a estrutura peerInfo

  // Tenta adicionar o ESP32 3 à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o ESP32 3, exibe mensagem de falha no display e reinicia o dispositivo
    Serial.println("Falha ao adicionar peer 3. O dispositivo reiniciará em ");
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }
    Serial.println("Reiniciando ESP32");
    ESP.restart();
  }

  // Inicializa as variáveis com os valores 0
  dataSend3.value = 0;
  dataSend2.value = 0;
}

void loop() {

  esp_now_send(macAddress2Esp32, (uint8_t *)&dataSend2, sizeof(DataStruct));  // Envie os dados para o endereço MAC do segundo ESP32
  esp_now_send(macAddress3Esp32, (uint8_t *)&dataSend3, sizeof(DataStruct));  // Envie os dados para o endereço MAC do terceiro ESP32

  delay(1000);  // Espera 1 segundo
}

Este sketch é o ESP32 1 do projeto de comunicação multidirecional com três ESP32s. Ele utiliza a biblioteca ESP-NOW para realizar a comunicação sem fio entre os dispositivos. As bibliotecas utilizadas neste sketch são a esp_now.h e a WiFi.h. A esp_now.h é uma biblioteca específica para o protocolo de comunicação ESP-NOW, enquanto a WiFi.h é utilizada para configurar a rede sem fio e é necessária para o funcionamento da esp_now.h.

Na função onDataSent, é definido o comportamento que deve ocorrer quando um pacote de dados é enviado para os outros ESP32s. Nessa função, são comparados os endereços MAC dos dispositivos que enviaram uma resposta. Se o endereço MAC for o do ESP32 2, o valor de dataSend2.value é enviado pela porta serial e o valor é incrementado em 1. Se o endereço MAC for o do ESP32 3, o valor de dataSend3.value é enviado pela porta serial e o valor é decrementado em 1.

Na função onDataRecv, define-se o comportamento que deve ocorrer quando um pacote de dados é recebido. Nessa função, são definidos os valores das estruturas de dados dataRecv2 e dataRecv3 que armazenam as informações recebidas do ESP32 2 e do ESP32 3, respectivamente. Quando um pacote é recebido, o valor é armazenado em dataRecv2 ou dataRecv3, dependendo do endereço MAC do dispositivo de origem.

Na função setup, é definido a taxa de transmissão de dados serial, inicializado o monitor serial, iniciada a conexão WiFi e registrado os dispositivos ESP-NOW. Além disso, nesta função, também são configurados os endereços MAC dos outros dois dispositivos da rede ESP-NOW (ESP32 2 e ESP32 3) e definidas as estruturas de dados para troca de informações entre as placas.

Na função loop, enviam-se os valores de dataSend2 e dataSend3 para os ESP32s 2 e 3, respectivamente, a cada 1 segundo, usando a função esp_now_send passando como argumento o endereço MAC do requerido ESP32 a enviar a mensagem ESP-NOW.

Esquemático do ESP32 2

É utilizado somente a placa ESP32 sem nenhum hardware adicional.

Sketch do ESP32 2

/******************************************************************************
                    Comunicação Multidirecional com três ESP32s
                              Sketch ESP32 2
                      Criado em 08 de Março de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

#include <esp_now.h>  // Inclui a biblioteca esp_now para o uso do protocolo de comunicação ESP-NOW
#include <WiFi.h>     // Inclui a biblioteca WiFi para configuração da rede sem fio

uint8_t macAddress1Esp32[] = { 0x24, 0x62, 0xAB, 0xFE, 0x7B, 0xEC };  // Define o endereço MAC do ESP32 1. Coloque o endereço da sua placa aqui
uint8_t macAddress3Esp32[] = { 0xA4, 0xE5, 0x7C, 0x46, 0xFF, 0x1C };  // Define o endereço MAC do ESP32 3. Coloque o endereço da sua placa aqui

struct DataStruct {  // Define a estrutura DataStruct para troca de informações
  int value;         // Variável inteira value
};

DataStruct dataRecv1;  // Declara a estrutura dataRecv1 para armazenar dados recebidos da placa 1
DataStruct dataRecv3;  // Declara a estrutura dataRecv3 para armazenar dados recebidos da placa 3

DataStruct dataSend1;  // Declara a estrutura dataSend1 para armazenar dados enviados da placa 1
DataStruct dataSend3;  // Declara a estrutura dataSend3 para armazenar dados enviados da placa 3


esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

// Função para formatar um número inteiro como uma string
String formatarNumero(int number) {
  // Inicializa uma variável temporária como uma string vazia
  String temp = "";
  // Se o número fornecido for menor que 10, adiciona um "0" à string temporária
  if (number < 10) {
    temp = "0";
  }
  // Adiciona o número à string temporária
  temp += number;
  // Retorna a string formatada
  return temp;
}

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {  // Função chamada quando os dados são enviados
  if (memcmp(mac_addr, macAddress1Esp32, 6) == 0)                         // Se o MAC for do igual do ESP32 1, ...
  {
    Serial.print("̯ Enviado de 2 para 1 : ");
    if (status == ESP_NOW_SEND_SUCCESS) {               // Se o envio foi bem sucedido, ...
      Serial.println(formatarNumero(dataSend1.value));  // Converte o valor de dataSend1.value para uma string formatada e envia para a porta serial
      dataSend1.value++;                                // Incrementa o valor de dataSend1.value para o próximo envio

      // Verifica se o valor de dataSend1.value ultrapassou o limite de 10 e, caso sim, retorna para 0
      if (dataSend1.value > 10) {
        dataSend1.value = 0;
      }
    } else {
      Serial.println("??");  // Não foi enviado com sucesso
    }
  } else if (memcmp(mac_addr, macAddress3Esp32, 6) == 0)  // se o endereço MAC recebido for igual ao endereço MAC do ESP32 3, ...
  {
    Serial.print("̯ Enviado de 2 para 3 : ");
    if (status == ESP_NOW_SEND_SUCCESS) {               // Se o envio foi bem sucedido, ...
      Serial.println(formatarNumero(dataSend3.value));  // Converte o valor de dataSend3.value para uma string formatada e envia para a porta serial
      dataSend3.value--;                                // Decrementa o valor de dataSend3.value para o próximo envio

      // Verifica se o valor de dataSend3.value é menor do que 0 e, caso sim, retorna para 10
      if (dataSend3.value < 0) {
        dataSend3.value = 10;
      }
    } else {
      Serial.println("??");  // Não foi enviado com sucesso
    }
  }
}

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
  if (memcmp(mac, macAddress1Esp32, 6) == 0) {
    memcpy(&dataRecv1, incomingData, sizeof(dataRecv1));  // Copia os dados recebidos para a estrutura de dados dataRecv1

    // Mostra na Serial o dado recebido
    Serial.print("«╣ Recebido de 1 para 2: ");
    Serial.println(formatarNumero(dataRecv1.value));

  } else if (memcmp(mac, macAddress3Esp32, 6) == 0) {
    memcpy(&dataRecv3, incomingData, sizeof(dataRecv3));  // Copia os dados recebidos para a estrutura de dados dataRecv3

    // Mostra na Serial o dado recebido
    Serial.print("«╣ Recebido de 3 para 2: ");
    Serial.println(formatarNumero(dataRecv3.value));
  }
}

void setup() {
  delay(2000);
  Serial.begin(115200);                                                                                                                                                   // Inicia a comunicação serial com uma taxa de 115200 bits por segundo
  Serial.println("\n\nComunicação ESP-NOW multidirecional:\n\n    ╔» ESP32 1 «═══» ESP32 2 «╗\n    ╚═══════» ESP32 3 «═══════╝\n\n   Este dispositivo é o ESP32 2\n\n");  // Imprime uma mensagem na porta serial para indicar o início da comunicação ESP-NOW multidirecional

  WiFi.disconnect();    // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_STA);  // Define o modo WiFi como Station (cliente)

  if (esp_now_init() != ESP_OK) {                                                    // Inicializa o ESP-NOW e verifica se há erros
    Serial.println("Inicialização ESP-NOW com Erro. O dispositivo reiniciará em ");  // Se houver um erro, imprime uma mensagem de reinicialização na porta serial e após alguns instantes, reinicia o dispositivo
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }
    Serial.println("Reiniciando ESP32");
    ESP.restart();  // Reinicia o dispositivo
  }

  esp_now_register_send_cb(OnDataSent);  // Registra a função de callback que é chamada quando os dados são enviados
  esp_now_register_recv_cb(OnDataRecv);  // Registra a função de callback que é chamada quando os dados são recebidos

  memcpy(peerInfo.peer_addr, macAddress1Esp32, 6);  // Copia o endereço MAC do ESP32 1 para a estrutura peerInfo
  peerInfo.channel = 0;                             // Define o canal de comunicação como 0 na estrutura peerInfo
  peerInfo.encrypt = false;                         // Define a encriptação como desativada na estrutura peerInfo

  // Tenta adicionar o ESP32 1 à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o ESP32 1, exibe mensagem de falha na Serial e reinicia o dispositivo
    Serial.println("Falha ao adicionar peer 1. O dispositivo reiniciará em ");
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }
    Serial.println("Reiniciando ESP32");
    ESP.restart();
  }

  memcpy(peerInfo.peer_addr, macAddress3Esp32, 6);  // Copia o endereço MAC do ESP32 3 para a estrutura peerInfo

  // Tenta adicionar o ESP32 3 à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o ESP32 3, exibe mensagem de falha no display e reinicia o dispositivo
    Serial.println("Falha ao adicionar peer 3. O dispositivo reiniciará em ");
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }
    Serial.println("Reiniciando ESP32");
    ESP.restart();
  }

  // Inicializa as variáveis com os valores 0
  dataSend3.value = 0;
  dataSend1.value = 0;
}

void loop() {

  esp_now_send(macAddress1Esp32, (uint8_t *)&dataSend1, sizeof(DataStruct));  // Envie os dados para o endereço MAC do primeiro ESP32
  esp_now_send(macAddress3Esp32, (uint8_t *)&dataSend3, sizeof(DataStruct));  // Envie os dados para o endereço MAC do terceiro ESP32

  delay(1000);  // Espera 1 segundo
}

Este sketch faz o mesmo que o ESP32 1, com a diferença que o envio e recepção de dados ocorrem entre os ESP32s 1 e 3.

Esquemático do ESP32 3

É utilizado somente a placa ESP32 sem nenhum hardware adicional.

Sketch do ESP32 3

/******************************************************************************
                    Comunicação Multidirecional com três ESP32s
                              Sketch ESP32 3
                      Criado em 08 de Março de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

#include <esp_now.h>  // Inclui a biblioteca esp_now para o uso do protocolo de comunicação ESP-NOW
#include <WiFi.h>     // Inclui a biblioteca WiFi para configuração da rede sem fio

uint8_t macAddress1Esp32[] = { 0x24, 0x62, 0xAB, 0xFE, 0x7B, 0xEC };  // Define o endereço MAC do ESP32 1. Coloque o endereço da sua placa aqui
uint8_t macAddress2Esp32[] = { 0x10, 0x52, 0x1C, 0x68, 0x90, 0xFC };  // Define o endereço MAC do ESP32 2. Coloque o endereço da sua placa aqui

struct DataStruct {  // Define a estrutura DataStruct para troca de informações
  int value;         // Variável inteira value
};

DataStruct dataRecv1;  // Declara a estrutura dataRecv1 para armazenar dados recebidos da placa 1
DataStruct dataRecv2;  // Declara a estrutura dataRecv2 para armazenar dados recebidos da placa 2

DataStruct dataSend1;  // Declara a estrutura dataSend1 para armazenar dados enviados da placa 1
DataStruct dataSend2;  // Declara a estrutura dataSend2 para armazenar dados enviados da placa 2

esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

// Função para formatar um número inteiro como uma string
String formatarNumero(int number) {
  // Inicializa uma variável temporária como uma string vazia
  String temp = "";
  // Se o número fornecido for menor que 10, adiciona um "0" à string temporária
  if (number < 10) {
    temp = "0";
  }
  // Adiciona o número à string temporária
  temp += number;
  // Retorna a string formatada
  return temp;
}

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {  // Função chamada quando os dados são enviados
  if (memcmp(mac_addr, macAddress1Esp32, 6) == 0)                         /// Se o MAC for do igual do ESP32 1, ...
  {
    Serial.print("̯ Enviado de 3 para 1 : ");
    if (status == ESP_NOW_SEND_SUCCESS) {               // Se o envio foi bem sucedido, ...
      Serial.println(formatarNumero(dataSend1.value));  // Converte o valor de dataSend1.value para uma string formatada e envia para a porta serial
      dataSend1.value++;                                // Incrementa o valor de dataSend1.value para o próximo envio

      // Verifica se o valor de dataSend1.value ultrapassou o limite de 10 e, caso sim, retorna para 0
      if (dataSend1.value > 10) {
        dataSend1.value = 0;
      }
    } else {
      Serial.println("??");  // Não foi enviado com sucesso
    }
  } else if (memcmp(mac_addr, macAddress2Esp32, 6) == 0)  // se o endereço MAC recebido for igual ao endereço MAC do ESP32 2, ...
  {
    Serial.print("̯ Enviado de 3 para 2 : ");
    if (status == ESP_NOW_SEND_SUCCESS) {               // Se o envio foi bem sucedido, ...
      Serial.println(formatarNumero(dataSend2.value));  // Converte o valor de dataSend2.value para uma string formatada e envia para a porta serial
      dataSend2.value--;                                // Decrementa o valor de dataSend2.value para o próximo envio

      // Verifica se o valor de dataSend2.value é menor do que 0 e, caso sim, retorna para 10
      if (dataSend2.value < 0) {
        dataSend2.value = 10;
      }
    } else {
      Serial.println("??");  // Não foi enviado com sucesso
    }
  }
}

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
  if (memcmp(mac, macAddress1Esp32, 6) == 0) {
    memcpy(&dataRecv1, incomingData, sizeof(dataRecv1));  // Copia os dados recebidos para a estrutura de dados dataRecv1

    // Mostra na Serial o dado recebido
    Serial.print("«╣ Recebido de 1 para 3: ");
    Serial.println(formatarNumero(dataRecv1.value));

  } else if (memcmp(mac, macAddress2Esp32, 6) == 0) {
    memcpy(&dataRecv2, incomingData, sizeof(dataRecv2));  // Copia os dados recebidos para a estrutura de dados dataRecv2

    // Mostra na Serial o dado recebido
    Serial.print("«╣ Recebido de 2 para 3: ");
    Serial.println(formatarNumero(dataRecv2.value));
  }
}

void setup() {
  delay(2000);
  Serial.begin(115200);                                                                                                                                                   // Inicia a comunicação serial com uma taxa de 115200 bits por segundo
  Serial.println("\n\nComunicação ESP-NOW multidirecional:\n\n    ╔» ESP32 1 «═══» ESP32 2 «╗\n    ╚═══════» ESP32 3 «═══════╝\n\n   Este dispositivo é o ESP32 3\n\n");  // Imprime uma mensagem na porta serial para indicar o início da comunicação ESP-NOW multidirecional

  WiFi.disconnect();    // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_STA);  // Define o modo WiFi como Station (cliente)

  if (esp_now_init() != ESP_OK) {                                                    // Inicializa o ESP-NOW e verifica se há erros
    Serial.println("Inicialização ESP-NOW com Erro. O dispositivo reiniciará em ");  // Se houver um erro, imprime uma mensagem de reinicialização na porta serial e após alguns instantes, reinicia o dispositivo
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }
    Serial.println("Reiniciando ESP32");
    ESP.restart();  // Reinicia o dispositivo
  }

  esp_now_register_send_cb(OnDataSent);  // Registra a função de callback que é chamada quando os dados são enviados
  esp_now_register_recv_cb(OnDataRecv);  // Registra a função de callback que é chamada quando os dados são recebidos

  memcpy(peerInfo.peer_addr, macAddress1Esp32, 6);  // Copia o endereço MAC do ESP32 1 para a estrutura peerInfo
  peerInfo.channel = 0;                             // Define o canal de comunicação como 0 na estrutura peerInfo
  peerInfo.encrypt = false;                         // Define a encriptação como desativada na estrutura peerInfo

  // Tenta adicionar o ESP32 1 à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o ESP32 1, exibe mensagem de falha no display e reinicia o dispositivo
    Serial.println("Falha ao adicionar peer 1. O dispositivo reiniciará em ");
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }
    Serial.println("Reiniciando ESP32");
    ESP.restart();
  }

  memcpy(peerInfo.peer_addr, macAddress2Esp32, 6);  // Copia o endereço MAC do ESP32 2 para a estrutura peerInfo

  // Tenta adicionar o ESP32 2 à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o ESP32 2, exibe mensagem de falha no display e reinicia o dispositivo
    Serial.println("Falha ao adicionar peer 2. O dispositivo reiniciará em ");
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }
    Serial.println("Reiniciando ESP32");
    ESP.restart();
  }

  // Inicializa as variáveis com os valores 0
  dataSend2.value = 0;
  dataSend1.value = 0;
}

void loop() {

  esp_now_send(macAddress1Esp32, (uint8_t *)&dataSend1, sizeof(DataStruct));  // Envie os dados para o endereço MAC do primeiro ESP32
  esp_now_send(macAddress2Esp32, (uint8_t *)&dataSend2, sizeof(DataStruct));  // Envie os dados para o endereço MAC do segundo ESP32

  delay(1000);  // Espera 1 segundo
}

Este sketch faz o mesmo que o ESP32 1, com a diferença que o envio e recepção de dados ocorrem entre os ESP32s 1 e 2.

Demonstração do Funcionamento


Uso Integrado das Comunicações ESP-NOW e WiFi

Para utilizar a comunicação ESP-NOW juntamente com a comunicação WiFi, é necessário seguir algumas recomendações. Utilizar estes dois modos de comunicações juntos permite soluções interessantes para dispositivos que requerem baixo consumo de energia, alta confiabilidade e grande alcance de comunicação, além de acesso à Internet.

As recomendações incluem:

  • Cada placa ES32 que NÃO se conecta ao WiFi do roteador deve utilizar o mesmo canal WiFi de cada placa ESP32 que se conecta à rede WiFi do roteador;
  • O modo WiFi de cada placa que se conecte ao WiFi deve ser ponto de acesso e estação: WIFI_AP_STA (WiFi.mode(WIFI_AP_STA);).

Visão Geral

Neste exemplo, haverá um ESP32 mestre que envia comandos ao ESP32 escravo (slave). O ESP32 mestre se conectará à rede WiFi do roteador para verificar se a rede WiFi do roteador tem Internet.

Hardware e Software

Esquemático do ESP32 Mestre

É utilizado somente a placa ESP32 sem nenhum hardware adicional.

Sketch do ESP32 Mestre

/******************************************************************************
                  Uso integrado das comunicações ESP-NOW e WiFi
                              Sketch Mestre
                   Criado em 12 de Março de 2023
                por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Inclusão de bibliotecas
//#include <esp_wifi.h>  // Biblioteca para configuração do módulo Wi-Fi do ESP32
#include <esp_now.h>  // Biblioteca para utilizar o protocolo de comunicação ESP-NOW
#include <WiFi.h>     // Biblioteca para conectar em redes Wi-Fi

uint8_t slaveMacAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };  // Define o endereço MAC do dispositivo escravo. Coloque o endereço MAC de sua placa aqui

// Estrutura de dados para troca de informações
struct DataStruct {
  int value;  // Dado do tipo inteiro
};

esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW

// Declara a estrutura dataToSend
DataStruct dataToSend;

const char* ssid = "SSID";      // variável para armazenar o nome da rede Wi-Fi. Coloque o ssid da sua rede aqui
const char* pass = "PASSWORD";  // variável para armazenar a senha da rede Wi-Fi. Coloque a senha da sua rede aqui

void OnDataSent(const uint8_t* mac_addr, esp_now_send_status_t status) {  // Função chamada quando os dados são enviados
  if (memcmp(mac_addr, slaveMacAddress, 6) == 0) {                        // Verifica se o endereço MAC do dispositivo recebedor é igual ao endereço do escravo
    if (status == ESP_NOW_SEND_SUCCESS) {                                 // Se o envio foi bem sucedido, escreve a mensagem de sucesso na Serial Monitor
      Serial.print("ESP-NOW: Envio OK. Valor enviado:");
      Serial.println((dataToSend.value));  // Escreve o valor no display
    } else {                               // Caso contrário, escreve a mensagem de falha na Serial Monitor
      Serial.println("ESP-NOW: Falha ao enviar dados para o dispositivo escravo!");
    }
  }
}

bool haConexaoInternet() {  // Função que verifica se há conexão com a internet
  bool conectado = false;   // Variável que armazenará se há conexão com a internet ou não
  WiFiClient client;        // Objeto da classe WiFiClient, que permite se conectar a um servidor na internet

  if (WiFi.status() == WL_CONNECTED) {    // Verifica se o dispositivo está conectado à rede Wi-Fi
    if (client.connect("1.1.1.1", 80)) {  // Tenta se conectar a um servidor externo (Cloudflare DNS, que está em 1.1.1.1 na porta 80)
      conectado = true;                   // Se a conexão for bem sucedida, define a variável conectado como verdadeira
    }
    client.stop();  // Encerra a conexão com o servidor
  }

  return conectado;  // Retorna se há conexão com a internet ou não
}

void setup() {
  delay(2000);           // Atraso de 2 segundos antes de executar o código do Setup
  Serial.begin(115200);  // Inicializa a comunicação serial com uma taxa de transmissão de 115200 bits por segundo

  Serial.println("\n\nUso integrado das comunicações ESP-NOW e WiFi:\n\n    ESP32 Mestre ═══» ESP32 Escravo \n\n   Este dispositivo é o ESP32 Mestre\n\n");  // Imprime uma mensagem na porta serial para indicar o início da comunicação de Uso integrado ESP-NOW e WiFi

  WiFi.disconnect();       // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_AP_STA);  // Define o modo WiFi como Access Point (AP) e Station (cliente)
  WiFi.begin(ssid, pass);  // Conecta ao roteador WiFi com o nome de rede (SSID) e a senha (password) especificados

  while (WiFi.status() != WL_CONNECTED) {  // Aguarda a conexão com o AP WiFi e exibe um ponto no monitor serial a cada segundo até que a conexão seja bem-sucedida
    delay(1000);
    Serial.print(".");
  }
  Serial.println();

  // Inicializa o ESP-NOW e verifica se há erros
  if (esp_now_init() != ESP_OK) {                                                    // Inicializa o ESP-NOW e verifica se há erros
    Serial.println("Inicialização ESP-NOW com Erro. O dispositivo reiniciará em ");  // Se houver um erro, imprime uma mensagem na porta serial

    // Loop de contagem regressiva para reiniciar o dispositivo
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }

    // Imprime uma mensagem de reinicialização e reinicia o dispositivo
    Serial.println("Reiniciando ESP32");
    ESP.restart();
  }

  esp_now_register_send_cb(OnDataSent);            // Registra a função de callback que é chamada quando os dados são enviados
  peerInfo.encrypt = false;                        // Define a criptografia como desativada para a conexão com o escravo
  memcpy(peerInfo.peer_addr, slaveMacAddress, 6);  // Copia o endereço MAC do escravo para a estrutura peerInfo
  // Tenta adicionar o escravo à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o escravo, exibe mensagem de falha no display e reinicia o dispositivo
    Serial.println("Falha ao adicionar peer");
    delay(2500);
    ESP.restart();
  }

  dataToSend.value = 0;  // Prepara os dados a serem enviados com o valor igual a zero
}

void loop() {                                                                // Função loop() que executa repetidamente enquanto a placa estiver ligada
  delay(1000);                                                               // Aguarda por 1 segundo antes de executar a próxima iteração do loop
  esp_now_send(slaveMacAddress, (uint8_t*)&dataToSend, sizeof(DataStruct));  // Envia os dados armazenados em dataToSend para o endereço MAC do escravo por meio do protocolo ESP-NOW

  dataToSend.value++;  // Incrementa o valor de dataToSend.value

  // Se o valor inteiro ultrapassar 10, retorna para 0
  if (dataToSend.value > 10) {
    dataToSend.value = 0;  // o valor de dataToSend.value volta à ser Zero
  }

  // Verifica se há conexão com a internet por meio da função haConexaoInternet()
  bool result = haConexaoInternet();
  Serial.print("Há Internet? ");
  Serial.println(result ? "SIM" : "NÃO");
}

Este é um sketch para uso integrado das comunicações ESP-NOW e WiFi em um dispositivo ESP32. Ele permite que um dispositivo ESP32 opere como mestre e se comunique com um dispositivo escravo via protocolo ESP-NOW. Além disso, a conexão WiFi também é configurada para permitir a verificação de conexão à internet.

As bibliotecas necessárias são incluídas no início do sketch, incluindo a biblioteca esp_now.h para usar o protocolo de comunicação ESP-NOW e a biblioteca WiFi.h para conectar em redes WiFi. O endereço MAC do dispositivo escravo é definido em “slaveMacAddress”.

A estrutura de dados “DataStruct” é declarada para troca de informações entre o mestre e o escravo. Já “esp_now_peer_info_t” é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW. A variável “dataToSend” é declarada como uma instância da estrutura DataStruct.

A função “OnDataSent” é chamada quando os dados são enviados. Ele verifica se o endereço MAC do dispositivo receptor é igual ao endereço do escravo e se o envio foi bem-sucedido.

A função “haConexaoInternet” verifica se há conexão com a internet através de uma tentativa de conexão ao servidor 1.1.1.1 na porta 80 da Cloudflare DNS.

No código “setup”, o dispositivo é inicializado e a conexão WiFi é estabelecida. O ESP-NOW é inicializado e verificado se há erros. Se houver erros, uma mensagem é impressa na porta serial e o dispositivo é reiniciado. Se tudo ocorrer bem, uma mensagem é impressa indicando que o dispositivo é o ESP32 mestre.

No código “loop”, é feita a espera por um segundo usando a função delay para evitar sobrecarregar a rede com muitas mensagens. Em seguida, a função esp_now_send() é usada para enviar os dados armazenados na variável dataToSend para o endereço MAC do dispositivo escravo por meio do protocolo ESP-NOW.

Depois, o valor da variável dataToSend é incrementado em 1 usando o operador ++. Se o valor inteiro da variável dataToSend ultrapassar 10, ele é redefinido para 0.

Por fim, a função haConexaoInternet() é usada para verificar se há conexão com a Internet e o resultado é exibido no monitor serial.

Esquemático do ESP32 Escravo

É utilizado somente a placa ESP32 sem nenhum hardware adicional.

Sketch do ESP32 Escravo

/******************************************************************************
                  Uso integrado das comunicações ESP-NOW e WiFi
                              Sketch Escravo
                   Criado em 12 de Março de 2023
                por Michel Galvão (https://micsg.com.br)

              Eletrogate | Arduino, Robótica, IoT, Apostilas e Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Inclusão de bibliotecas
#include <esp_now.h>   // Biblioteca para utilizar o protocolo de comunicação ESP-NOW
#include <WiFi.h>      // Biblioteca para conectar em redes Wi-Fi
#include <esp_wifi.h>  // Biblioteca para configuração do módulo Wi-Fi do ESP32

// Estrutura de dados para troca de informações
struct DataStruct {
  int value;  // Dado do tipo inteiro
};

// Variável para armazenar os dados recebidos
DataStruct dataStruct;

void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {  // Função que é chamada quando dados são recebidos via ESP-NOW
  memcpy(&dataStruct, incomingData, sizeof(dataStruct));                     // Copia os dados recebidos para a estrutura de dados

  // Mostra na Serial o dado recebido
  Serial.print("Dado recebido: ");
  Serial.println(dataStruct.value);
}

void setup() {
  delay(2000); // Atraso de 2 segundos antes de executar o código do Setup
  Serial.begin(115200); // Inicia a comunicação serial com uma taxa de 115200 bits por segundo
  Serial.println("\n\nUso integrado das comunicações ESP-NOW e WiFi:\n\n    ESP32 Mestre ═══» ESP32 Escravo \n\n   Este dispositivo é o ESP32 Escravo\n\n");  // Imprime uma mensagem na porta serial para indicar o início da comunicação de Uso integrado ESP-NOW e WiFi

  WiFi.disconnect();    // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_STA);  // Define o modo WiFi como Station (cliente)
  
  int32_t channel = 3; // Define o canal WiFi a ser utilizado

  // Configura a placa ESP32 para escutar o canal especificado
  esp_wifi_set_promiscuous(true);
  esp_wifi_set_channel(3, WIFI_SECOND_CHAN_NONE);
  esp_wifi_set_promiscuous(false);

  WiFi.channel(channel); // Configura o canal WiFi

  if (esp_now_init() != ESP_OK) {                                                    // Inicializa o ESP-NOW e verifica se há erros
    Serial.println("Inicialização ESP-NOW com Erro. O dispositivo reiniciará em ");  // Se houver um erro, imprime uma mensagem na porta serial

    // Loop de contagem regressiva para reiniciar o dispositivo
    for (int i = 3; i > 0; i--) {
      Serial.print(i);
      delay(1000);
    }

    // Imprime uma mensagem de reinicialização e reinicia o dispositivo
    Serial.println("Reiniciando ESP32");
    ESP.restart();
  }

  esp_now_register_recv_cb(OnDataRecv);  // Registra a função de callback que é chamada quando os dados são recebidos
}

void loop() {}

Este é um sketch para o dispositivo ESP32 Escravo que utiliza comunicação ESP-NOW. Ele começa com a inclusão de três bibliotecas: esp_now.h, WiFi.h e esp_wifi.h. A primeira biblioteca é responsável por permitir o uso do protocolo ESP-NOW, enquanto a segunda e terceira bibliotecas são usadas para conectar a placa ESP32 a redes WiFi e configurar o módulo WiFi.

Em seguida, é definida uma estrutura de dados chamada “DataStruct” que contém apenas um campo do tipo inteiro chamado “value”. Essa estrutura será usada para receber informações do dispositivo mestre.

Depois disso, é definida uma função chamada “OnDataRecv” que é chamada quando dados são recebidos via ESP-NOW. Essa função copia os dados recebidos para a variável “dataStruct” e os imprime na porta serial.

Na função “setup”, é definido um atraso de 2 segundos antes de iniciar a comunicação serial e imprimir uma mensagem para indicar o início da comunicação ESP-NOW. Em seguida, a placa ESP32 é desconectada de qualquer rede WiFi previamente conectada e o modo WiFi é definido como Station (cliente). Um canal WiFi é definido e a placa ESP32 é configurada para escutar o canal especificado. A função “esp_now_init” é chamada para inicializar o protocolo ESP-NOW e, se houver erros, o dispositivo é reiniciado. Por fim, a função “esp_now_register_recv_cb” é chamada para registrar a função “OnDataRecv” como a função de callback que será chamada quando os dados são recebidos.

A função “loop” é deixada em branco, pois não há nenhuma operação que precise ser repetida continuamente.

Demonstração do Funcionamento


Teste de Alcance ESP-NOW entre duas Placas ESP32s

Testar o alcance da comunicação wireless é essencial para garantir a confiabilidade e a eficiência da transmissão de dados em diferentes ambientes e condições.

Para realizar o teste de alcance ESP-NOW, será necessário configurar duas placas ESP32s para se comunicarem entre si: uma como mestre e outra como escravo.

A placa mestre será responsável por enviar dados para a placa escravo. Caso a placa escravo receba os dados, o LED ONBOARD é acesso. Caso contrário, ele é apagado.

Para medir a distância, foi obtido as coordenadas geográficas da base do ESP32 Mestre (fixo) e também as coordenadas geográficas de até onde o ESP32 escravo (móvel) conseguiu obter os dados do Mestre. Para cálculo da distância foi inserido no Google Earth Pro as coordenadas geográficas e feita a medição em metros entre os pontos. Abaixo está os resultados:

Hardware e Software

Esquemático do ESP32 Mestre

É utilizado somente a placa ESP32 sem nenhum hardware adicional.

Sketch do ESP32 Mestre

/******************************************************************************
         Comunicação Unidirecional com mestre enviando para um escravo
                              Sketch Mestre
                   Criado em 14 Março de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

#include <esp_now.h>  // Inclui a biblioteca esp_now para o uso do protocolo de comunicação ESP-NOW
#include <WiFi.h>     // Inclui a biblioteca WiFi para configuração da rede sem fio

uint8_t slaveMacAddress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };  // Define o endereço MAC do dispositivo escravo

struct DataStruct {  // Define a estrutura DataStruct para troca de informações
  int integerData;   // Variável inteira integerData
};

DataStruct dataToSend;

esp_now_peer_info_t peerInfo;  // Cria uma estrutura esp_now_peer_info_t, que é utilizada para registrar informações sobre um peer (dispositivo) que será adicionado à rede ESPNOW


void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {  // Função chamada quando os dados são enviados
  if (status == ESP_NOW_SEND_SUCCESS) {                                   // Se o envio foi bem sucedido, ...
    Serial.println("Envio OK");                                           // Mostra sucesso
  } else {
    Serial.println("Envio Falho");  // Mostra falha
  }
  dataToSend.integerData++;  // Incrementa o valor inteiro

  // Se o valor inteiro ultrapassar 10, retorna para 0
  if (dataToSend.integerData > 10) {
    dataToSend.integerData = 0;  // o valor de dataToSend.integerData volta à ser Zero
  }
}


void setup() {
  Serial.begin(115200);
  Serial.println("Teste de Alcance ESP-NOW");

  WiFi.disconnect();    // Desconecta de qualquer rede WiFi previamente conectada
  WiFi.mode(WIFI_STA);  // Define o modo WiFi como Station (cliente)

  if (esp_now_init() != ESP_OK) {  // Inicializa o ESP-NOW e verifica se há erros
    Serial.println("Inicialização ESP-NOW com Erro. Reiniciando...");
    delay(2500);    // Espera por 2,5 segundos
    ESP.restart();  // Reinicia o dispositivo
  }

  esp_now_register_send_cb(OnDataSent);  // Registra a função de callback que é chamada quando os dados são enviados

  memcpy(peerInfo.peer_addr, slaveMacAddress, 6);  // Copia o endereço MAC do escravo para a estrutura peerInfo
  peerInfo.channel = 0;                            // Define o canal de comunicação como 0 na estrutura peerInfo
  peerInfo.encrypt = false;                        // Define a encriptação como desativada na estrutura peerInfo

  // Tenta adicionar o escravo à lista de pares de comunicação ESP-NOW e verifica se foi bem sucedido
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // Caso não seja possível adicionar o escravo, exibe mensagem de falha na Serial e reinicia o dispositivo
    Serial.println("Falha ao adicionar peer. Reiniciando...");
    delay(2500);
    ESP.restart();
  }
}

void loop() {
  esp_now_send(slaveMacAddress, (uint8_t *)&dataToSend, sizeof(DataStruct));  // Envie os dados para o endereço MAC do dispositivo escravo
  delay(20); // Pausa no programa por 20 milissegundos
}

Esse é um sketch de um mestre ESP32 que utiliza o protocolo de comunicação sem fio ESP-NOW para enviar dados para um escravo unidirecionalmente.

O sketch é iniciado com a inclusão de duas bibliotecas: a biblioteca ESP-NOW, para comunicação sem fio, e a biblioteca WiFi, para conexão a redes Wi-Fi. Em seguida é feita a definição do endereço MAC do dispositivo escravo, a definição da estrutura DataStruct que será utilizada para trocar informações, a criação da estrutura esp_now_peer_info_t que registra informações sobre um peer (dispositivo) que será adicionado à rede ESP-NOW, é criada a função OnDataSent que é chamada quando os dados são enviados e as funções setup() e loop().

Na função setup(), o código define a porta serial, desconecta qualquer rede WiFi previamente conectada, define o modo WiFi como Station (cliente), inicializa o ESP-NOW e registra a função de callback que é chamada quando os dados são enviados. Em seguida, o endereço MAC do escravo é copiado para a estrutura peerInfo, o canal de comunicação é definido como 0 e a encriptação é desativada na estrutura peerInfo. Por fim, o código tenta adicionar o escravo à lista de pares de comunicação ESP-NOW e verifica se foi bem-sucedido. Se não for possível adicionar o escravo, o código exibe uma mensagem de falha na Serial e reinicia o dispositivo.

Na função loop(), o código envia os dados para o endereço MAC do dispositivo escravo utilizando a função esp_now_send() e pausa o programa por 20 milissegundos. A função OnDataSent() é chamada após a transmissão dos dados e exibe uma mensagem no monitor serial informando que os dados foram enviados com sucesso ou com falha, além de administrar o valor dos dados a enviar. O valor dos dados é incrementado a cada envio e, caso ultrapasse 10, retorna para 0.

Esquemático do ESP32 Escravo

É utilizado somente a placa ESP32 sem nenhum hardware adicional.

Sketch do ESP32 Escravo

/******************************************************************************
         Comunicação Unidirecional com mestre enviando para um escravo
                              Sketch Escravo
                   Criado em 14 de Março de 2023
                por Michel Galvão (https://micsg.com.br)

  Eletrogate - Loja de Arduino \\ Robótica \\ Automação \\ Apostilas \\ Kits
                            https://www.eletrogate.com/
******************************************************************************/

// Inclusão de bibliotecas
#include <esp_now.h>  // Biblioteca para utilizar o protocolo de comunicação ESP-NOW
#include <WiFi.h>     // Biblioteca para conectar em redes Wi-Fi

// Estrutura de dados para troca de informações
struct DataStruct {
  int integerData;  // Dado do tipo inteiro
};

// Variável para armazenar os dados recebidos
DataStruct dataStruct;

// Variável para armazenar o tempo da última recepção
unsigned long ultimaRecepcao = 0;

// Função que é chamada quando dados são recebidos via ESP-NOW
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
  memcpy(&dataStruct, incomingData, sizeof(dataStruct));  // Copia os dados recebidos para a estrutura de dados
  // Mostra na Serial as informações sobre os dados recebidos
  Serial.print("Obtido ");
  Serial.print(len);
  Serial.print(" Bytes: ");
  digitalWrite(LED_BUILTIN, HIGH);
  ultimaRecepcao = millis();

  Serial.println(dataStruct.integerData);
}

void setup() {
  Serial.begin(115200);

  // Desconecta de alguma conexão WiFi anterior e define o modo estação (STA)
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);

  pinMode(LED_BUILTIN, OUTPUT);  // Configura o pino do LED BUILTIN como saída

  // Inicia a biblioteca ESP-NOW e, caso ocorra algum erro, reinicia o dispositivo
  if (esp_now_init() != ESP_OK) {
    Serial.println("Inicializacao ESP-NOW com Erro. Reiniciando...");
    delay(2500);
    ESP.restart();
  }

  // Registra a função OnDataRecv como a função a ser chamada quando receber dados via ESP-NOW
  esp_now_register_recv_cb(OnDataRecv);
  ultimaRecepcao = millis();
}

void loop() {
  if (millis() - ultimaRecepcao > 1000) {  // se o tempo até a última recepção passar de 1 segundo, mostra erro apagando o LED
    digitalWrite(LED_BUILTIN, LOW);
  }
}

Este é um sketch para um dispositivo escravo ESP32 que utiliza o protocolo de comunicação sem fio ESP-NOW para receber dados unidirecionalmente de um dispositivo mestre.

O sketch é iniciado com a inclusão de duas bibliotecas: a biblioteca ESP-NOW, para comunicação sem fio, e a biblioteca WiFi, para conexão a redes Wi-Fi. Em seguida, é definida a estrutura de dados DataStruct para trocar informações. A variável dataStruct é definida para armazenar os dados recebidos e ultimaRecepcao é definida para armazenar o tempo da última recepção.

Na função OnDataRecv(), que é chamada quando os dados são recebidos via ESP-NOW, os dados são copiados para a estrutura de dados dataStruct, e as informações sobre os dados recebidos são mostradas na Serial. O LED embutido é aceso para indicar a recepção de dados, e a variável ultimaRecepcao é atualizada com o valor atual de millis().

Na função setup(), a Serial é iniciada com uma taxa de transmissão de 115200 baud. O dispositivo se desconecta de qualquer conexão Wi-Fi anterior e é definido como estação (STA). O pino do LED embutido é definido como saída. Em seguida, a biblioteca ESP-NOW é iniciada, e caso ocorra algum erro, o dispositivo é reiniciado. A função OnDataRecv() é registrada como a função a ser chamada quando receber dados via ESP-NOW.

Na função loop(), é verificado se o tempo até a última recepção é maior que 1 segundo. Se for o caso, o LED embutido é apagado para indicar que não houve recepção de dados recentemente. A função loop() é executada continuamente enquanto o dispositivo estiver ligado.

Resultados da Medição

Observação: O resultado do teste pode variar de acordo com:

  • Antena externa: A presença de uma antena externa pode influenciar o alcance, pois uma antena externa pode ter maior ganho do que a antena interna das placas ESP32. No entanto, a qualidade da antena externa também pode afetar o alcance. Uma antena de má qualidade pode reduzir o alcance em vez de aumentá-lo;
  • Qualidade das antenas: As antenas que vêm nas placas ESP32 podem ter qualidade variável e isso pode influenciar a qualidade do sinal e o alcance.
  • Condições ambientais: O ambiente em que as placas estão sendo testadas também pode afetar o alcance. Por exemplo, o sinal de rádio pode ser afetado pela umidade, temperatura, chuva, neve, entre outras condições climáticas;
  • Altura da antena: A altura da antena em relação ao solo também pode influenciar o alcance. Antenas mais altas geralmente têm um alcance maior, mas também podem ser mais suscetíveis a interferências.
  • Duração do teste: A duração do teste pode afetar o resultado do alcance, especialmente se a placa estiver sendo alimentada por uma bateria. Testes mais longos podem esgotar a bateria mais rapidamente, o que pode afetar a potência de transmissão e, consequentemente, o alcance.
  • Objetos próximos: Objetos próximos às placas, como objetos metálicos, podem afetar o alcance, refletindo ou absorvendo o sinal. Portanto, é importante realizar testes em diferentes ambientes e distâncias para determinar o alcance real das placas.

Com Visada

Com visada (sem obstáculos na entre os ESP32s), foi obtido uma distância de 60 metros de distância.

Sem Visada

Sem visada (com obstáculos na entre os ESP32s), foi obtido uma distância de 25 metros de distância.


Conclusão

Em conclusão, o ESP-NOW é uma tecnologia de rede sem fio altamente eficiente e flexível que oferece uma solução segura e eficiente em termos de energia para a comunicação entre dispositivos ESP32s ou ESP8266s. Uma das principais vantagens que o ESP-NOW tem, é que ele permite aumentar o alcance da comunicação quando utilizado no modo de comunicação multidirecional. Ao permitir que vários dispositivos sejam usados como repetidores, ele pode ampliar a área de cobertura da rede sem fio.

Gostaríamos de saber se você curtiu este post! Por favor, avalie e deixe um comentário sobre o conteúdo. E não esqueça de nos seguir no Instagram e nos marcar quando fizer algum projeto nosso: @eletrogate.

Até a próxima!


Sobre o Autor


Michel Galvão

Hobbysta em Sistemas Embarcados e IoT. Tem experiência em Automação Residencial e Agrícola.


Eletrogate

27 de abril de 2023

A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.

Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!