Introduction: ESP-NOW: WiFi With 3x More Range

About: Do you like technology? Follow my channel on Youtube and my Blog. In them I put videos every week of microcontrollers, arduinos, networks, among other subjects.

When using the ESP-NOW protocol for bridging between multiple ESP32s, our WiFi has up to 3 times more range (480 meters total). We achieved this 2.4GHz wireless network with the built-in antennas of the ESP32. In our project today, therefore, an ESP32 will read a pin and send the value to another ESP32. This other ESP32 will receive this value, change the output of the pin, and send this value to an upcoming ESP32. The next ESP32 will do the same thing and go over to the next ESP32. In short, I set up a remote control with two bridges in the middle.

In this project, we created software that can send the values that we select to countless ESP32s. Our example uses four chips, but you can use as many as you want, as this protocol is very fast. I want to emphasize that this scheme is not for linking the Internet. Instead, it’s for automation, which is the sending of control bits. In addition, this protocol is much faster than LoRa.

Step 1: ESP32 Pinout

Step 2: Communication

As shown in the picture:

1st: Master takes the pin and sends it.

2nd: The next ESP32, Slave 1, receives and changes the value of the pin, and sends it to the next.

3rd: The next ESP32, Slave 2, receives and changes the value of the pin, and sends it to the next.

4th: Finally, the room ESP32, Slave 3, receives and changes the value of the pin.

Step 3: Esp32EspNowSendReceive.ino

First, we will include the ESP-NOW and WiFi libraries. Let's also set pin 2 to read and send the value (as well as channel 1) for the connection. I emphasize that if Master is set, Master.ino will be compiled. Otherwise, Slave.ino will be compiled. Finally, I create an object that is the peer, which will save the address.

//Libs do espnow e wifi
#include <esp_now.h> #include <WiFi.h> //Pino utilizado para leitura //e que será enviado o valor #define PIN 2 //Canal usado para conexão #define CHANNEL 1 //Se MASTER estiver definido //o compilador irá compilar o Master.ino //Se quiser compilar o Slave.ino remova ou //comente a linha abaixo #define MASTER //Estrutura com informações //sobre o próximo peer esp_now_peer_info_t peer;

Step 4: Esp32EspNowSendReceive.ino - ModeStation

Here we have the function that initializes the station mode. I have explained a lot about this in ESP32 with ESP-NOW protocol. Also in this step, we show in the serial monitor the Mac Address of the ESP when in station mode.

//Função para inicializar o modo station
void modeStation(){ //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()); }

Step 5: Esp32EspNowSendReceive.ino - InitESPNow

We deal with this part of the code that concerns the ESP-NOW startup function.

//Função de inicialização do ESPNow
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(); } }

Step 6: Esp32EspNowSendReceive.ino - AddPeer

Through the MAC address, we added a new peer here. In addition, we inform the channel and also add the slave.

//Função que adiciona um novo peer
//através de seu endereço MAC void addPeer(uint8_t *peerMacAddress){ //Informamos o canal peer.channel = CHANNEL; //0 para não usar criptografia ou 1 para usar peer.encrypt = 0; //Copia o endereço do array para a estrutura memcpy(peer.peer_addr, peerMacAddress, 6); //Adiciona o slave esp_now_add_peer(&peer); }

Step 7: Esp32EspNowSendReceive.ino - Send

We have in this part the function that will send the value to the peer that has the MAC Address specified. We work with the possibilities of sending successfully and with errors.

//Função que irá enviar o valor para
//o peer que tenha o mac address especificado void send(const uint8_t *value, uint8_t *peerMacAddress){ esp_err_t result = esp_now_send(peerMacAddress, value, sizeof(value)); 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"); } }

Step 8: Master.ino

Here is the #ifdef MASTER, which will only compile if the Master is set in the build directive. We include in this part the MAC Address to which we will send the data.

