ESP32 com protocolo ESP-Now



Uma rede muito especial, de alta velocidade e, sendo assim, perfeita para a automação residencial e industrial, e que se trata de mais um protocolo desenvolvido pela Espressif, este é o ESP-Now. É deste mecanismo, que permite que vários dispositivos se comuniquem sem utilizar uma rede WiFi feita por um roteador, que vamos tratar hoje. Vou mostrar uma introdução sobre o assunto e fazer vários ESPs32 se comunicarem através deste esquema. Sendo assim, um ESP32 vai fazer a leitura dos pinos e transmitir seus valores, enquanto os demais dispositivos vão receber estes valores e alterar a saída dos pinos de acordo com tais números.


Esta rede é confiável, sem deixar de mencionar que é 2.4GHz. Ela ainda é aquela rede com a mesma frequência e os mesmo canais do teu roteador WiFi. O destaque, no entanto, é que ela passa longe do WiFi, pois ela é instantânea.


ESP32 Pinout

Deixo aqui a pinagem do ESP32.


Sobre o ESP-Now

·         Protocolo de comunicação criado pela Espressif.
·         Não necessita de uma rede WiFi.
·         Similar ao protocolo de baixo consumo utilizado em mouse sem fio de 2.4GHz.
·         Necessário pareamento inicial.
·         Após o pareamento a conexão é persistente peer-to-peer.

Exemplo do tutorial


1.       Master faz a leitura dos pinos.
2.       Broadcast dos valores lidos.
3.       Slaves alteram a saída dos seus pinos para ficar igual aos valores recebidos do Master.

Demonstração



Na nossa montagem temos um ESP32 isolado, que está configurado como Master. Lembrando que, na verdade, não existe um dispositivo Master, de fato, mas todos funcionam como Stations. No entanto, para facilitar a identificação, aponto esse primeiro ESP como Master, no qual configurei um botãozinho no GPIO02. Quando este botão é apertado, um Led acende neste microcontrolador, ação repedida instantaneamente por todos os outros quatro ESPs32. Por que isso ocorre? Porque no momento que o Master envia a informação para a estação, ele está mandando um MAC address de Broadcast, o que significa que todos da rede recebem os dados ao mesmo tempo.
Neste exemplo, compilei o receptor igual para todos os microcontroladores. Copiei o código que envia para o Master e este manda o Broadcast para os demais. Neste esquema ainda é possível ver que praticamente não temos tempo de Boot, pois ao desligar e religar o ESP o funcionamento é retomado imediatamente.
No print serial do Setup, tanto do código que envia quanto do que recebe, constam os valores do MAC address de cada um dos chips envolvidos. Veja no exemplo abaixo:



Distância

Vamos mostrar aqui que nos testes que realizamos com os ESPs32, estes conseguiram se comunicar por até 165,47 metros de distância, em linha reta, isso com o uso somente das antenas internas dos dispositivos. Veja no mapa.


Código

ESPNowMaster.ino

Aqui vamos atuar com as bibliotecas ESP_NOW.h e WiFi.h. Vamos definir o canal para conexão e os pinos que serão lidos, bem como os dados que serão enviados aos Slaves. Destacando que é importante que o código fonte dos Slaves tenha este mesmo array com os mesmos GPIOs. Na parte do Setup vamos calcular a quantidade de pinos e colocar nesta variável, assim não precisaremos trocar toda vez que mudarmos a quantidade de pinos. Ainda trabalhamos o MAC Address dos slaves para os quais iremos enviar a leitura.

//Libs do espnow e wifi
#include <esp_now.h>
#include <WiFi.h>

//Canal usado para conexão
#define CHANNEL 1

//Pinos que iremos ler (digitalRead) e enviar para os Slaves
//É importante que o código fonte dos Slaves tenha este mesmo array com os mesmos gpios
//na mesma ordem
uint8_t gpios[] = {23, 2};

//No setup iremos calcular a quantidade de pinos e colocar nesta variável,
//assim não precisamos trocar aqui toda vez que mudarmos a quantidade de pinos,
//tudo é calculado no setup
int gpioCount;

