[EN] ESP-NOW

The article discusses the use of communication developed by espressif to communicate between its microcontrollers via wireless communication, as an alternative to the development of a non-linear  Client/Server system. It describes the working process of working as a service provider, commander, and a set of related instructions through the Arduino core, along with explaining the working examples that come with both Arduino cores, which are basic and Multi-slave, which can be applied variously.

ESP-NOW

ESP-NOW is a communication protocol designed by espressif, the manufacturer of the esp8266 and esp32 microcontrollers for communication between devices via wireless or WiFi. It supports encrypted and non-encrypted communications, transmits/receives up to 250 bytes per frame, and callback for receiving notification of transmission results. However, the ESP-NEW is limited by 3 issues:

  • When using encryption in communication, only 10 devices are available in Station mode and only 6 devices are available in SoftAP or SoftAP+Station mode.
  • Decrypted communication is compatible with up to 20 devices.
  • The data in the send/receive frame or payload portion cannot exceed 250 bytes.

The communication architecture of ESP-NOW requires that each communication band have a node known as a master or controller and a slave or peer. There are two types of communication:

  • One-way communication in which one device is the sender and the other is the receiver. It works in three ways:
    • 1 sender and 1 receiver (one to one)
    • 1 sender, multiple receivers (one master, multiple slaves)
    • Multiple senders and one receiver (multiple masters, one slave)
  • Two-way communication where both master and slave can be both sender and receiver.

Instruction set

Here are some known commands and constants.

Error code

This residual constant is the result returned of type esp_err_t , which is a data type returned from an ESP-NOW-related routine.

ErrorDescription
ESP_ERR_ESPNOW_NOT_INITThe programmer has not yet called esp_now_init()
ESP_ERR_ESPNOW_ARGThe argument passed to the command has an error.
ESP_ERR_ESPNOW_NO_MEMInsufficient memory.
ESP_ERR_ESPNOW_FULLThe number of peers in the full list.
ESP_ERR_ESPNOW_NOT_FOUNDESP-NOW Peer not found.
ESP_ERR_ESPNOW_INTERNALAn internal error has occurred.
ESP_ERR_ESPNOW_EXISTAlready have this Peer.
ESP_ERR_ESPNOW_IFAlready have this PeerAn interface error has occurred.

If the command is executed successfully or the send/receive is successful, it returns ESP_OK

Constants inside ESP-NOW

Here is a list of reference constants regarding data amount, key size, and peer count.

  • ESP_NOW_ETH_ALEN A value of 6 is the size of the peer’s MAC address.
  • ESP_NOW_KEY_LEN A value of 16 is the size of the Peer master key.
  • ESP_NOW_MAX_TOTAL_PEER_NUM A value of 20 is the maximum number of peers.
  • ESP_NOW_MAX_ENCRYPT_PEER_NUM A value of 6 is the maximum number of Peers when communicating encrypted.
  • ESP_NOW_MAX_DATA_LEN A value of 250 is the maximum number of bytes of data used for communication per frame.

Peer information

The data structure for storing details of each Peer is named esp_now_peer_info or esp_now_peer_info_t. which contains the following information:

typedef struct esp_now_peer_info {
    uint8_t peer_addr[ESP_NOW_ETH_ALEN];     
    uint8_t lmk[ESP_NOW_KEY_LEN];            
    uint8_t channel;                         
    wifi_interface_t ifidx;  
    bool encrypt;
    void *priv; 
} esp_now_peer_info_t;

From such a structure, it can be seen that

  1. peer_addr is the MAC address of Peer which is used in Station or SoftAP mode.
  2. lmk is the size of the original key (master key) used to encrypt and decrypt data.
  3. channel is the number of communication channels between each other but if set to 0 means using the communication channel by Station or SoftAP communicates with for another case, the channel number must be specified to match the station or SoftAP communicating.
  4. ifidx is the numeric value of an interface or interface (IF: Interface).
  5. encrypt: If true means communication with peers is encrypted, if false it is non-encrypted communication.
  6. priv is a pointer variable that points to the peer’s private data.

Start command

esp_err_t esp_now_init(void);

This command is used to initialize ESP-NOW and restore.

  • ESP_OK if successful
  • ESP_ERR_ESPNOW_INTERNAL

esp_err_t esp_now_deinit(void);

This command is used to terminate ESP-NOW and will return ESP_OK after the abort is complete.

Command to see the version of ESP-NOW

