IoT

Rede ESP-NOW integrada com Web Server

Eletrogate 20 de junho de 2024

Introdução

Neste post vamos apresentar um cenário específico de uma Rede ESP-NOW usando a topologia em estrela (veja a Figura 1 – Rede ESP-NOW integrada com Web Server). No centro da estrela teremos um ESP32 fazendo o papel de RECEPTOR da Rede ESP-NOW e, ao mesmo tempo, sendo um servidor Web para atender requisições HTTP na porta 80 através de um navegador qualquer (exceto o antigo Internet Explorer). Nas pontas da estrela, teremos o ESP32 ou o ESP8286 como TRANSMISSOR enviando dados para o RECEPTOR central. Cada estação, RECEPTOR ou TRANSMISSOR, terá como função coletar dados de temperatura e umidade através de um DHT11/DHT22 para que tais dados sejam visualizados numa página HTTP no navegador do usuário (desktop ou celular) numa rede local. Múltiplos usuários podem acessar a página web até o limite 10 usuários para o ESP32.

Existe um artigo no Blog da Eletrogate  (link a seguir) que explora diversos aspectos e cenários da Rede ESP-NOW. Aborda também os conceitos e pré-requisitos para a implementação. Sendo assim, partiremos do princípio de que os conceitos básicos da Rede ESP-NOW já são conhecidos pelo leitor. Caso isso não seja seu caso, recomendamos dar uma estudada no artigo mencionado. Na seção de Referência, apresentamos outros links que exploram os recursos da rede ESP-NOW. Sugerimos também que o leitor dê uma olhada para conhecer a potencialidade deste protocolo que permite muitas aplicações úteis no mundo “maker”.

https://blog.eletrogate.com/esp-now-comunicacao-sem-fio-entre-esp32s/


Objetivo

O objetivo deste artigo é mostrar um cenário teórico, mas que pode ser estendido para o mundo real, como por exemplo, na automação residencial, onde diferentes partes de uma residência poderiam ter um ESP32 (TRANSMISSOR/RECEPTOR) trocando mensagens com um nó central para gerenciamento e/ou dar comandos.

A grande vantagem da Rede ESP-NOW é não depender de Internet ativa. Os componentes podem se comunicar sem fio com o alcance bastante razoável até 220m num cenário com antenas direcionadas, sem barreiras etc. A Referência 3 descreve um experimento em campo aberto que atingiu esta marca. A Referência 1 relata um experimento que atingiu 60m sem obstáculos e 25m com obstáculos. Não enfatizou se foram usadas antenas externas, mas relaciona fatores que influenciam o alcance. Vale a pena ler o artigo.

Em nosso caso, utilizaremos uma conexão com a rede Wi-Fi para permitir a visualização dos dados numa rede local, onde apenas o nó central se conecta na rede Wi-Fi e não nos preocuparemos com medições de alcance.

 


Bibliotecas

Antes de iniciar a programação, é preciso fazer a instalação das placas da Espressif e das seguintes bibliotecas:

  • ESPAsyncWebServer (disponível no GitHub do desenvolvedor);
  • ESP8266Wifi;
  • esp-wifi;
  • esp-now;
  • espnow;
  • WiFi;
  • DHT;
  • ArduinoJson
  • RTCMemory

Com exceção da biblioteca ESPAsyncWebServer, você encontra as demais no próprio Gerenciador de Bibliotecas da Arduino IDE, sem contar que algumas, como as relacionadas ao Wi-Fi, são instaladas juntamente com o pacote de placas da Espressif.

Se tem dúvidas na instalação das placas, siga nosso tutorial de instalação.


Detalhes da Implementação

Utilizaremos no nó central/receptor um ESP32 Wroom 32U com antena (somente para exemplificar) e um sensor DHT22. Para os transmissores, um dos nós será composto por um ESP32 30 pinos e um DHT22, enquanto um segundo transmissor será composto por um ESP8266 com e sensor DHT11.

Os seguintes pré-requisitos são importantes para a coexistência da rede ESP-NOW e o Web Server no mesmo nó:

  1. As estações transmissoras devem usar o mesmo canal Wi-Fi da estação receptora.
  2. O canal Wi-Fi da estação receptora é determinado pelo roteador, não sendo possível mudar. Constatei isso na prática.
  3. As estações transmissoras devem estar no modo WIFI_AP_STA.

Encontramos na Referência 3 uma rotina que pode ser usada nas estações transmissoras para descobrir o canal Wi-Fi a partir do SSID da rede.

//---------------------------------------------
// Rotina para detectar o canal utilizado pela
// Rede Wi-Fi cujo SSID foi definido
//---------------------------------------------

int32_t getWiFiChannel(const char *ssid) 
{
  if (int32_t n = WiFi.scanNetworks()) 
  {
      for (uint8_t i=0; i<n; i++) 
      {
          if (!strcmp(ssid, WiFi.SSID(i).c_str())) 
          {
              return WiFi.channel(i);
          }
      }
  }
  return 0;
}

Assim, foi possível nivelar o canal Wi-Fi do transmissor com o receptor. Após a detecção do canal da rede Wi-Fi no transmissor, o canal foi configurado explicitamente através do código a seguir:

// Altera o canal na rede Wi-Fi local

#ifdef ESP8266
  wifi_promiscuous_enable(true);
  wifi_set_channel(channel);
  wifi_promiscuous_enable(false); 
#else
  esp_wifi_set_promiscuous(true);
  esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
  esp_wifi_set_promiscuous(false);    
#endif

Existem duas comunicações: a primeira entre os nós transmissores com o nó central e a segunda entre o nó central e a página web.

A comunicação entre transmissores e o receptor é feita usando a estrutura a seguir através da API da rede ESP-NOW:

//----------------------------------------------
// Definição da Estrutura de Dados a Transmitir
//----------------------------------------------

typedef struct struct_mensagem 
{
  char nomeEstacao[MAX_NOME+1]; // Nome desta estação ou Nó da rede ESP-NOW 
  char macEstacao[18];          // MAC desta estação  ou Nó da rede ESP-NOW 
  float temp;                   // Temperatura lida
  float umid;                   // Umidade lida
} struct_mensagem;

A seguir, apresentamos os três nós da rede ESP-NOW com seus respectivos diagramas do circuito. Como são modelos de placas diferentes, há uma pequena variação nas portas utilizadas.

Já a comunicação entre o nó central e a página web é feita através de web socket usando o JSON a seguir:

{
   "nomeEstacao":"Estação-2",
   "macEstacao":"0C:B8:15:D8:79:14",
   "temp":26.29999924,
   "umid":55.20000076
}

No Javascript do HTML, há a criação do evento que escutará o envio feito pelo nó central. Desta forma, não há necessidade de dar refresh na página a cada atualização de dados. Criaremos um painel por linha no HTML para representar os dados de cada nó, como se fosse uma lista de painéis dentro do container principal da página web (veja a Figura 8 – Página Web no celular e a Figura 9 – Página Web no desktop). Na chegada dos dados de um nó, apenas o painel correspondente será atualizado. Isso otimiza o processo e alivia o ESP32 de refresh’s constantes. Não existe limitação para o número de nós representados no HTML, pois teremos cada nó em uma linha de representação dentro do navegador. A limitação do número de nós é da rede ESP-NOW, cujo limite é de 20 nós. As figuras a seguir mostram a página web renderizada no celular e no desktop:

 


Código do Nó Receptor

//-----------------------------------------------------------------------------
// Função : Este sketch tem como objetivo implementar o nó principal de uma 
//          rede ESP-NOW como RECEPTOR dos dados de temperatura e umidade de um 
//          ou mais TRANSMISSORES e publicar os dados como servidor HTTP.
//
// Autores: Alberto de Almeida Menezes
//          Dailton Menezes
//
// Versão : V1.0 Mai/2024
//
// Observações: 1) O RECEPTOR deve ser um ESP32
//              2) Implementaremos como AsyncWebServer
//              3) Os transmissores podem ser ESP32 ou ESP8266
//              4) O nome da estação receeptora será incluída no mDNS para evitar o uso de IP absoluto
// 
// Referências: 1) https://dronebotworkshop.com/esp-now/
//              2) https://randomnerdtutorials.com/esp-now-esp32-arduino-ide/
//              3) https://randomnerdtutorials.com/esp32-esp-now-wi-fi-web-server/
//              4) https://randomnerdtutorials.com/esp-now-esp8266-nodemcu-arduino-ide/
//              5) https://www.youtube.com/watch?v=_eHe7ViXyY8&t=898s
//              
//-----------------------------------------------------------------------------

