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/
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.
Antes de iniciar a programação, é preciso fazer a instalação das placas da Espressif e das seguintes bibliotecas:
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.
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ó:
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:
//----------------------------------------------------------------------------- // 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); }
//----------------------------------------------------------------------------- // 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(); } }
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(); } }
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);
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); }
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!
|
|
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!