esp_err_t esp_now_get_version(uint32_t *version);

This command is used to read the version number of ESP-NOW, it must pass a variable of type uint32_t and store the referenced version in memory. Then return the value to ESP_OK or ESP_ERR_ESPNOW_ARG, if there is a problem asking for arguments passed to a function.

callback

The form of the callback used to be called when ESP-NOW receives data must have a function definition structure:

typedef void (*esp_now_recv_cb_t)(const uint8_t *mac_addr, const uint8_t *data, int data_len);

The form of the callback function to be executed when a transmission error occurs must be structured as follows:

typedef void (*esp_now_send_cb_t)(const uint8_t *mac_addr, esp_now_send_status_t status);

The command to define the callback function when the register send event occurs is as follows:

esp_err_t esp_now_register_send_cb(esp_now_send_cb_t cb);

From the command, cb is the name of the callback function written to be called and the return has the following effect:

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT
  • ESP_ERR_ESPNOW_INTERNAL

The command for defining the callback function to be executed when receiving data has the following format:

esp_err_t esp_now_register_recv_cb(esp_now_recv_cb_t cb);

The return value from the commands are

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT
  • ESP_ERR_ESPNOW_INTERNAL

The command for unregistering the callback function that is called after data acquisition has occurred in the following format:

esp_err_t esp_now_unregister_recv_cb(void);

The return value from the commands are

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT

The command to unregister a callback function in response to transmission has the following form:

esp_err_t esp_now_unregister_send_cb(void);

The return value from the command is

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT

Data transmission command

The command to send data len size to peer with position value peer_addr has the following format:

esp_err_t esp_now_send(const uint8_t *peer_addr, const uint8_t *data, size_t len);

The return value is

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT
  • ESP_ERR_ESPNOW_ARG
  • ESP_ERR_ESPNOW_INTERNAL
  • ESP_ERR_ESPNOW_NO_MEM
  • ESP_ERR_ESPNOW_NOT_FOUND
  • ESP_ERR_ESPNOW_IF

Peer command

The command for adding peers to the system has the following form:

esp_err_t esp_now_add_peer(const esp_now_peer_info_t *peer);

The return value of the peer add function are:

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT
  • ESP_ERR_ESPNOW_ARG
  • ESP_ERR_ESPNOW_FULL
  • ESP_ERR_ESPNOW_NO_MEM
  • ESP_ERR_ESPNOW_EXIST

If you want to remove unwanted peers by specifying peer_addr , here is the form of usage:

esp_err_t esp_now_del_peer(const uint8_t *peer_addr);

When the work is done, the results of the work will be reported.

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT
  • ESP_ERR_ESPNOW_ARG
  • ESP_ERR_ESPNOW_NOT_FOUND

To change the peer’s data, a new set of values must be passed to the following function.

esp_err_t esp_now_mod_peer(const esp_now_peer_info_t *peer);

Return value from function esp_now_mod_peer() namely.

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT
  • ESP_ERR_ESPNOW_ARG
  • ESP_ERR_ESPNOW_FULL

For reading peer data with position value peer_addr, a buffer must be provided for storing esp_now_peer_info_t and pass the position value to the following command to read the specified peer’s data and store it in the prepared buffer.

esp_err_t esp_now_get_peer(const uint8_t *peer_addr, esp_now_peer_info_t *peer);

The result of the work is as follows.

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT
  • ESP_ERR_ESPNOW_ARG
  • ESP_ERR_ESPNOW_NOT_FOUND

If the number of peers to be read is unknown, programmers can read from a list of peers with the following command to access the list by setting from_head to true means reading the first item from the list and if set to false, it will be the next item on every run.

esp_err_t esp_now_fetch_peer(bool from_head, esp_now_peer_info_t *peer);

Return from esp_now_fetch_peer() is as follows

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT
  • ESP_ERR_ESPNOW_ARG
  • ESP_ERR_ESPNOW_NOT_FOUND

To check if peer_addr is in the peer list, the result is true or false.

bool esp_now_is_peer_exist(const uint8_t *peer_addr);

If you want to know the number of peers added to the system, the number of peers can be counted with the esp_now_get_peer_num() command by passing the position of the peer storage variable to the following function.

esp_err_t esp_now_get_peer_num(esp_now_peer_num_t *num);

The result of the function’s execution or the return value from that function:

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT
  • ESP_ERR_ESPNOW_ARG

Commands about master