#include <esp_now.h>          // Biblioteca ESP-NOW para o ESP32
#include <WiFi.h>             // Biblioteca WiFi genérica
#include <ESPAsyncWebServer.h>// Biblioteca Asynch Web Server
#include <ArduinoJson.h>      // Biblioteca JSON para comunicação e parãmetros
#include <esp_wifi.h>         // Biblioteca WiFi especifica para ESP32
#include <ESPmDNS.h>          // Biblioteca para inclusão do hostname no mDNS
#include <DHT.h>              // Biblioteca para o DHT11/DHT22

#define LED_BUILTIN  2        // Pino para o Led Interno do ESP32/ESP8266
#define MAX_NOME     20       // Tamanho máximo do Nome da Estação
#define VARREDURA    5000     // Tempo de varredura em msec
#define NOME_ESTACAO "ESPNOWWEB" // Nome desta Estação de coleta de Temp/Umid

#define DHTPin       27       // Porta 27 ESP32-WROOM-32U
#define DHTType      DHT22    // Tipo DHT22

//-------------------------
// Definição do Objeto DHT
//-------------------------

DHT dht(DHTPin, DHTType);     // Instancia do Objeto DHT

//----------------------------------------------
// Definição da Estrutura de Dados a Receber
//----------------------------------------------

typedef struct struct_mensagem 
{
  char nomeEstacao[MAX_NOME+1]; // Nome esta estação ou Nó da rede ESP-NOW 
  char macEstacao[18];          // MAC desta estação  ou Nó da rede ESP-NOW 
  float temp;                   // Temperatura lida
  float umid;                   // Umidade lida
} struct_mensagem;

//------------------------------------------------------
// Ajuste as linhas abaixo para a sua credencial de rede
//------------------------------------------------------

const char* ssid     = "<não se esqueça de informar o SSID de sua Rede Wi-Fi>";
const char* password = "<não se esqueça de informar s SENHA de sua Rede Wi-Fi>";

//----------------------------------
// Variáveis de uso geral
//----------------------------------

unsigned long lastVarredura=0; // Última varredura 
bool estadoLed = LOW;          // Estado do Led para indicar a varredura
float temp;                    // Para leitura da Temperatura
float umid;                    // Para leitura da umidade
JsonDocument jsonLeitura;      // Para enviar leitura local 
String jsonTexto;              // Para envio do Json como texto 

//------------------------------------
// Define o HTML para Página Principal
//------------------------------------

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ESP-NOW Data</title>
    <style>
       header { background-color: #333; color: #fff; text-align: center; padding: 1px; font-size: 16px;}
//       body {
//           font-family: Arial, sans-serif;
//           background-color: #f0f0f0;
//           margin: 0;
//           padding: 0;
//           display: flex;
//           justify-content: center;
//           align-items: center;
//           height: 100vh;
//       }
       .container {
           width: 100%;
           max-width: 600px;
           margin: 20px;
       }
       .panel {
           background-color: white;
           border-radius: 8px;
           box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
           margin-bottom: 10px;
           padding: 10px;
       }
       .panel .header {
           font-size: 14px;
//           font-weight: bold;
           color: #888;
           margin-bottom: 10px;
       }
       .panel .content {
           font-size: 24px;
           margin-bottom: 10px;
       }
       .panel .timestamp {
           font-size: 14px;
           color: #888;
       }   
       .blue-light { color: lightblue; }
       .blue-dark { color: darkblue; }
       .green { color: green; }
       .red-light { color: lightcoral; }
       .red-dark { color: darkred; }
       .orange { color: orange; }
       .yellow { color: yellow; }           
    </style>       
</head>
<body>
    <header>
      <h2>Receptor ESP-NOW Web</h2>
    </header>
    <div id="transmitter-data"></div>

    <script>

       // Função para determinar a cor da Temperatura
       
       function getTemperatureClass(temp) 
       {
            if (temp < 10) return 'blue-light';
            if (temp < 20) return 'blue-dark';
            if (temp < 25) return 'green';
            if (temp < 30) return 'red-light';
            return 'red-dark';
        }

        // Função para determinar a cor da Umidade
        
        function getHumidityClass(humid) 
        {
            if (humid <= 15) return 'red-dark';
            if (humid <= 30) return 'orange';
            if (humid <= 55) return 'blue-light';
            return 'green';
        }

        if (!!window.EventSource) 
        {
           // Lista para manter controle dos elementos por ID
           var transmitterElements = {}; 

           // Evento para receber os dados
           var source = new EventSource('/events');
           
           source.addEventListener('open', function(e) {
            console.log("Events Connected");
           }, false);
           source.addEventListener('error', function(e) {
            if (e.target.readyState != EventSource.OPEN) {
              console.log("Events Disconnected");
            }
           }, false);
           
           source.addEventListener('message', function(e) {
            console.log("message", e.data);
           }, false);
           
           source.addEventListener('new_readings', function(e) {
            console.log("new_readings", e.data);
            // Obtenha os dados do evento
            var eventData = JSON.parse(e.data);
            var nomeEstacao = eventData.nomeEstacao;
            var macEstacao  = eventData.macEstacao;
            var temp        = Math.round(eventData.temp);
            var umid        = Math.round(eventData.umid);
            var timestamp   = new Date().toLocaleString();

            // Obtenha as cores para a temperatura e umidade
            var tempClass = getTemperatureClass(temp);
            var humidClass = getHumidityClass(umid);

            // Verifique se o elemento para este ID já existe na lista
            if (transmitterElements[macEstacao]) {
                // Atualizar o conteúdo do painel existente
                var contentElement = transmitterElements[macEstacao].querySelector('.content');
                var timestampElement = transmitterElements[macEstacao].querySelector('.timestamp');

                contentElement.innerHTML = `Temperatura: <span class="${tempClass}">${temp}°C</span>, Umidade: <span class="${humidClass}">${umid}%</span>`;
                timestampElement.innerHTML = `Timestamp: ${timestamp}`;
            } else {
                // Caso contrário, crie um novo elemento e adicione-o na lista
                var newTransmitterDataElement = document.createElement("div");
                newTransmitterDataElement.classList.add("panel");
                newTransmitterDataElement.id = macEstacao;
                newTransmitterDataElement.innerHTML = `
                    <div class="header">ID: ${nomeEstacao}, MAC: ${macEstacao}</div>
                    <div class="content">Temperatura: <span class="${tempClass}">${temp}°C</span>,  Umidade: <span class="${humidClass}">${umid}%</span></div>
                    <div class="timestamp">Timestamp: ${timestamp}</div>
                `;
                // Adiciona o novo painel na página
                document.getElementById("transmitter-data").appendChild(newTransmitterDataElement);
                // Adiciona a refer~encia do painel na lista
                transmitterElements[macEstacao] = newTransmitterDataElement;
            }
           }, false);
        }
    </script>
</body>
</html>
)rawliteral";

//---------------------------------------------
// Variáveis para controle do Server http
//---------------------------------------------

AsyncWebServer server(80);
AsyncEventSource events("/events");

//-------------------------------------------
// Prototipação da rotina usada para recepção
//-------------------------------------------

void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len);

//---------------------------------------------
// Inicialização do Programa e recursos usados
//---------------------------------------------