//Mac Address dos slaves para os quais iremos enviar a leitura
//Se quiser enviar para todos os Slaves utilize apenas o endereço de broadcast {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}.
//Se quiser enviar para ESPs específicos coloque o Mac Address (obtido através da função WiFi.macAddress())
uint8_t macSlaves[][6] = {
  //Se for enviar para ESPs específicos, coloque cada endereço separado por vírgula
  // {0x24, 0x0A, 0xC4, 0x0E, 0x3F, 0xD1}, {0x24, 0x0A, 0xC4, 0x0E, 0x4E, 0xC3}
  //Se for enviar para todos, apenas deixe o endereço de broadcast {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
  {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
};

ESPNowMaster.ino - Setup

Nesta parte vamos tratar do Master, lembrando que temos um Master e quatro Slaves. Vamos calcular o tamanho do array de GPIOs que serão lidos com o digitalRead. Ainda iremos colocar o ESP em modo station, mostrar no Monitor Serial o MAC Address deste ESP quando em modo station e chamar a função que inicializa o ESPNow.

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

  //Cálculo do tamanho do array de gpios que serão lidos com o digitalRead
  //sizeof(gpios) retorna a quantidade de bytes que o array gpios aponta
  //Sabemos que todos os elementos do array são do tipo uint8_t
  //sizeof(uint8_t) retorna a quantidade de bytes que o tipo uint8_t possui
  //Sendo assim para saber quantos elementos o array possui
  //fazemos a divisão entre a quantidade total de bytes do array e quantos
  //bytes cada elemento possui
  gpioCount = sizeof(gpios)/sizeof(uint8_t);

  //Colocamos o ESP em modo station
  WiFi.mode(WIFI_STA);

  //Mostramos no Monitor Serial o Mac Address deste ESP quando em modo station
  Serial.print("Mac Address in Station: "); 
  Serial.println(WiFi.macAddress());

  //Chama a função que inicializa o ESPNow
  InitESPNow();

Calculamos também o tamanho do array com os MAC address dos slaves e criamos uma variável que irá guardar as informações de cada slave. Informamos o canal e adicionamos o slave.

  //Cálculo do tamanho do array com os mac address dos slaves
  //sizeof(macSlaves) retorna a quantidade de bytes que o array macSlaves aponta
  //Sabemos que cada mac address é um array de 6 posições e
  //cada posição possui sizeof(uint8_t) bytes, então
  //a quantidade de slaves é a divisão da quantidade de bytes
  //total do array pela quantidade de posições e o resultado
  //dessa divisão dividimos novamente por quantos bytes cada posição possui
  int slavesCount = sizeof(macSlaves)/6/sizeof(uint8_t);

  //Para cada slave
  for(int i=0; i<slavesCount; i++){
    //Criamos uma variável que irá guardar as informações do slave
    esp_now_peer_info_t slave;
    //Informamos o canal
    slave.channel = CHANNEL;
    //0 para não usar criptografia ou 1 para usar
    slave.encrypt = 0;
    //Copia o endereço do array para a estrutura
    memcpy(slave.peer_addr, macSlaves[i], sizeof(macSlaves[i]));
    //Adiciona o slave
    esp_now_add_peer(&slave);
  }

Nesta etapa, registramos o callback que nos informará sobre o status do envio. A função que será executada é OnDataSent e está declarada abaixo. Colocamos o slave em modo de leitura e chamamos a função send, que trata do envio.

//Registra o callback que nos informará sobre o status do envio
  //A função que será executada é OnDataSent e está declarada mais abaixo
  esp_now_register_send_cb(OnDataSent);
  
  //Para cada pino que está no array gpios
  for(int i=0; i<gpioCount; i++){
    //Colocamos em modo de leitura
    pinMode(gpios[i], INPUT);
  }

  //Chama a função send
  send();
}

ESPNowMaster.ino – InitESPNow

A função InitESPNow é simples e trabalha aqui com as possibilidades de inicialização bem-sucedida, bem como de erro no momento da inicialização.

void InitESPNow() {
  //Se a inicialização foi bem sucedida
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  //Se houve erro na inicialização
  else {
    Serial.println("ESPNow Init Failed");
    ESP.restart();
  }
}

ESPNowMaster.ino - send

Temos aqui a função que irá fazer a leitura dos pinos que estão no array GPIOs e enviar os valores lidos para os outros ESPs. Esse array que irá armazenar os valores lidos.