The command for setting PMK (Primary Master Key), which is the key for accessing and decrypting the communication within the network of ESP-NOW, is used as follows.

esp_err_t esp_now_set_pmk(const uint8_t *pmk);

The return values from the encryption and decryption code configuration are:

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT
  • ESP_ERR_ESPNOW_ARG

Settings for exiting power saving mode by configuring window in microseconds. which ranges from 0 to 65535 for the chip to be awakened in every window has the following format.

esp_err_t esp_now_set_wake_window(uint16_t window);

The return value of the chip wake-up time setting function is as follows:

  • ESP_OK
  • ESP_ERR_ESPNOW_NOT_INIT

Note

  1. It is effective only when the mode is ESP_WIFI_STA_DISCONNECTED_PM_ENABLE.
  2. This configuration is only possible with Station mode and when in a disconnected location.
  3. If multiple module settings are called, the chip will select the largest value as the active value.
  4. If there is a gap between time and window value less than 5ms, it means the chip is working all the time.
  5. If the window value is not set, the chip will wake up when there is no connection when using ESP-NOW.

Example Code

In the Arduino core for ESP32 kit, there are 2 examples to study:

Master-side programming algorithm

  • ESPNow Init on Master and set it in STA mode
  • Start scanning for Slave ESP32 (we have added a prefix of `slave` to the SSID of slave for an easy setup)
  • Once found, add Slave as peer
  • Register for send callback
  • Start Transmitting data from Master to Slave

Slave-side programming algorithm

  • ESPNow Init on Slave
  • Update the SSID of Slave with a prefix of `slave`
  • Set Slave in AP mode
  • Register for receive callback and wait for data
  • Once data arrives, print it in the serial monitor

Basic

The Basic example consists of a master and slave side program used to communicate with each other. The master checks if there is a slave in the system and when found it will report the information received from the slave. The slave side registers the system and sends the data out. The working example is shown in Figure 1.

Master code

/**
   ESPNOW - Basic communication - Master
   Date: 26th September 2017
   Author: Arvind Ravulavaru <https://github.com/arvindr21>
   Purpose: ESPNow Communication between a Master ESP32 and a Slave ESP32
   Description: This sketch consists of the code for the Master module.
   Resources: (A bit outdated)
   a. https://espressif.com/sites/default/files/documentation/esp-now_user_guide_en.pdf
   b. http://www.esploradores.com/practica-6-conexion-esp-now/

   << This Device Master >>

   ..

   Note: Master and Slave have been defined to easily understand the setup.
         Based on the ESPNOW API, there is no concept of Master and Slave.
         Any devices can act as master or salve.
*/

#include <esp_now.h>
#include <WiFi.h>

// Global copy of slave
esp_now_peer_info_t slave;
#define CHANNEL 3
#define PRINTSCANRESULTS 0
#define DELETEBEFOREPAIR 0

// Init ESP Now with fallback
void InitESPNow() {
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  else {
    Serial.println("ESPNow Init Failed");
    // Retry InitESPNow, add a counte and then restart?
    // InitESPNow();
    // or Simply Restart
    ESP.restart();
  }
}

// Scan for slaves in AP mode
void ScanForSlave() {
  int8_t scanResults = WiFi.scanNetworks();
  // reset on each scan
  bool slaveFound = 0;
  memset(&slave, 0, sizeof(slave));

  Serial.println("");
  if (scanResults == 0) {
    Serial.println("No WiFi devices in AP Mode found");
  } else {
    Serial.print("Found "); Serial.print(scanResults); Serial.println(" devices ");
    for (int i = 0; i < scanResults; ++i) {
      // Print SSID and RSSI for each device found
      String SSID = WiFi.SSID(i);
      int32_t RSSI = WiFi.RSSI(i);
      String BSSIDstr = WiFi.BSSIDstr(i);

      if (PRINTSCANRESULTS) {
        Serial.print(i + 1);
        Serial.print(": ");
        Serial.print(SSID);
        Serial.print(" (");
        Serial.print(RSSI);
        Serial.print(")");
        Serial.println("");
      }
      delay(10);
      // Check if the current device starts with `Slave`
      if (SSID.indexOf("Slave") == 0) {
        // SSID of interest
        Serial.println("Found a Slave.");
        Serial.print(i + 1); Serial.print(": "); Serial.print(SSID); Serial.print(" ["); Serial.print(BSSIDstr); Serial.print("]"); Serial.print(" ("); Serial.print(RSSI); Serial.print(")"); Serial.println("");
        // Get BSSID => Mac Address of the Slave
        int mac[6];
        if ( 6 == sscanf(BSSIDstr.c_str(), "%x:%x:%x:%x:%x:%x",  &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5] ) ) {
          for (int ii = 0; ii < 6; ++ii ) {
            slave.peer_addr[ii] = (uint8_t) mac[ii];
          }
        }

        slave.channel = CHANNEL; // pick a channel
        slave.encrypt = 0; // no encryption

        slaveFound = 1;
        // we are planning to have only one slave in this example;
        // Hence, break after we find one, to be a bit efficient
        break;
      }
    }
  }

  if (slaveFound) {
    Serial.println("Slave Found, processing..");
  } else {
    Serial.println("Slave Not Found, trying again.");
  }

  // clean up ram
  WiFi.scanDelete();
}