void setup() 
{
    // Inicializa a Serial
    
    Serial.begin(115200);
    while (!Serial) ;

    // Inicializa LED_BUILTIN

    pinMode(LED_BUILTIN, OUTPUT);

    // Inicializa o DHT11/DHT22
  
    dht.begin();

    // Inicializa Json com a parte fixa para envio das leituras locais

    jsonLeitura["nomeEstacao"] = NOME_ESTACAO;
    jsonLeitura["macEstacao"]  = WiFi.macAddress();    
    
    // Define o modo Estação e AP ao mesmo tempo para ser possível 
    // funcionar como ESP-NOW e WebServer simultaneamente
    
    WiFi.mode(WIFI_AP_STA);

    // Inicializa a Rede ESP-NOW
    
    if (esp_now_init() != ESP_OK) 
    {
      Serial.println("Erro na inicialização do ESP-NOW");
      return;
    }
    
    // Registra a rotina de Callback para recepção dos dados da Rede ESP-NOW 
    
    if (esp_now_register_recv_cb(OnDataRecv) != ESP_OK)
    {
       Serial.println("Erro ao registrar o CallBack de recepção do ESP-NOW");
       return;        
    }    
    
    // Inicializa a estação Wi-Fi
    
    WiFi.begin(ssid, password);
    Serial.print("Configurando o Modo Estação Wi-Fi");
    while (WiFi.status() != WL_CONNECTED) {
      delay(1000);
      Serial.print(".");
    }
    Serial.println("\n");

    // Configurar o canal WiFi após a conexão. Não funcionou. O roteador
    // é que define o canal e não permite alterar. Teremos que usar
    // hardcode no Transmissor pois cada roteador define um canal diferente
    // de 0 a 12

    //esp_wifi_set_promiscuous(true); // Modo promíscuo é necessário para configurar o canal
    //esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE);
    //esp_wifi_set_promiscuous(false); // Desativar o modo promíscuo

    // Mostra o IP adquirido da rede Wi-Fi
   
    Serial.print("Endereço IP : ");
    Serial.println(WiFi.localIP());

    // Mostra o MAC do Esp32

    Serial.print("Endereço MAC: ");
    Serial.println(WiFi.macAddress()); 

    // Mostra o canal associado pela rede Wi-Fi. Este mesmo canal
    // deverá ser utilizado pelo Transmissor

    Serial.print("Canal Wi-Fi : ");
    Serial.println(WiFi.channel());
    Serial.println();

    // Define o HostName para o servidor web para facilitar o acesso na rede local
    // sem conhecer o IP previamente

    WiFi.setHostname(String(NOME_ESTACAO).c_str());

    Serial.print("Adicionando " + String(NOME_ESTACAO) + " no MDNS... ");
    if (MDNS.begin(String(NOME_ESTACAO).c_str()))
    {
        Serial.println("adicionado corretamente no MDNS!");
        Serial.println("Use http://" + String(NOME_ESTACAO) + ".local no seu navegador...");  
        Serial.println("Ou opcionalmente...");    
    }
    else 
    {
        Serial.println("Erro ao adicionar no MDNS!");
    }
    Serial.println("Use http://" + WiFi.localIP().toString() + " no seu navegador...\n");
    
    MDNS.addService("http", "tcp", 80);    
  
    // Configurar rota para a página HTML
    
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/html", index_html);
    });

    // Configura onConnect dos Eventos
    
    events.onConnect([](AsyncEventSourceClient *client)
    {
      if(client->lastId())
      {
        Serial.printf("Cliente reconectado! Último ID obtido: %u\n", client->lastId());
      }
    });
    server.addHandler(&events);

    // Iniciar o servidor Web
    
    server.begin();
   
}

//----------------------------------------------
// Loop principal para fazer as leituras da
// temperatura e umidade e enviar para a página
// fazer o refresh
//---------------------------------------------

void loop() 
{

  // Verifica se deve fazer a varredura

  if (millis() - lastVarredura > VARREDURA)
  {

     // Alterna o estado do Led

     estadoLed = !estadoLed;
     digitalWrite(LED_BUILTIN,estadoLed);

     // Lê os valores do DHT. Considera Zero em caso de erro.
    
     temp = dht.readTemperature();
     if (isnan(temp)) temp = 0;
     delay(10);
     umid = dht.readHumidity();
     if (isnan(umid)) umid = 0;
     delay(10);

     // Mostra os valores lidos na Console

     Serial.print("Estação: ");
     Serial.println(NOME_ESTACAO);
     Serial.print("MAC Address: ");
     Serial.println(WiFi.macAddress());     
     Serial.print("Temperatura: ");
     Serial.print(temp,0);
     Serial.println("°C");
     Serial.print("Umidade: ");
     Serial.print(umid,0);
     Serial.println("%");
     Serial.println();

     // Atualiza Json com dados da Temp/Umid para envio

     jsonLeitura["temp"] = temp;
     jsonLeitura["umid"] = umid; 

     // Serializar o objeto JSON para uma string
  
     serializeJson(jsonLeitura, jsonTexto);     
     //serializeJson(jsonLeitura, Serial); 

     // Enviar os dados serializados para a página HTML usando o evento personalizado
  
     events.send(jsonTexto.c_str(), "new_readings", millis());      

     // Atualiza a última varredura

     lastVarredura = millis();     

  }
}

//--------------------------------------
// Definição do Callback do recebimento
//--------------------------------------

void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) 
{

  // Estrutura local para ser usada na recepção por causa de reentrância

  struct_mensagem mensagem;     

  // Liga o LED Interno na recepção

  digitalWrite(LED_BUILTIN, HIGH);
  
  // Recebe os dados transmidos
  
  memcpy(&mensagem, incomingData, sizeof(mensagem));

  // Mostra o Nome da Estação que enviou

  Serial.print("Estação: ");
  Serial.println(mensagem.nomeEstacao);

  // Mostra o MAC da Estação que enviou

  Serial.print("MAC Address: ");
  Serial.println(mensagem.macEstacao);
  
  // Mostra a Temperatura 
  
  Serial.print("Temp: ");
  Serial.print(mensagem.temp,0);
  Serial.println("°C");

  // Mostra a Umidade
  
  Serial.print("Umid: ");
  Serial.print(mensagem.umid,0); 
  Serial.println("%");

  // Dá uma linha de separação na Console

  Serial.println();

  // Criar um objeto JSON para enviar para a página HTML
  
  JsonDocument jsonDocument;
  jsonDocument["nomeEstacao"] = mensagem.nomeEstacao;
  jsonDocument["macEstacao"]  = mensagem.macEstacao;
  jsonDocument["temp"]        = mensagem.temp;
  jsonDocument["umid"]        = mensagem.umid;

  // Serializar o objeto JSON para uma string
  
  String jsonData;
  serializeJson(jsonDocument, jsonData);
  //serializeJson(jsonDocument, Serial);

  // Enviar os dados serializados para a página HTML usando o evento personalizado
  
  events.send(jsonData.c_str(), "new_readings", millis()); 

  // Desliga o LED Interno na recepção

  digitalWrite(LED_BUILTIN, LOW);  
  
}

 


Código do Nó Transmissor

//-----------------------------------------------------------------------------
// Função : Este sketch tem como objetivo implementar um nó de uma rede ESP-NOW
//          como TRANSMISSOR para enviar dados de temperatura e umidade de um 
//          DHT22/DHT11 para um  RECEPTOR que publicará os dados via WEB.
//
// Autores: Alberto de Almeida Menezes
//          Dailton Menezes 
//
// Versão : V1.0 Mai/2024
//
// Observações: 1) O TRANSMISSOR por ser um ESP32 ou ESP8266
//              2) O Canal associado pela sua rede wi-fi será determinado
//                 através do SSID fornecido (hardcode)
// 
// Referências: 1) https://dronebotworkshop.com/esp-now/
//              2) https://randomnerdtutorials.com/esp-now-esp32-arduino-ide/
//              3) https://randomnerdtutorials.com/esp32-esp-now-wi-fi-web-server/
//              4) https://randomnerdtutorials.com/esp-now-esp8266-nodemcu-arduino-ide/
//              5) https://www.youtube.com/watch?v=_eHe7ViXyY8&t=898s
//              
//-----------------------------------------------------------------------------

#define VARREDURA    5000           // Tempo de varredura em msec
#define MAX_NOME     20             // Tamanho máximo do Nome da Estação

// Definições para o ESP32-CAM ou XIAO ESP32S3