//Função que irá fazer a leitura dos pinos
//que estão no array gpios e enviar os valores
//lidos para os outros ESPs
void send(){
  //Array que irá armazenar os valores lidos
  uint8_t values[gpioCount];

  //Para cada pino
  for(int i=0; i<gpioCount; i++){
    //Lê o estado do pino e armazena no array
    values[i] = digitalRead(gpios[i]);
  }

O endereço de broadcast irá enviar as informações para todos os ESPs. Se quiser que a informação vá para ESPs específicos você deve chamar a função esp_now_send para cada MAC Address, passando esse MAC Address como primeiro parâmetro no lugar do Broadcast.

 //O endereço de broadcast irá enviar as informações para todos os ESPs
  //Se quiser que a informação vá para ESPs específicos você deve chamar a função
  //esp_now_send para cada Mac Address, passando o Mac Address como primeiro
  //parâmetro no lugar do broadcast
  uint8_t broadcast[] = {0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF};
  esp_err_t result = esp_now_send(broadcast, (uint8_t*) &values, sizeof(values));
  Serial.print("Send Status: ");
  //Se o envio foi bem sucedido
  if (result == ESP_OK) {
    Serial.println("Success");
  }
  //Se aconteceu algum erro no envio
  else {
    Serial.println("Error");
  }
}

ESPNowMaster.ino – OnDataSent

Vamos agora definir a função que serve de callback para nos avisar sobre a situação do envio que fizermos. Copiamos o MAC Address destino para uma string e mostramos o MAC Address que foi destino da mensagem. Exibimos ainda se o status do envio foi bem sucedido ou não.

//Função que serve de callback para nos avisar
//sobre a situação do envio que fizemos
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  char macStr[18];
  //Copiamos o Mac Address destino para uma string
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  //Mostramos o Mac Address que foi destino da mensagem
  Serial.print("Sent to: "); 
  Serial.println(macStr);
  //Mostramos se o status do envio foi bem sucedido ou não
  Serial.print("Status: "); 
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Fail");
  //Enviamos novamente os dados
  send();
}

SPNowMaster.ino – Loop

Não precisamos fazer nada no Loop, pois sempre que recebemos o feedback do envio através da função OnDataSent nós enviamos os dados novamente, fazendo com que os dados estejam sempre sendo enviados em sequência.

//Não precisamos fazer nada no loop
//pois sempre que recebemos o feedback
//do envio através da função OnDataSent
//nós enviamos os dados novamente,
//fazendo com que os dados estejam sempre
//sendo enviados em sequência
void loop() {
}

ESPNowSlave.ino

Agora partimos para os Slaves. Atuamos com as bibliotecas ESP_NOW.h e WiFi.h. Os pinos que iremos escrever (digitalWrite) terão os valores recebidos do Master. Lembrando que é importante que o código fonte do Master tenha este mesmo array com os mesmos GPIOs na mesma ordem. No setup iremos calcular a quantidade de pinos e colocar nesta variável. Assim, não precisamos trocar aqui toda vez que mudarmos a quantidade de pinos, pois tudo é calculado no setup.

//Libs do espnow e wifi
#include <esp_now.h>
#include <WiFi.h>

//Pinos que iremos escrever (digitalWrite) cujos valores são recebios do Master
//É importante que o código fonte do Master tenha este mesmo array com os mesmos gpios
//na mesma ordem
uint8_t gpios[] = {23, 2};

//No setup iremos calcular a quantidade de pinos e colocar nesta variável,
//assim não precisamos trocar aqui toda vez que mudarmos a quantidade de pinos,
//tudo é calculado no setup
int gpioCount;

ESPNowSlave.ino - Setup

Temos nesta etapa o cálculo do tamanho do array de GPIOs. Colocamos o ESP em modo station e mostramos no Monitor Serial o Mac Address deste ESP quando em modo station. Se quiser que o Master mande para ESPs em específico, altere no array de slaves (no código fonte do Master) para que ele possua apenas os Mac Addresses printados aqui. Por fim, chamamos a função que inicializa o ESPNow.