// Check if the slave is already paired with the master.
// If not, pair the slave with master
bool manageSlave() {
  if (slave.channel == CHANNEL) {
    if (DELETEBEFOREPAIR) {
      deletePeer();
    }

    Serial.print("Slave Status: ");
    // check if the peer exists
    bool exists = esp_now_is_peer_exist(slave.peer_addr);
    if ( exists) {
      // Slave already paired.
      Serial.println("Already Paired");
      return true;
    } else {
      // Slave not paired, attempt pair
      esp_err_t addStatus = esp_now_add_peer(&slave);
      if (addStatus == ESP_OK) {
        // Pair success
        Serial.println("Pair success");
        return true;
      } else if (addStatus == ESP_ERR_ESPNOW_NOT_INIT) {
        // How did we get so far!!
        Serial.println("ESPNOW Not Init");
        return false;
      } else if (addStatus == ESP_ERR_ESPNOW_ARG) {
        Serial.println("Invalid Argument");
        return false;
      } else if (addStatus == ESP_ERR_ESPNOW_FULL) {
        Serial.println("Peer list full");
        return false;
      } else if (addStatus == ESP_ERR_ESPNOW_NO_MEM) {
        Serial.println("Out of memory");
        return false;
      } else if (addStatus == ESP_ERR_ESPNOW_EXIST) {
        Serial.println("Peer Exists");
        return true;
      } else {
        Serial.println("Not sure what happened");
        return false;
      }
    }
  } else {
    // No slave found to process
    Serial.println("No Slave found to process");
    return false;
  }
}

void deletePeer() {
  esp_err_t delStatus = esp_now_del_peer(slave.peer_addr);
  Serial.print("Slave Delete Status: ");
  if (delStatus == ESP_OK) {
    // Delete success
    Serial.println("Success");
  } else if (delStatus == ESP_ERR_ESPNOW_NOT_INIT) {
    // How did we get so far!!
    Serial.println("ESPNOW Not Init");
  } else if (delStatus == ESP_ERR_ESPNOW_ARG) {
    Serial.println("Invalid Argument");
  } else if (delStatus == ESP_ERR_ESPNOW_NOT_FOUND) {
    Serial.println("Peer not found.");
  } else {
    Serial.println("Not sure what happened");
  }
}

uint8_t data = 0;
// send data
void sendData() {
  data++;
  const uint8_t *peer_addr = slave.peer_addr;
  Serial.print("Sending: "); Serial.println(data);
  esp_err_t result = esp_now_send(peer_addr, &data, sizeof(data));
  Serial.print("Send Status: ");
  if (result == ESP_OK) {
    Serial.println("Success");
  } else if (result == ESP_ERR_ESPNOW_NOT_INIT) {
    // How did we get so far!!
    Serial.println("ESPNOW not Init.");
  } else if (result == ESP_ERR_ESPNOW_ARG) {
    Serial.println("Invalid Argument");
  } else if (result == ESP_ERR_ESPNOW_INTERNAL) {
    Serial.println("Internal Error");
  } else if (result == ESP_ERR_ESPNOW_NO_MEM) {
    Serial.println("ESP_ERR_ESPNOW_NO_MEM");
  } else if (result == ESP_ERR_ESPNOW_NOT_FOUND) {
    Serial.println("Peer not found.");
  } else {
    Serial.println("Not sure what happened");
  }
}

// callback when data is sent from Master to Slave
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  char macStr[18];
  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]);
  Serial.print("Last Packet Sent to: "); Serial.println(macStr);
  Serial.print("Last Packet Send Status: "); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}