//#define ESP32CAM                  // Descomente se for um ESP32-CAM
//#define XIAO                      // Descomente se for um XIAO ESP32S3

//----------------------------
// Inclusão das bibliotecas
//----------------------------

#ifdef ESP8266
#include <ESP8266WiFi.h>            // Biblioteca WiFi para o ESP8266
#include <espnow.h>                 // Biblioteca ESP-NOW para o ESP8266
extern "C" {
  #include "user_interface.h" 
}
#else
#include <WiFi.h>                   // Biblioteca WiFi para o ESP32
#include <esp_now.h>                // Biblioteca ESP-NOW para o ESP32
#include <esp_wifi.h>               // Biblioteca WiFi especifica para ESP32
#endif

#include <DHT.h>                    // Biblioteca para o DHT11/DHT22

//------------------------
// Definições para o DHT
//------------------------

#ifdef ESP8266
#define NOME_ESTACAO "Estação-8266" // Nome desta Estação de coleta de Temp/Umid
#define DHTPin  14                  // Porta D5 = GPIO14 no ESP8266
#define DHTType DHT11               // Tipo DHT11
#define LED_BUILTIN  2              // Pino para o Led Interno do ESP32/ESP8266
#elif defined(ESP32CAM)
#define NOME_ESTACAO "Estação-CAM"  // Nome desta Estação de coleta de Temp/Umid
#define DHTPin  GPIO_NUM_13         // Porta GPIO13 no ESP32-CAM
#define DHTType DHT11               // Tipo DHT22
#define LED_BUILTIN  GPIO_NUM_33    // Pino para o Led Interno do ESP32-CAM
#elif defined(XIAO)
#define NOME_ESTACAO "Estação-XIAO" // Nome desta Estação de coleta de Temp/Umid
#define DHTPin  GPIO_NUM_8          // Porta D9 = GPIO8 no XIAO ESP32S3
#define DHTType DHT11               // Tipo DHT11
#define LED_BUILTIN  GPIO_NUM_21    // Pino para o Led Interno do XIAO
#else
#define NOME_ESTACAO "Estação-ESP"  // Nome desta Estação de coleta de Temp/Umid
#define DHTPin  4                   // Porta D4 = GPIO4 no ESP32
#define DHTType DHT22               // Tipo DHT22
#define LED_BUILTIN  2              // Pino para o Led Interno do ESP32/ESP8266
#endif
 
//-------------------------
// Definição do Objeto DHT
//-------------------------

DHT dht(DHTPin, DHTType);           // Instancia do Objeto DHT

//---------------------------
// Ajuste o SSID de sua Rede
//---------------------------

const char* ssid   = "<não esqueça de informr o SSID da sua Rede>";

//-----------------------------------------
// Definições para a Temperatura e Umidade
//-----------------------------------------

float temp;                         // Para leitura da Temperatura
float umid;                         // Para leitura da umidade

//--------------------------------------------------------
// Definição do Endereço do RECEPTOR
// Importante: Não esqueça de alterar a definição a seguir
//             para o MAC de seu Nó Central
//--------------------------------------------------------

uint8_t receptorAddress[] = {0x08, 0x3A, 0xF2, 0xB6, 0xA9, 0xE8};

//----------------------------------
// Variáveis de uso geral
//----------------------------------

unsigned long lastVarredura=0;      // Última varredura 
bool estadoLed = LOW;               // Estado do Led para indicar a varredura
int32_t channel;                    // Canal da rede Wi-Fi que será usado 


//----------------------------------------------
// Definição da Estrutura de Dados a Transmitir
//----------------------------------------------

typedef struct struct_mensagem 
{
  char nomeEstacao[MAX_NOME+1];     // Nome esta estação ou Nó da rede ESP-NOW 
  char macEstacao[18];              // MAC desta estação  ou Nó da rede ESP-NOW 
  float temp;                       // Temperatura lida
  float umid;                       // Umidade lida
} struct_mensagem;

//-------------------------------
// Definição da Área a Transmitir
//-------------------------------

struct_mensagem mensagem;           // Estrutura para ser usada na transmissão

//--------------------------------------------
// Definição da Área para MAC do Destinatário 
//--------------------------------------------

#ifdef ESP32
esp_now_peer_info_t peerInfo = {};  // Endereço do destinatário já inicializado
#endif

//--------------------------------
// Definição do Callback do envio
//--------------------------------

#ifdef ESP8266
void OnDataSent(uint8_t *mac_addr, uint8_t status)
#else
void OnDataSent(const uint8_t *macAddr, esp_now_send_status_t status)
#endif
{
  Serial.print("Status do Último Envio: ");
  Serial.println(status == 0 ? "Sucesso" : "Falhou");
}

//---------------------------------------------
// Rotina para detectar o canal utilizado pela
// Rede Wi-fi cujo o SSID foi definido
//---------------------------------------------

int32_t getWiFiChannel(const char *ssid) 
{
  if (int32_t n = WiFi.scanNetworks()) 
  {
      for (uint8_t i=0; i<n; i++) 
      {
          if (!strcmp(ssid, WiFi.SSID(i).c_str())) 
          {
              return WiFi.channel(i);
          }
      }
  }
  return 0;
}

//--------------------------------------
// Rotina de Inicialização da Aplicação
//--------------------------------------

void setup() 
{

  // Inicialização da Serial
  
  Serial.begin(115200);
  while (!Serial);

  // Inicializa LED interno

  pinMode(LED_BUILTIN, OUTPUT);

  // Inicializa o DHT11/DHT22
  
  dht.begin();

  // Inicializa a rede Wi-fi como Estação
  
  WiFi.mode(WIFI_STA);

  // Descobre qual o canal definido pelo roteador para a rede do SSID informado
  
  channel = getWiFiChannel(ssid);

  // Altera o canal na rede Wi-fi local

#ifdef ESP8266
  wifi_promiscuous_enable(true);
  wifi_set_channel(channel);
  wifi_promiscuous_enable(false); 
#else
  esp_wifi_set_promiscuous(true);
  esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
  esp_wifi_set_promiscuous(false);    
#endif

  // Inicializa a rede ESP-NOW
  
  if (esp_now_init() != 0) 
  {
    Serial.println("Erro ao inicializar a Rede ESP-NOW");
    return;
  }
  else Serial.println("Rede ESP-NOW inicializada com sucesso");

  // Definição do Callback do envio

#ifdef ESP8266
  esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
#endif
  
  esp_now_register_send_cb(OnDataSent);

#ifdef ESP8266
  if (esp_now_add_peer(receptorAddress, ESP_NOW_ROLE_SLAVE, channel, NULL, 0 ) != 0)  
#else 
  memcpy(peerInfo.peer_addr, receptorAddress, 6);
  peerInfo.channel = channel;
  peerInfo.encrypt = false;
  if (esp_now_add_peer(&peerInfo) != 0) 
#endif
  {
    Serial.println("Erro ao adicionar o Destinatário");
    return;
  }
  else Serial.println("Destinatário adicionado com sucesso");

  // Atualiza a parte fixa na área de transmissão
  
  strcpy(mensagem.nomeEstacao,NOME_ESTACAO);
  strcpy(mensagem.macEstacao,WiFi.macAddress().c_str());

  // Mostra o Nome desta Estação

  Serial.print("Nome da Estação : ");
  Serial.println(mensagem.nomeEstacao);

  // Mostra o MAC desta Estação

  Serial.print("MAC ADDRESS da Estação : ");
  Serial.println(mensagem.macEstacao);

  // Mostra o canal a ser utilizado

  Serial.print("Canal da Rede Wi-fi: ");
  Serial.println(channel);

  // Mostra LED_BUILTIN

  Serial.print("LED_BUILTIN: ");
  Serial.println(LED_BUILTIN);  

}

//----------------------------------------------
// Loop para fazer as leituras de temperatura e 
// umidade e envio para o RECEPTOR
//----------------------------------------------