//Apenas irá compilar se MASTER estiver definido
#ifdef MASTER //Mac Address do peer para o qual iremos enviar os dados uint8_t peerMacAddress[] = {0x24, 0x0A, 0xC4, 0x0E, 0x3F, 0xD0};

Step 9: Master.ino - Setup

Call the functions of initialize station mode and ESP-NOW, add the peer, register the callback, and execute the onDataSent function. Finally, we put the pin in read mode, which is performed and then the data is sent.

void setup() {
Serial.begin(115200); //Chama a função que inicializa o modo station modeStation(); //Chama a função que inicializa o ESPNow InitESPNow(); //Adiciona o peer addPeer(peerMacAddress); //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); //Colocamos o pino em modo de leitura pinMode(PIN, INPUT); //Lê o valor do pino e envia readAndSend(); }

Step 10: Master.ino - ReadAndSend

Here we have the readAndSend, which is the function responsible for reading the pin and sending the value to the peer.

//Função responsável pela
//leitura do pino e envio //do valor para o peer void readAndSend(){ //Lê o valor do pino uint8_t value = digitalRead(PIN); //Envia o valor para o peer send(&value, peerMacAddress); }

Step 11: Master.ino - OnDataSent

Already OnDataSent is the function that serves as a callback to warn us about the shipping situation we have made. It is important to remember that when we receive the result of the last shipment, we can read and send again.

//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) { Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Fail"); //Quando recebemos o resultado do último envio //já podemos ler e enviar novamente readAndSend(); }

Step 12: Master.ino - Loop

No action is necessary in the Loop, because whenever we receive feedback from the Send via the OnDataSent function, we send the data again, causing the data to always be sent in sequence.

//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() { } #endif

Step 13: Slave.ino

Starting the Slave code, we highlight that it will only compile if Master is not defined. In the case of compiling as Slave, we then have the Mac Address of the peer to which we will send the data.

//Apenas irá compilar se MASTER não estiver definido
#ifndef MASTER //Mac Address do peer para o qual iremos enviar os dados uint8_t peerMacAddress[6] = {0x24, 0x0A, 0xC4, 0x0E, 0x46, 0xCC};

Step 14: Slave.ino - Setup

In the Slave Setup, again we will call the functions to initialize station mode and ESP-NOW, add the peer, and register the callback. The novelty is that, this time, we have two callbacks. So let's run the onDataRecv function, which will inform you that we have received the data, as well as the onDataSent function, which will inform you about the status of sending data. Finally, we put the pin in output mode.

void setup() {
Serial.begin(115200); //Chama a função que inicializa o modo station modeStation(); //Chama a função que inicializa o ESPNow InitESPNow(); //Adiciona o peer addPeer(peerMacAddress); //Registra o callback que nos informará //que recebemos dados. //A função que será executada //é onDataRecv e está declarada mais abaixo esp_now_register_recv_cb(onDataRecv); //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); //Colocamos o pino como saída pinMode(PIN, OUTPUT); }

Step 15: Slave.ino - OnDataRecv

This function onDataRecv serves as a callback, and every time there is a send, it will call the send (value, peerMacAddress). This is for the send to read the value to the next ESP.

//Função que serve de callback para nos avisar
//que recebemos dados void onDataRecv(const uint8_t *mac_addr, const uint8_t *value, int len) { //Coloca o valor recebido na saída do pino digitalWrite(PIN, *value); //Envia o valor lido para o próximo esp //Se este for o último, comente esta linha antes de compilar send(value, peerMacAddress); }

Step 16: Slave.ino - OnDataSent

Already, this function serves as a callback to let us know about the shipping situation we have made.

//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) { Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Fail"); }

Step 17: Slave.ino - Loop

Again, we do not need to do anything in the Loop, because we send the data as soon as we receive it from the other ESPs by the callback.

//Não precisamos fazer nada no loop
//nós enviamos os dados assim que //recebemos do outro esp pelo callback void loop() { } #endif

Step 18: Files

Download the files:

PDF

INO