void setup() {
  Serial.begin(115200);
  //Set device in STA mode to begin with
  WiFi.mode(WIFI_STA);
  Serial.println("ESPNow/Basic/Master Example");
  // This is the mac address of the Master in Station Mode
  Serial.print("STA MAC: "); Serial.println(WiFi.macAddress());
  // Init ESPNow with a fallback logic
  InitESPNow();
  // Once ESPNow is successfully Init, we will register for Send CB to
  // get the status of Trasnmitted packet
  esp_now_register_send_cb(OnDataSent);
}

void loop() {
  // In the loop we scan for slave
  ScanForSlave();
  // If Slave is found, it would be populate in `slave` variable
  // We will check if `slave` is defined and then we proceed further
  if (slave.channel == CHANNEL) { // check if slave channel is defined
    // `slave` is defined
    // Add slave as peer if it has not been added already
    bool isPaired = manageSlave();
    if (isPaired) {
      // pair success or already paired
      // Send data to device
      sendData();
    } else {
      // slave pair failed
      Serial.println("Slave pair failed!");
    }
  }
  else {
    // No slave found to process
  }

  // wait for 3seconds to run the logic again
  delay(3000);
}

slave code

/**
   ESPNOW - Basic communication - Slave
   Date: 26th September 2017
   Author: Arvind Ravulavaru <https://github.com/arvindr21>
   Purpose: ESPNow Communication between a Master ESP32 and a Slave ESP32
   Description: This sketch consists of the code for the Slave module.
   Resources: (A bit outdated)
   a. https://espressif.com/sites/default/files/documentation/esp-now_user_guide_en.pdf
   b. http://www.esploradores.com/practica-6-conexion-esp-now/

   << This Device Slave >>

  ...

   Note: Master and Slave have been defined to easily understand the setup.
         Based on the ESPNOW API, there is no concept of Master and Slave.
         Any devices can act as master or salve.
*/

#include <esp_now.h>
#include <WiFi.h>

#define CHANNEL 1

// Init ESP Now with fallback
void InitESPNow() {
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  else {
    Serial.println("ESPNow Init Failed");
    // Retry InitESPNow, add a counte and then restart?
    // InitESPNow();
    // or Simply Restart
    ESP.restart();
  }
}

// config AP SSID
void configDeviceAP() {
  const char *SSID = "Slave_1";
  bool result = WiFi.softAP(SSID, "Slave_1_Password", CHANNEL, 0);
  if (!result) {
    Serial.println("AP Config failed.");
  } else {
    Serial.println("AP Config Success. Broadcasting with AP: " + String(SSID));
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("ESPNow/Basic/Slave Example");
  //Set device in AP mode to begin with
  WiFi.mode(WIFI_AP);
  // configure device AP mode
  configDeviceAP();
  // This is the mac address of the Slave in AP Mode
  Serial.print("AP MAC: "); Serial.println(WiFi.softAPmacAddress());
  // Init ESPNow with a fallback logic
  InitESPNow();
  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info.
  esp_now_register_recv_cb(OnDataRecv);
}

// callback when data is recv from Master
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
  char macStr[18];
  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]);
  Serial.print("Last Packet Recv from: "); Serial.println(macStr);
  Serial.print("Last Packet Recv Data: "); Serial.println(*data);
  Serial.println("");
}

void loop() {
  // Chill
}
(Figure. 1 Result of basic example)

ง Multi-Slave

Master code

/**
   ESPNOW - Basic communication - Slave
   Date: 26th September 2017
   Author: Arvind Ravulavaru <https://github.com/arvindr21>
   Purpose: ESPNow Communication between a Master ESP32 and a Slave ESP32
   Description: This sketch consists of the code for the Slave module.
   Resources: (A bit outdated)
   a. https://espressif.com/sites/default/files/documentation/esp-now_user_guide_en.pdf
   b. http://www.esploradores.com/practica-6-conexion-esp-now/

   << This Device Slave >>

   ...

   Note: Master and Slave have been defined to easily understand the setup.
         Based on the ESPNOW API, there is no concept of Master and Slave.
         Any devices can act as master or salve.
*/

#include <esp_now.h>
#include <WiFi.h>

#define CHANNEL 1

// Init ESP Now with fallback
void InitESPNow() {
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  else {
    Serial.println("ESPNow Init Failed");
    // Retry InitESPNow, add a counte and then restart?
    // InitESPNow();
    // or Simply Restart
    ESP.restart();
  }
}