void loop() 
{

  // Verifica se deve fazer a varredura

  if (millis() - lastVarredura > VARREDURA)
  {

     // Alterna o estado do Led

     estadoLed = !estadoLed;
     digitalWrite(LED_BUILTIN,estadoLed);

     // Lê os valores do DHT. Considera Zero em caso de erro.
    
     temp = dht.readTemperature();
     if (isnan(temp)) temp = 0;
     delay(10);
     umid = dht.readHumidity();
     if (isnan(umid)) umid = 0;
     delay(10);

     // Mostra os valores lidos na Console

     Serial.print("Wi-Fi Channel: ");
     Serial.println(WiFi.channel());
     Serial.print("Temperatura: ");
     Serial.print(temp,0);
     Serial.println("°C");
     Serial.print("Umidade: ");
     Serial.print(umid,0);
     Serial.println("%");
  
     // Adiciona os valores lidos na estrutura de transmissão
    
     mensagem.temp = temp;
     mensagem.umid = umid;
  
     // Envia os dados
    
     esp_now_send(receptorAddress, (uint8_t *) &mensagem, sizeof(mensagem));

     // Atualiza a última varredura

     lastVarredura = millis();
  
  }
  
}

 


Insucessos no Projeto

Quando implementamos o nó central com a função de Web Server coexistindo com a função receptor da Rede ESP-NOW, o ESP8266 passou a dar erro na transmissão do pacote. O ESP8266, teoricamente, só consegue mandar dados para um nó apenas com a função de receptor. Pesquisei de diversas formas, mas não encontrei uma solução. Para exemplificar isso, caso algum leitor queira comprovar, estou apresentando o sketch do nó central apenas como receptor. Com este receptor, o ESP8266 consegue enviar pacotes normalmente. Já o nó ESP32 transmissor permaneceu estável e não apresentou tal problema.

//-----------------------------------------------------------------------------
// Função : Este sketch tem como objetivo implementar o nó principal de uma 
//          rede ESP-NOW como RECEPTOR dos dados de temperatura e umidade de um 
//          ou mais TRANSMISSORES.
//
// Autores: Alberto de Almeida Menezes
//          Dailton Menezes
//
// Versão : V1.0 Mai/2024
//
// Observação: O RECEPTOR deve ser um ESP32
// 
// Referências: 1) https://dronebotworkshop.com/esp-now/
//              2) https://randomnerdtutorials.com/esp-now-esp32-arduino-ide/
//              3) https://randomnerdtutorials.com/esp32-esp-now-wi-fi-web-server/
//              4) https://randomnerdtutorials.com/esp-now-esp8266-nodemcu-arduino-ide/
//              5) https://www.youtube.com/watch?v=_eHe7ViXyY8&t=898s
//              
//-----------------------------------------------------------------------------

//----------------------------
// Inclusão das bibliotecas
//----------------------------

#include <WiFi.h>             // Biblioteca WiFi para o ESP32
#include <esp_now.h>          // Biblioteca ESP-NOW para o ESP32
#include <esp_wifi.h>         // Biblioteca WiFi especifica para ESP32
#include <DHT.h>              // Biblioteca para o DHT11/DHT22

#define LED_BUILTIN  2        // Pino para o Led Interno do ESP32/ESP8266
#define MAX_NOME     20       // Tamanho máximo do Nome da Estação
#define VARREDURA    5000     // Tempo de varredura em msec
#define NOME_ESTACAO "Estação-0" // Nome desta Estação de coleta de Temp/Umid

#define DHTPin       27       // Porta 27 ESP32-WROOM-32U
#define DHTType      DHT22    // Tipo DHT22

//-------------------------
// Definição do Objeto DHT
//-------------------------

DHT dht(DHTPin, DHTType);     // Instancia do Objeto DHT

//------------------------------------------------------
// Ajuste as linhas abaixo para a sua credencial de rede
//------------------------------------------------------

const char* ssid = "<não se esqueça de informar o SSID de sua Rede Wi-Fi>";

//----------------------------------
// Variáveis de uso geral
//----------------------------------

unsigned long lastVarredura=0; // Última varredura 
bool estadoLed = LOW;          // Estado do Led para indicar a varredura
float temp;                    // Para leitura da Temperatura
float umid;                    // Para leitura da umidade
int32_t channel;               // Canal da rede Wi-Fi que será usado 

//----------------------------------------------
// Definição da Estrutura de Dados a Receber
//----------------------------------------------

typedef struct struct_mensagem 
{
  char nomeEstacao[MAX_NOME+1]; // Nome esta estação ou Nó da rede ESP-NOW 
  char macEstacao[18];          // MAC desta estação  ou Nó da rede ESP-NOW 
  float temp;                   // Temperatura lida
  float umid;                   // Umidade lida
} struct_mensagem;

//--------------------------------------
// Definição do Callback do recebimento
//--------------------------------------

void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) 
{

  // Estrutura local para ser usada na recepção por causa de reentrância

  struct_mensagem mensagem;     

  // Liga o LEd Interno na recepção

  digitalWrite(LED_BUILTIN, HIGH);
  
  // Recebe os dados transmidos
  
  memcpy(&mensagem, incomingData, sizeof(mensagem));

  // Mostra o Nome da Estação que enviou

  Serial.print("Estação: ");
  Serial.println(mensagem.nomeEstacao);

  // Mostra o MAC da Estação que enviou

  Serial.print("MAC Address: ");
  Serial.println(mensagem.macEstacao);
  
  // Mostra a Temperatura 
  
  Serial.print("Temp: ");
  Serial.print(mensagem.temp,0);
  Serial.println("°C");

  // Mostra a Umidade
  
  Serial.print("Umid: ");
  Serial.print(mensagem.umid,0); 
  Serial.println("%");

  // Faz uma separação na Console

  Serial.println();

  // Desliga o LEd Interno na recepção

  digitalWrite(LED_BUILTIN, HIGH);
  
}

//---------------------------------------------
// Rotina para detectar o canal utilizado pela
// Rede Wi-fi cujo o SSID foi definido
//---------------------------------------------

int32_t getWiFiChannel(const char *ssid) 
{
  if (int32_t n = WiFi.scanNetworks()) 
  {
      for (uint8_t i=0; i<n; i++) 
      {
          if (!strcmp(ssid, WiFi.SSID(i).c_str())) 
          {
              return WiFi.channel(i);
          }
      }
  }
  return 0;
}
 
void setup() 
{

  // Inicializa a Serial 
  
  Serial.begin(115200);
  while (!Serial);

  // Inicializa LED_BUILTIN

  pinMode(LED_BUILTIN, OUTPUT);  

  // Inicializa o DHT11/DHT22
  
  dht.begin();

  // Inicializa o Wifi Mode 
  
  WiFi.mode(WIFI_STA);

  // Descobre qual o canal definido pelo roteador para a rede do SSID informado
  
  channel = getWiFiChannel(ssid);

  // Atualiza o canal a ser usado

  esp_wifi_set_promiscuous(true); // Modo promíscuo é necessário para configurar o canal
  esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
  esp_wifi_set_promiscuous(false); // Desativar o modo promíscuo  

  // Inicializa ESP-NOW
  
  if (esp_now_init() != 0) 
  {
     Serial.println("Erro ao inicializar a Rede ESP-NOW");
     return;
  }
   
  // Registra o Callback
  
  esp_now_register_recv_cb(OnDataRecv);

  // Hello na Console

  Serial.println("Receptor de Dados de Tempertura/Umidade V1.0 Mai/2024");
  Serial.print("Escutando no Canal : ");
  Serial.println(WiFi.channel());
  
}

//----------------------------------------------
// Loop principal para fazer as leituras da
// temperatura e umidade e enviar para a Console
//---------------------------------------------

void loop() 
{

  // Verifica se deve fazer a varredura

  if (millis() - lastVarredura > VARREDURA)
  {

     // Alterna o estado do Led

     estadoLed = !estadoLed;
     digitalWrite(LED_BUILTIN,estadoLed);

     // Lê os valores do DHT. Considera Zero em caso de erro.
    
     temp = dht.readTemperature();
     if (isnan(temp)) temp = 0;
     delay(10);
     umid = dht.readHumidity();
     if (isnan(umid)) umid = 0;
     delay(10);

     // Mostra os valores lidos na Console

     Serial.print("Estação: ");
     Serial.println(NOME_ESTACAO);
     Serial.print("MAC Address: ");
     Serial.println(WiFi.macAddress());     
     Serial.print("Temperatura: ");
     Serial.print(temp,0);
     Serial.println("°C");
     Serial.print("Umidade: ");
     Serial.print(umid,0);
     Serial.println("%");
     Serial.println();

     // Atualiza a última varredura

     lastVarredura = millis();     

  }
}

 