void setup() {
  Serial.begin(115200);
  
  //Cálculo do tamanho do array de gpios
  //sizeof(gpios) retorna a quantidade de bytes que o array gpios aponta
  //Sabemos que todos os elementos do array são do tipo uint8_t
  //sizeof(uint8_t) retorna a quantidade de bytes que o tipo uint8_t possui
  //Sendo assim para saber quantos elementos o array possui
  //fazemos a divisão entre a quantidade total de bytes do array e quantos
  //bytes cada elemento possui
  gpioCount = sizeof(gpios)/sizeof(uint8_t);

  //Colocamos o ESP em modo station
  WiFi.mode(WIFI_STA);
  
  //Mostramos no Monitor Serial o Mac Address deste ESP quando em modo station
  //Se quiser que o Master mande para ESPs em específico, altere no 
  //array de slaves (no código fonte do Master) para que ele possua apenas os Mac Addresses printados aqui
  Serial.print("Mac Address in Station: "); 
  Serial.println(WiFi.macAddress());

  //Chama a função que inicializa o ESPNow
  InitESPNow();

Registramos o callback que nos informará quando o Master enviou algo. A função que será executada é OnDataRecv e está declarada abaixo. Colocamos em modo de output.

 //Registra o callback que nos informará quando o Master enviou algo
  //A função que será executada é OnDataRecv e está declarada mais abaixo
  esp_now_register_recv_cb(OnDataRecv);

  //Para cada pino que está no array gpios
  for(int i=0; i<gpioCount; i++){
    //Colocamos em modo de output
    pinMode(gpios[i], OUTPUT);
  }
}

ESPNowSlave.ino – InitESPNow

Mais uma vez trabalhamos com a função InitESPNow, desta vez no Slave. Novamente teremos duas as possibilidades: de inicialização bem-sucedida ou de erro no momento da inicialização.

void InitESPNow() {
  //Se a inicialização foi bem sucedida
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  //Se houve erro na inicialização
  else {
    Serial.println("ESPNow Init Failed");
    ESP.restart();
  }
}

ESPNowSlave.ino – OnDataRecv

Retomamos também a função que serve de callback, aqui no Slave para nos avisar quando chegou algo do Master. Copiamos o MAC Address origem para uma string e mostramos o MAC Address que foi a origem da mensagem. Para cada pino colocamos o valor recebido do Master na saída do respectivo.

//Função que serve de callback para nos avisar
//quando chegou algo do Master
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
  char macStr[18];
  //Copiamos o Mac Address origem para uma string
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  //Mostramos o Mac Address que foi a origem da mensagem
  Serial.print("Received from: "); 
  Serial.println(macStr);
  Serial.println("");

  //Para cada pino
  for(int i=0; i<gpioCount; i++){
    //Colocamos o valor recebido do Master na saída do respectivo pino
    digitalWrite(gpios[i], data[i]);
  }
}

ESPNowSlave.ino – Loop

Mais uma vez, não precisamos fazer nada no Loop. Sempre que chegar algo do Master, a função OnDataRecv é executada automaticamente, já que adicionamos como callback utilizando a função esp_now_register_recv_cb.

//Não precisamos fazer nada no loop
//Sempre que chegar algo do Master
//a função OnDataRecv é executada automaticamente
//já que adicionamos como callback utilizando a função
//esp_now_register_recv_cb
void loop() {
}


Faça o download dos arquivos