// config AP SSID
void configDeviceAP() {
  const char *SSID = "Slave_1";
  bool result = WiFi.softAP(SSID, "Slave_1_Password", CHANNEL, 0);
  if (!result) {
    Serial.println("AP Config failed.");
  } else {
    Serial.println("AP Config Success. Broadcasting with AP: " + String(SSID));
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("ESPNow/Basic/Slave Example");
  //Set device in AP mode to begin with
  WiFi.mode(WIFI_AP);
  // configure device AP mode
  configDeviceAP();
  // This is the mac address of the Slave in AP Mode
  Serial.print("AP MAC: "); Serial.println(WiFi.softAPmacAddress());
  // Init ESPNow with a fallback logic
  InitESPNow();
  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info.
  esp_now_register_recv_cb(OnDataRecv);
}

// callback when data is recv from Master
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
  char macStr[18];
  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]);
  Serial.print("Last Packet Recv from: "); Serial.println(macStr);
  Serial.print("Last Packet Recv Data: "); Serial.println(*data);
  Serial.println("");
}

void loop() {
  // Chill
}

Slave code

/**
   ESPNOW - Basic communication - Slave
   Date: 26th September 2017
   Author: Arvind Ravulavaru <https://github.com/arvindr21>
   Purpose: ESPNow Communication between a Master ESP32 and multiple ESP32 Slaves
   Description: This sketch consists of the code for the Slave module.
   Resources: (A bit outdated)
   a. https://espressif.com/sites/default/files/documentation/esp-now_user_guide_en.pdf
   b. http://www.esploradores.com/practica-6-conexion-esp-now/

   << This Device Slave >>

   ...

   Note: Master and Slave have been defined to easily understand the setup.
         Based on the ESPNOW API, there is no concept of Master and Slave.
         Any devices can act as master or salve.
*/

#include <esp_now.h>
#include <WiFi.h>

#define CHANNEL 1

// Init ESP Now with fallback
void InitESPNow() {
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  else {
    Serial.println("ESPNow Init Failed");
    // Retry InitESPNow, add a counte and then restart?
    // InitESPNow();
    // or Simply Restart
    ESP.restart();
  }
}

// config AP SSID
void configDeviceAP() {
  String Prefix = "Slave:";
  String Mac = WiFi.macAddress();
  String SSID = Prefix + Mac;
  String Password = "123456789";
  bool result = WiFi.softAP(SSID.c_str(), Password.c_str(), CHANNEL, 0);
  if (!result) {
    Serial.println("AP Config failed.");
  } else {
    Serial.println("AP Config Success. Broadcasting with AP: " + String(SSID));
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("ESPNow/Basic/Slave Example");
  //Set device in AP mode to begin with
  WiFi.mode(WIFI_AP);
  // configure device AP mode
  configDeviceAP();
  // This is the mac address of the Slave in AP Mode
  Serial.print("AP MAC: "); Serial.println(WiFi.softAPmacAddress());
  // Init ESPNow with a fallback logic
  InitESPNow();
  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info.
  esp_now_register_recv_cb(OnDataRecv);
}

// callback when data is recv from Master
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
  char macStr[18];
  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]);
  Serial.print("Last Packet Recv from: "); Serial.println(macStr);
  Serial.print("Last Packet Recv Data: "); Serial.println(*data);
  Serial.println("");
}

void loop() {
  // Chill
}

Conclusion

From this article, you will find that ESP-NOW supports both One-way and Two-way communication, making it possible to design a variety of programming. Considering the amount of data that is limited to 250 bytes and does not have a complex protocol format like when using ESP8266WiFi/WiFiClient/WiFiServer with increasing security with encryption, communication is faster than doing in the article “Programming a client/server for a weather station over a wireless network“. In addition, it can be tested and used with ESP32 and ESP32-S2 well. Finally, have fun with programming.

If you want to talk with us, feel free to leave comments below!!

References

  1. espressif : API Reference : Networking APIs : ESP-NOW
  2. Arduino core for ESP32 (ESP32): esp_now.h
  3. Arduino core for ESP32 (ESP32-S2): esp_now.h
  4. Arduino core for ESP32 (ESP32-C3): esp_now.h
  5. ESP-NOW : Basic, Multi-Slave

(C) 2021, By Jarut Busarathid and Danai Jedsadathitikul
Updated 2021-12-19