Demonstração da Rede ESP-NOW com diferentes ESP's transmissores

Para esta demonstração, fizemos duas alterações nos sketch’s do Receptor e dos Transmissores. As alterações foram as seguintes:

Nos transmissores, o sketch foi alterado para suportar os modelos ESP32 Normal, com 30 pinos, ESP32-CAM e XIAO ESP32S3. A compilação é condicional baseada na definição de qual microcontrolador foi escolhido nas definições a seguir:

// Definições para o ESP32-CAM ou XIAO ESP32S3

//#define ESP32CAM                  // Descomente se for um ESP32-CAM
//#define XIAO                      // Descomente se for um XIAO ESP32S3

O ESP32 e o ESP8266 serão assumidos  na escolha da placa (Board) no IDE na hora da compilação, sendo que as duas definições acima devem permanecer comentadas.

A tabela a seguir mostra quais placas (Board) devem ser escolhidas na Arduino IDE :

Microcontrolador Opção de Compilação para a placa (Board)
ESP32 ESP32 Dev Module
ESP32-CAM Al Thinker ESP32-CAM
ESP8266 Generic ESP8266 Module
XIAO ESP32S3 XIAO_ESP32S3

No Receptor, foi implementada a inclusão do nome da ESTAÇÃO no mDNS para facilitar o acesso à URL do servidor WEB. Para isso será necessário incluir a biblioteca <ESPmDNS.h> e o código abaixo na rotina void setup():

#include <ESPmDNS.h>          // Biblioteca para inclusão do hostname no mDNS

    // Define o HostName para o servidor web para facilitar o acesso na rede local
    // sem conhecer o IP previamente

    WiFi.setHostname(String(NOME_ESTACAO).c_str());

    Serial.print("Adicionando " + String(NOME_ESTACAO) + " no MDNS... ");
    if (MDNS.begin(String(NOME_ESTACAO).c_str()))
    {
        Serial.println("adicionado corretamente no MDNS!");
        Serial.println("Use http://" + String(NOME_ESTACAO) + ".local no seu navegador...");  
        Serial.println("Ou opcionalmente...");    
    }
    else 
    {
        Serial.println("Erro ao adicionar no MDNS!");
    }
    Serial.println("Use http://" + WiFi.localIP().toString() + " no seu navegador...\n");
    
    MDNS.addService("http", "tcp", 80);

 

 


Transmissor usando DEEP SLEEP MODE

Nesta seção, vamos apresentar a versão do Transmissor usando a função Deep Sleep Mode  disponível na família de microcontroladores ESP, visando economia de energia, principalmente se a alimentação da placa for via bateria. No Blog da Eletrogate existe um post que aborda este assunto (link a seguir). Recomendamos aos leitores darem uma olhada pois há um maior aprofundamento no assunto de Deep Sleep Mode.

Economizando Energia – Sleep Modes no ESP8266 – Blog Eletrogate

Neste exemplo, utilizaremos o Deep Sleep Mode controlado por Timer para definir o tempo de hibernação. Existem outras formas que o artigo mencionado explora bem. O transmissor foi adaptado para usar o Deep Sleep tanto na família ESP32 como no ESP8266, pois há diferença nas API’s. Na tabela a seguir, é possível observar que o uso do Deep Sleep Mode pode promover a queda do consumo de energia da ordem de 240 mA para 150 µA. Portanto, é uma economia expressiva para autonomia de baterias e/ou de economia de energia elétrica.

Mode Description Power Consumption
Active (RF) The chip radio is powered up. The chip can receive, transmit, or listen. All systems GO! 95-240 mA
Modem-Sleep The CPU is operational and the clock is configurable. Wi-Fi/Bluetooth disabled. Radio silence. 20-68 mA
Deep Sleep The Ultra Low Power coprocessor is functional. RTC can still work. Enter Sandman. 10 µA – 150 µA (note the unit change!)

A caraterística marcante da aplicação usando hibernação é o fato da funcionalidade principal acontecer na rotina setup(), ou seja, a rotina loop() ficará sem utilidade (vazia) ou pode ser utilizada para uma função secundária.  No nosso caso, vamos utilizá-la para piscar o LED interno, de forma rápida, caso algum erro aconteça na inicialização da Rede ESPNOW na rotina setup(), ou seja, a funcionalidade principal não poderia ser executada (coleta, envio e a hibernação). O fato de toda funcionalidade ficar dentro do setup(), quando o setup() é iniciado, a área de dados global da aplicação será perdida e reiniciada para os valores definidos em tempo de compilação. Caso seja necessário persistir variáveis que não podem ser perdidas, existe a possibilidade de armazenar usando a memória RTC, que fica preservada durante a hibernação. Neste próximo exemplo, utilizaremos o recurso da memória RTC para armazenar o contador de ciclos de execuções. A Referência 8 demonstra o uso da memória RTC.

A implementação do acesso à memória RTC é feita de forma diferente na família ESP32 para o ESP8266. Utilizaremos a compilação condicional para usar as diferentes API’s. No ESP32, o uso é bem mais simples, basta declarar a estrutura de dados desejada com o prefixo RTC_DATA_ATTR. No ESP8266 é necessário usar uma biblioteca específica, a RTCMemory.h, e usar API’s para ler e salvar. Veja a declaração da área para o contador a seguir :

//----------------------------------------------
// Definição da Estrutura do contador que será
// persistido na RTC Memory para evitar perda.
// Máximo tamanho de 508 bytes para ESP8266 ou
// 8K para o ESP32
//----------------------------------------------

typedef struct 
{
  uint_fast32_t valor;
} rtcDados;

//-------------------------------
// Definição da Área do Contador
//-------------------------------

#ifdef ESP8266
RTCMemory<rtcDados> contador_ciclos;    // Definição do objeto para tratar a RTC Memory
rtcDados *contador;                     // Área para receber os dados da RTC Memory no ESP8266
#else
RTC_DATA_ATTR uint_fast32_t contador=0; // Área para receber os dados da RTC Memory no ESP32
#endif

O tipo uint_fast32_t é um recurso livre para a implementação do fornecedor da arquitetura do microcontrolador. É um tipo inteiro e sem sinal que pode ter 32 Bits ou 64 Bits, dependendo da arquitetura.

As operações de recuperação, atualização e salvamento do contador na memória RTC pode ser observada no fragmento do código a seguir:

   // Inicializa o objeto da RTC Memory para o ESP8266
   
#ifdef ESP8266
   if (contador_ciclos.begin()) 
   {
      Serial.println("Contador já existe previamente.");
   }
   else 
   {
      Serial.println("Buffer do Contador foi inicializado.");
   }
    
   // Recupera o contador da RTC Memory
  
    contador = contador_ciclos.getData();
#endif    


   // Mostra o contador recuperado da prévia execução

#ifdef ESP8266  
   Serial.printf("Valor do Contador recuperado: %d\n",contador->valor); 
#else
   Serial.printf("Valor do Contador recuperado: %d\n",contador);
#endif      

   // Atualiza o novo contador de ciclos, mostra na console e salva na RTC Menory

#ifdef ESP8266 
   Serial.printf("Valor do Contador atualizado: %d\n",++contador->valor);
   contador_ciclos.save();
#else
   Serial.printf("Valor do Contador atualizado: %d\n",++contador);
#endif   

Outra observação importante, no ESP8266 o evento de acordar só acontece se os pinos RST e GPIO16 estiverem interligados. O post mencionado também mostra o diagrama da interligação.

A seguir, apresentamos o código completo:

//-----------------------------------------------------------------------------
// Função : Este sketch tem como objetivo implementar um nó de uma rede ESP-NOW
//          como TRANSMISSOR para enviar dados de temperatura e umidade de um 
//          DHT22/DHT11 para um RECEPTOR que publicará os dados via WEB. Usa
//          também a hipernação na modalidade DEEP SLEEP com TIMER para economia  
//          do consumo de energia, dando mais autonomia em caso da alimentação ser
//          através de bateria ou, dando mais economia quando a alimentação for
//          direta.
//
// Autores: Alberto de Almeida Menezes
//          Dailton Menezes 
//
// Versão : V1.0 Mai/2024
//
// Observações: 1) O TRANSMISSOR por ser um ESP32 ou ESP8266
//              2) O Canal associado pela sua rede wi-fi será determinado
//                 através do SSID fornecido (hardcode)
//              3) Usaremos o modo DEEP SLEEP para economia de 
//                 energia através de timer.
//              4) Como toda a funcionalidade do programa acontece na rotina de setup(),
//                 a área de dados global é perdida a cada acordada do DEEP SLEEP MODE.
//                 Utilizaremos a RTC Memory para armezenar o contador de 
//                 ciclos durante o período de hibernação para evitar a perda;
//
// Referências  1) https://blog.eletrogate.com/esp-now-comunicacao-sem-fio-entre-esp32s/
//              2) https://dronebotworkshop.com/esp-now/
//              3) https://randomnerdtutorials.com/esp-now-esp32-arduino-ide/
//              4) https://randomnerdtutorials.com/esp32-esp-now-wi-fi-web-server/
//              5) https://randomnerdtutorials.com/esp-now-esp8266-nodemcu-arduino-ide/
//              6) https://www.youtube.com/watch?v=_eHe7ViXyY8&t=898s
//              7) https://randomnerdtutorials.com/esp8266-deep-sleep-with-arduino-ide/
//              8) https://www.youtube.com/watch?v=II5YmGYIFJk
//              
//-----------------------------------------------------------------------------

#define VARREDURA    5              // Tempo de varredura em segundos
#define MAX_NOME     20             // Tamanho máximo do Nome da Estação
#define ALERTA       200            // Intervalo para piscar em caso de problema (msec)

// Definições para o ESP32-CAM ou XIAO ESP32S3

#define ESP32CAM                  // Descomente se for um ESP32-CAM
#define XIAO                      // Descomente se for um XIAO ESP32S3

//----------------------------
// Inclusão das bibliotecas
//----------------------------

#ifdef ESP8266
#include <ESP8266WiFi.h>            // Biblioteca WiFi para o ESP8266
#include <espnow.h>                 // Biblioteca ESP-NOW para o ESP8266
#include <RTCMemory.h>              // Biblioteca para ler/gravar RTC Memory
extern "C" {
  #include "user_interface.h" 
}
#else
#include <WiFi.h>                   // Biblioteca WiFi para o ESP32
#include <esp_now.h>                // Biblioteca ESP-NOW para o ESP32
#include <esp_wifi.h>               // Biblioteca WiFi especifica para ESP32
#endif

#include <DHT.h>                    // Biblioteca para o DHT11/DHT22

//------------------------
// Definições para o DHT
//------------------------

#ifdef ESP8266
#define NOME_ESTACAO "Estação-8266" // Nome desta Estação de coleta de Temp/Umid
#define DHTPin  14                  // Porta D5 = GPIO14 no ESP8266
#define DHTType DHT11               // Tipo DHT11
#define LED_BUILTIN  2              // Pino para o Led Interno do ESP32/ESP8266
#elif defined(ESP32CAM)
#define NOME_ESTACAO "Estação-CAM"  // Nome desta Estação de coleta de Temp/Umid
#define DHTPin  GPIO_NUM_13         // Porta GPIO13 no ESP32-CAM
#define DHTType DHT11               // Tipo DHT22
#define LED_BUILTIN  GPIO_NUM_33    // Pino para o Led Interno do ESP32-CAM
#elif defined(XIAO)
#define NOME_ESTACAO "Estação-XIAO" // Nome desta Estação de coleta de Temp/Umid
#define DHTPin  GPIO_NUM_8          // Porta D9 = GPIO8 no XIAO ESP32S3
#define DHTType DHT11               // Tipo DHT11
#define LED_BUILTIN  GPIO_NUM_21    // Pino para o Led Interno do XIAO
#else
#define NOME_ESTACAO "Estação-ESP"  // Nome desta Estação de coleta de Temp/Umid
#define DHTPin  4                   // Porta D4 = GPIO4 no ESP32
#define DHTType DHT22               // Tipo DHT22
#define LED_BUILTIN  2              // Pino para o Led Interno do ESP32/ESP8266
#endif
 
//-------------------------
// Definição do Objeto DHT
//-------------------------

DHT dht(DHTPin, DHTType);           // Instancia do Objeto DHT

//---------------------------
// Ajuste o SSID de sua Rede
//---------------------------

const char* ssid   = "<não esqueça de informr o SSID da sua Rede>";

//-----------------------------------------
// Definições para a Temperatura e Umidade
//-----------------------------------------

float temp;                         // Para leitura da Temperatura
float umid;                         // Para leitura da umidade

//--------------------------------------------------------
// Definição do Endereço do RECEPTOR
// Importante: Não esqueça de alterar a definição a seguir
//             para o MAC de seu Nó Central
//--------------------------------------------------------

uint8_t receptorAddress[] = {0x08, 0x3A, 0xF2, 0xB6, 0xA9, 0xE8};

//----------------------------------
// Variáveis de uso geral
//----------------------------------

int32_t channel;                       // Canal da rede Wi-Fi que será usado 

//----------------------------------------------
// Definição da Estrutura de Dados a Transmitir
//----------------------------------------------

typedef struct struct_mensagem 
{
  char nomeEstacao[MAX_NOME+1];     // Nome esta estação ou Nó da rede ESP-NOW 
  char macEstacao[18];              // MAC desta estação  ou Nó da rede ESP-NOW 
  float temp;                       // Temperatura lida
  float umid;                       // Umidade lida
} struct_mensagem;

//-------------------------------
// Definição da Área a Transmitir
//-------------------------------

struct_mensagem mensagem;           // Estrutura para ser usada na transmissão

//----------------------------------------------
// Definição da Estrutura do contador que será
// persistido na RTC Memory para evitar perda.
// Máximo tamanho de 508 bytes para ESP8266 ou
// 8K para o ESP32
//----------------------------------------------

typedef struct 
{
  uint_fast32_t valor;
} rtcDados;

//-------------------------------
// Definição da Área do Contador
//-------------------------------

#ifdef ESP8266
RTCMemory<rtcDados> contador_ciclos;    // Definição do objeto para tratar a RTC Memory
rtcDados *contador;                     // Área para receber os dados da RTC Memory no ESP8266
#else
RTC_DATA_ATTR uint_fast32_t contador=0; // Área para receber os dados da RTC Memory no ESP32
#endif

//--------------------------------------------
// Definição da Área para MAC do Destinatário 
//--------------------------------------------

#ifdef ESP32
esp_now_peer_info_t peerInfo = {};  // Endereço do destinatário já inicializado
#endif

//--------------------------------
// Definição do Callback do envio
//--------------------------------

#ifdef ESP8266
void OnDataSent(uint8_t *mac_addr, uint8_t status)
#else
void OnDataSent(const uint8_t *macAddr, esp_now_send_status_t status)
#endif
{
  Serial.print("Status do Último Envio: ");
  Serial.println(status == 0 ? "Sucesso" : "Falhou");
}

//---------------------------------------------
// Rotina para detectar o canal utilizado pela
// Rede Wi-fi cujo o SSID foi definido
//---------------------------------------------

int32_t getWiFiChannel(const char *ssid) 
{
  if (int32_t n = WiFi.scanNetworks()) 
  {
     for (uint8_t i=0; i<n; i++) 
     {
        if (!strcmp(ssid, WiFi.SSID(i).c_str())) 
        {
           return WiFi.channel(i);
        }
     }
  }
  return 0;
}