21 comentários:

  1. Vou baixar esse material, muito didático, sei como dá trabalho montar isso tudo pra ficar tão bem organizado. Parabéns!!

    ResponderExcluir
  2. Boa noite . Seus vídeos são ótimos. Aprendi muito com eles.
    Estou atualmente trabalhando com ESP-TOUCH uma função "na minha opinião" melhor que wi-fi manager , quando tiver um tempo dá uma olhada no ESP-TOUCH , ficaria muito bom um vídeo seu sobre essa função de conectar wi-fi do esp32.

    Obrigado pela atenção e por fazer vídeos aulas muito bons .

    ResponderExcluir
  3. Muito bom seu trabalho, parabéns, acredito que da possibilidades de fazer monitoração de sensores em uma rede fechada e monitora-la com cel via wi-fi.
    Abraço.

    ResponderExcluir
  4. Muito bom seu trabalho, parabéns! Seria possível usar o ESP NOW para comunicar entre os ESP's e o MASTER comunicar com a rede externa para exibir valores de sensores e receber comandos de um aplicativo?

    ResponderExcluir
  5. Boa tarde Fernando , acompanhando as suas aulas sobre os protocolos esp-now e lorawan. Qual destes você indicaria para fazer controle de acesso em quartos de hotel que possui 3 andares. Obrigado.

    ResponderExcluir
  6. Amazing, but i wanted to know if this protocol work with others models of esp.

    ResponderExcluir
  7. Boa tarde Fernando! Quando copio o código fonte para a IDE Arduino, a Library não está instalada, como faço para instalar a mesma?
    Abraço!

    ResponderExcluir
    Respostas
    1. Olá, Dario. Qual delas não está instalada?
      O que exatamente mostra?

      Excluir
    2. Boa noite Fernando! Fiz a reinstalação dos drivers e agora está funcionando corretamente. Obrigado pela atenção!

      Excluir
  8. Parabéns, Fernando...seus vídeos são excelentes, me divirto muito com sua linguagem...
    Uma pergunta: é possível usar a rede esp-now e o wifi em paralelo?
    Um forte abraço,

    ResponderExcluir
    Respostas
    1. Olá, Ailson. Ainda estou checando essa possibilidade. Abraço

      Excluir
  9. Olá Fernando, poderia me ajudar?! Estou com problema em incluir a biblioteca esp_now.h
    Arduino IDE me retorna o seguinte erro:

    fatal error: esp_now.h: No such file or directory
    #include


    Como instalar a mesma, pois não a encontro no gerenciador de bibliotecas da baixar.

    ResponderExcluir
  10. Olá, bom dia, onde posso encontrar a lib esp_now.h?
    Ao executar o seu código de exemplo retorna a seguinte mensagem:

    /home/lucas/Área de Trabalho/ESP_NOW Teste/EspNowMaster/EspNowMaster.ino:2:21: fatal error: esp_now.h: No such file or directory
    #include
    ^
    compilation terminated.
    exit status 1
    Erro compilando para a placa NodeMCU 1.0 (ESP-12E Module)

    Desde já, agradeço, gosto muito do seu conteúdo sobre ESP.

    ResponderExcluir
    Respostas
    1. Same problem with me. What is the solution? Ahmedriazku@yahoo.com

      Excluir
  11. TEM COMO FAZER AO CONTRARIO VARIOS ESP32 MANDADO SINAL PARA UM ESP E ELE FAZER UM COMANDO RELATIVO A QUAL ESPP ESTA MANDANDO

    ResponderExcluir
  12. Boa noite fernando, é possivel fazer a comunicação com esp.now entre um esp32 como master e um esp8266 como slave?

    ResponderExcluir
  13. Boa tarde Fernando.
    Tentei utilizar estes códigos para teste em um projeto que estou fazendo mas no esp que defini como mestre aparece a seguinte mensagem:

    E (277) ESPNOW: Peer interface is invalid
    Send Status: Error

    ResponderExcluir
    Respostas
    1. Olá, eu tive o mesmo problema e neste fim de semana consegui ter tempo de fuçar...
      Encontre a linha 'esp_now_peer_info_t slave;'
      e coloque ela antes de setup() e loop() para que a variável slave se torne 'publica'.
      Em todos os exemplos da Espressif essa variável está posicionada como pública.
      A princípio não vi motivo para não usar da forma que o Fernando usou mas enfim, fazendo isso parou de dar o erro que vc menciona. Abs

      Excluir
  14. Olá professor!

    É minha primeira experiencia com a ESP32, acompanho seu canal e acho espetacular (tanto é que comprei 2 ESPs para testar).

    Estou com a ultima versão do Arduino IDE 1.8.5 e fiz a instalação limpa da interface da expressif p ESP32 diretamente da github conforme seu vídeo (excelente)...

    Preciso de sua ajuda pois neste teste estou implementando o projeto com duas ESP32 Wemos (Com suporte p bateria, acho que vc tem uma delas ai rs).
    O Código da Slave funciona sem erros, porem o da master retorna "E (290) ESPNOW: Peer interface is invalid"... Já testei invertendo a função das placas, porem o erro permanece na que esta como Master...

    Minha ideia é transmitir sinais de 2 Joysticks resistivos e outros botoes digitais para uma plataforma robótica esteira alem de receber desta plataforma dados coletados pelos sensores, exibindo em um display que ficara no controle.

    ResponderExcluir
  15. Boa noite, Fernando eu consigo usar o esp8266 ou mesmo o ESP32 para gerenciar iam conexão Modbus TCP/IP?

    ResponderExcluir
  16. Boa noite, o EspNow roda tbm no 8266?
    Estes códigos rodam no 8266?

    Obrigado

    ResponderExcluir

Tecnologia do Blogger.