//---------------------------------------------
// Rotina para executar as funções ao acordar: 
// as inicializações, coleta e envio na rede 
// espnow antes de preparar o DEEP SLEEP MODE
//---------------------------------------------

bool init_coleta_envia()
{

   // Liga o LED interno para mostrar que está acordando
 
   pinMode(LED_BUILTIN, OUTPUT);
   digitalWrite(LED_BUILTIN,HIGH);

   // Inicialização da Serial
  
   Serial.begin(115200);
   while (!Serial);

   // Inicializa o objeto da RTC Memory para o ESP8266
   
#ifdef ESP8266
   if (contador_ciclos.begin()) 
   {
      Serial.println("Contador já existe previamente.");
   }
   else 
   {
      Serial.println("Buffer do Contador foi inicializado.");
   }
    
   // Recupera o contador da RTC Memory
  
    contador = contador_ciclos.getData();
#endif    
  
   // Inicializa o DHT11/DHT22
  
   dht.begin();

   // Inicializa a rede Wi-fi como Estação
  
   WiFi.mode(WIFI_STA);

   // Descobre qual o canal definido pelo roteador para a rede do SSID informado
  
   channel = getWiFiChannel(ssid);

   // Altera o canal na rede Wi-fi local

#ifdef ESP8266
   wifi_promiscuous_enable(true);
   wifi_set_channel(channel);
   wifi_promiscuous_enable(false); 
#else
   esp_wifi_set_promiscuous(true);
   esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
   esp_wifi_set_promiscuous(false);    
#endif

   // Inicializa a rede ESP-NOW
  
   if (esp_now_init() != 0) 
   {
      Serial.println("Erro ao inicializar a Rede ESP-NOW");
      return false; // Led interno ficará constantemente ligado para mostrar que houve problema 
   }
   else Serial.println("Rede ESP-NOW inicializada com sucesso");

   // Definição do Callback do envio

#ifdef ESP8266
   esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
#endif
  
   esp_now_register_send_cb(OnDataSent);

#ifdef ESP8266
   if (esp_now_add_peer(receptorAddress, ESP_NOW_ROLE_SLAVE, channel, NULL, 0 ) != 0)  
#else 
   memcpy(peerInfo.peer_addr, receptorAddress, 6);
   peerInfo.channel = channel;
   peerInfo.encrypt = false;
   if (esp_now_add_peer(&peerInfo) != 0) 
#endif
   {
      Serial.println("Erro ao adicionar o Destinatário");
      return false; // Led interno ficará constantemente ligado para mostrar que houve problema 
   }
   else Serial.println("Destinatário adicionado com sucesso");

   // Atualiza a parte fixa na área de transmissão
  
   strcpy(mensagem.nomeEstacao,NOME_ESTACAO);
   strcpy(mensagem.macEstacao,WiFi.macAddress().c_str());

   // Mostra o Nome desta Estação

   Serial.print("Nome da Estação : ");
   Serial.println(mensagem.nomeEstacao);

   // Mostra o MAC desta Estação

   Serial.print("MAC ADDRESS da Estação : ");
   Serial.println(mensagem.macEstacao);

   // Mostra o canal a ser utilizado

   Serial.print("Canal da Rede Wi-fi: ");
   Serial.println(channel);

   // Mostra LED_BUILTIN

   Serial.print("LED_BUILTIN: ");
   Serial.println(LED_BUILTIN);  

   // Lê os valores do DHT. Considera Zero em caso de erro.
  
   temp = dht.readTemperature();
   if (isnan(temp)) temp = 0;
   delay(10);
   umid = dht.readHumidity();
   if (isnan(umid)) umid = 0;
   delay(10);

   // Mostra os valores lidos na Console

   Serial.print("Wi-Fi Channel: ");
   Serial.println(WiFi.channel());
   Serial.print("Temperatura: ");
   Serial.print(temp,0);
   Serial.println("°C");
   Serial.print("Umidade: ");
   Serial.print(umid,0);
   Serial.println("%");

   // Mostra o contador recuperado da prévia execução

#ifdef ESP8266  
   Serial.printf("Valor do Contador recuperado: %d\n",contador->valor); 
#else
   Serial.printf("Valor do Contador recuperado: %d\n",contador);
#endif      

   // Atualiza o novo contador de ciclos, mostra na console e salva na RTC Menory

#ifdef ESP8266 
   Serial.printf("Valor do Contador atualizado: %d\n",++contador->valor);
   contador_ciclos.save();
#else
   Serial.printf("Valor do Contador atualizado: %d\n",++contador);
#endif   

   // Adiciona os valores lidos na estrutura de transmissão
  
   mensagem.temp = temp;
   mensagem.umid = umid;

   // Envia os dados
  
   esp_now_send(receptorAddress, (uint8_t *) &mensagem, sizeof(mensagem));   

   // Retorna true pois tudo deu certo

   return true;
   
}

//---------------------------------------------
// Rotina para executar as finalizações após o 
// envio e antes de iniciar o DEEP SLEEP MODE
//---------------------------------------------

void finalizacoes_apos_envio()
{
   // Faz as finalizações

   Serial.println("Entrando em hibernação...");

#ifdef ESP8266   
   WiFi.disconnect();
#else   
   esp_wifi_disconnect();
#endif   

   // Apaga o LED interno para mostrar que vai dormir
   
   digitalWrite(LED_BUILTIN,LOW);   
  
}

//--------------------------------------
// Rotina de Inicialização da Aplicação
//--------------------------------------

void setup() 
{
   // Faz as inicializações, coleta e envio

   if (!init_coleta_envia()) return; // Led interno ficará constantemente ligado para mostrar que houve problema 
   
   // Prepara o DEEP SLEEP para contradores ARCH ESP32 pois no ESP8266 é em um passo.

#ifdef ARDUINO_ARCH_ESP32
   Serial.println("ARDUINO_ARCH_ESP32");
   esp_sleep_enable_timer_wakeup(VARREDURA * 1000000); // tempo do timer é em microsegundos
#endif   

   // Faz as finalizações antes de entrar no DEEP SLEEP

   finalizacoes_apos_envio();

   // Entra no DEEP SLEEP

#ifdef ESP8266
  Serial.println("ARDUINO_ARCH_ESP8266");
  ESP.deepSleep(VARREDURA * 1000000); // tempo do timer é em microsegundos
#else   
   esp_deep_sleep_start(); 
#endif   
  
}

//-------------------------------------------------------------------------------------
// Loop ficará piscando o LED Interno pois a rotina setup() retornou, significando que 
// houve um problema na inicialização da Rede ESPNOW não sendo possível então cumprir 
// o objetivo principal que é a coleta dos dados e envio para o RECEPTOR da Rede ESPNOW. 
// Assim, visualmente será possível saber se o programa está funcionando.
// Resumo : 1) Piscando rápido: houve problema
//          2) Piscando lento : funcionando bem, acente a cada envio e desliga.
//-------------------------------------------------------------------------------------

void loop() 
{

   // Pisca o LED rapidamente

   digitalWrite(LED_BUILTIN,HIGH);   
   delay(ALERTA);
   digitalWrite(LED_BUILTIN,LOW);
   delay(ALERTA);

}

 


Conclusão

O protocolo ESP-NOW é um excelente recurso para implementação de diversos cenários de comunicação entre microcontroladores ESP32 e ESP8266, possuindo alcance, velocidade, independência de Internet ativa e simplicidade na implementação. Possui algumas limitações, como por exemplo, o máximo tamanho do pacote é 250 bytes e o número máximo de nós é de 20, mas tais limitações podem ser flexibilizadas para uma gama variada de aplicações possíveis. Adicionalmente, os recursos como Async Web Server, DEEP SLEEP MODE e RTC MEMORY podem ampliar o espectro de aplicações. Até a próxima!


Sobre o autor


Alberto de Almeida Menezes
tinho.menezes@gmail.com

Bacharel em Engenharia de Áudio e Produção Musical pela Berklee College of Music.


Dailton de Oliveira Menezes
dailton.menezes@gmail.com

Bacharel em Ciência da Computação pela Universidade Federal de Minas Gerais.


Eletrogate

20 de junho de 2024

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!