ESP-NOW Serial

About this Post

ESP-NOW Serial was introduced with the update of the ESP32 board package for Arduino to version 3.x. It is an ESP-NOW feature that impresses with its simple operation. I wrote a separate article about ESP-NOW, which I have since adapted to reflect the changes in the 3.x board package.

ESP-NOW offers considerably more options than ESP-NOW Serial, but ESP-NOW is more complex, especially from board package version 3.x onwards. If you simply want to exchange some data between two or more ESP32 boards, ESP-NOW Serial could be just the thing for you.

These are the topics I will discuss:

Determining the MAC address

Before we get started with ESP-NOW Serial, I would like to show you how to determine the MAC addresses of your ESP32 boards, as you will need these for communication. As a reminder: the MAC address of a device is a specific identifier consisting of six bytes with which you can uniquely identify a device in a network.

The ESP32 has a different MAC address in station mode (STA) and access point mode (AP). In the example sketches, I only use STA mode, but you could also use AP mode or set up mixed networks with participants in STA and AP mode.

Station mode

#include "WiFi.h"

void setup(){
    Serial.begin(115200);
    delay(1000);
    WiFi.mode(WIFI_STA); 
    while (!(WiFi.STA.started())) {
        delay(10);
    }
    Serial.print("MAC-Address: ");
    String mac = WiFi.macAddress();
    Serial.println(mac);
    
    Serial.print("Formated: ");
    Serial.print("{");
    int index = 0;
    for (int i=0; i<6; i++) {
        Serial.print("0x");
        Serial.print(mac.substring(index, index+2));
        if(i<5){
            Serial.print(", ");
        }
        index += 3;
    }
    Serial.println("}");
}
 
void loop(){}

Here is an output example:

Output of MAC address in station mode
Output of MAC address in station mode

Access point mode

#include <WiFi.h> 

void setup(){
    Serial.begin(115200);
    delay(1000);
    WiFi.mode(WIFI_AP); 
    while (!(WiFi.AP.started())) {
        delay(10);
    }
    Serial.print("MAC-Address: ");
    String mac = WiFi.softAPmacAddress();
    Serial.println(mac);
    
    Serial.print("Formated: ");
    Serial.print("{");
    int index = 0;
    for (int i=0; i<6; i++) {
        Serial.print("0x");
        Serial.print(mac.substring(index, index+2));
        if (i<5) {
            Serial.print(", ");
        }
        index += 3;
    }
    Serial.println("}");
   
}
 
void loop(){}

And here is the output example:

Output of MAC address in AP mode

Alternative: changing the MAC address

Alternatively, you can change the MAC address specified by the hardware. The advantage of this is that you do not have to laboriously determine the MAC addresses for each board. I have described how to do this here. The change of the MAC address is not permanent.

ESP-NOW Serial minimum example

To get started, take the following sketch and upload it to two ESP32 boards. Before doing so, you must adapt the sketches regarding the MAC addresses of the communication partner (“peer”) and save it under different names.  

#include "ESP32_NOW_Serial.h"
#include "MacAddress.h"
#include "WiFi.h"
#include "esp_wifi.h"

#define ESPNOW_WIFI_CHANNEL 1

const MacAddress peer_mac({0x94, 0x3C, 0xC6, 0x34, 0xCF, 0xA4}); // counterpart MAC address

ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA);

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);

    while (!(WiFi.STA.started())) {
        delay(100);
    }
    NowSerial.begin(115200);
    Serial.println("You can now send data to the peer using the Serial Monitor.\n");
}

void loop() {
    while (NowSerial.available()) {
        Serial.write(NowSerial.read());
    }
    while (Serial.available()) {
        NowSerial.write(Serial.read());
    }
    delay(1);
}

Now you can open a serial monitor for both sketches and send messages from one board to the other. For better readability, you should set “Both NL CR” on both sides.

Explanations to the sketch

  • Use #define ESPNOW_WIFI_CHANNEL 1 to set the channel (1 to 14). The boards must be set to the same channel.
    • Each channel has its own frequency (2412 to 2484 MHz). Not every channel may be used in every region, although channels 1 – 11 should be allowed in most countries. You can find more details here.
  • const MacAddress peer_mac({....}) is the address of your “peer”.
  • ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA); creates your ESP-NOW Serial object. You pass it the peer MAC address, the channel and the mode (here WIFI_IF_STA = WiFi Interface Station).
  • Use WIFI.Mode() to set the mode, WIFI.setChannel() to set the channel and WIFI.STA.started() to check whether the mode has been started.
  • NowSerial.begin() starts your ESP-NOW Serial object.

The main loop loop() works in the same way as you are used to from Serial or SoftwareSerial communication. NowSerial.available() checks whether data has been received. NowSerial.read() reads the next character from the receiver buffer as a byte, and NowSerial.write() writes a character to the transmitter buffer memory as a byte.

Extended sketch

Attention: when the receiving board is not available

Disconnect one of the boards from the PC and try to send it a message from the other board. Of course, this does not work. Reconnect the board and send it a message again. It still does not work! The messages will be sent until you restart the sending board. I don’t know whether this is an intentional behavior or a bug that may be fixed in the future. In the following sketches, we catch the error by restarting the sending board if the transmission fails.

I would also like to use the next two sketches to show that other functions familiar from Serial and SoftwareSerial also work with ESP-NOW Serial. I have written a transmitter and a receiver sketch for this purpose.

Transmitter sketch

#include "ESP32_NOW_Serial.h"
#include "MacAddress.h"
#include "WiFi.h"
#include "esp_wifi.h"

#define ESPNOW_WIFI_CHANNEL 1
#define SEND_PERIOD 4000

const MacAddress peer_mac({0x94, 0x3C, 0xC6, 0x34, 0xCF, 0xA4});

ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA);

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);

    while (!(WiFi.STA.started())) {
        delay(100);
    }
    NowSerial.begin(115200);
}

void loop() {
    static unsigned long lastSent = 0;
    
    if (millis() - lastSent > SEND_PERIOD) {
        int success = NowSerial.println("Hi Receiver, greetings from your peer!");
        Serial.println("Message sent"); 
        lastSent = millis();
        
        if (!success) {
            Serial.println("Connection issue - rebooting in 3 seconds");
            delay(3000);
            ESP.restart();
        }
    }
    
    if (NowSerial.available()) {
        String messageIn = NowSerial.readStringUntil('\n');
        Serial.print("Receiver feedback: ");
        Serial.println(messageIn);
        Serial.println();
    }

    delay(1);
}

 

NowSerial.println() returns the number of bytes written to the buffer. If this value is zero, the ESP32 is restarted with ESP.restart(). I think the sketch should otherwise be largely self-explanatory.  

Receiver Sketch

#include "ESP32_NOW_Serial.h"
#include "MacAddress.h"
#include "WiFi.h"
#include "esp_wifi.h"

#define ESPNOW_WIFI_CHANNEL 1

const MacAddress peer_mac({0xC8, 0xC9, 0xA3, 0xCA, 0x22, 0x70});

ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA);

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);

    while (!(WiFi.STA.started())) {
        delay(100);
    }
    NowSerial.begin(115200);
}

void loop() {
    if (NowSerial.available()) {
        Serial.print("Sender message: ");
        String messageIn = NowSerial.readStringUntil('\n');
        Serial.println(messageIn);
        NowSerial.println("Thank you!");
    }
    delay(1);
}
 

Here are the outputs:

ESP-NOW Serial - Output of the transmitter
Output transmitter sketch
ESP-NOW Serial Output of the receiver
Output receiver sketch

You can disconnect the receiver from the power supply. You will then see that the transmitter board reboots automatically. However, this only happens after the second failed transmission. The error is not noticeable with the first message.

readStringUntil('\n'); ensures that the string is evaluated immediately when the new line character is read. If you use the function readString() instead, the code waits until the timeout (default setting: 1000 milliseconds) to see if any more characters are received.

Using more than two ESP-NOW Serial network participants

You want more than two ESP32 boards to communicate with each other? No problem. All you have to do is create a separate ESP-NOW Serial object for each receiving board. And if you want to send messages to all network users, you can do this using the broadcast address FF:FF:FF:FF:FF:FF.

The following sketches illustrate the principle using three boards. In this particular example, there is one transmitter and two receivers. The transmitter sends messages to receiver 1, then receiver 2 and finally receiver 1 and 2 at intervals of three seconds.

Transmitter

The sender sketch sends the messages and waits for confirmation of receipt. If the transmission fails, there is a restart. To control which board receives the next message, I have introduced the variable sendToReceiver.

#include "ESP32_NOW_Serial.h"
#include "MacAddress.h"
#include "WiFi.h"
#include "esp_wifi.h"

#define ESPNOW_WIFI_CHANNEL 1
#define SEND_PERIOD 3000

const MacAddress peer_mac_1({0xC8, 0xC9, 0xA3, 0xCA, 0x22, 0x70}); // peer 1
const MacAddress peer_mac_2({0xC8, 0xC9, 0xA3, 0xC6, 0xFE, 0x54}); // peer 2
const MacAddress all_peers_mac({0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); // broadcast

ESP_NOW_Serial_Class NowSerial1(peer_mac_1, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA);
ESP_NOW_Serial_Class NowSerial2(peer_mac_2, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA);
ESP_NOW_Serial_Class NowSerial_all(all_peers_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA);


void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);

    while (!(WiFi.STA.started())) {
        delay(10);
    }
    NowSerial1.begin(115200);
    NowSerial2.begin(115200);
    NowSerial_all.begin(115200);
}

void loop() {
    static unsigned long lastSent = millis();
    static int sendToReceiver = 1;

    if (millis() - lastSent > SEND_PERIOD && sendToReceiver == 1) {
        int success = NowSerial1.println("Hello Receiver 1, greetings from your peer!");
        lastSent = millis();
        sendToReceiver = 2;
        checkSuccess(success);
    }

    if (millis() - lastSent > SEND_PERIOD && sendToReceiver == 2) {
        int success = NowSerial2.println("Hello Receiver 2, greetings from your peer!");
        lastSent = millis();
        sendToReceiver = 3;
        checkSuccess(success);
    }

    if (millis() - lastSent > SEND_PERIOD && sendToReceiver == 3) {
        NowSerial_all.println("Hi all, greetings from your peer!");
        lastSent = millis();
        sendToReceiver = 1;
    }

    if (NowSerial1.available()) {
        Serial.print("Receiver 1 feedback: ");
        String messageIn = NowSerial1.readStringUntil('\n');
        Serial.println(messageIn);
    }

    if (NowSerial2.available()) {
        Serial.print("Receiver 2 feedback: ");
        String messageIn = NowSerial2.readStringUntil('\n');
        Serial.println(messageIn);
    }
    delay(1);
}

void checkSuccess(int success) {
    if (!success) {
        Serial.println("Connection issue - rebooting in 3 seconds"); 
        delay(3000); 
        ESP.restart(); 
    }
}

 

Receiver

The sketches for the two receivers are identical.

#include "ESP32_NOW_Serial.h"
#include "MacAddress.h"
#include "WiFi.h"

#include "esp_wifi.h"

#define ESPNOW_WIFI_CHANNEL 1

const MacAddress peer_mac({0x94, 0x3C, 0xC6, 0x34, 0xCF, 0xA4});

ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA);

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

    WiFi.mode(WIFI_STA);
    WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);

    while (!WiFi.STA.started()) {
        delay(10);
    }

    NowSerial.begin(115200);
}

void loop() {
    if (NowSerial.available()) {
        String messageIn = NowSerial.readStringUntil('\n');
        Serial.println(messageIn);
        NowSerial.println("Message received");
    }
    delay(1);
}

Here are the outputs:

Several ESP-NOW Serial network participants - output of the transmitter
Transmitter output
Several ESP-NOW Serial network members - output of receiver 1
Receiver 1 output
Several ESP-NOW Serial network members - output receiver 2
Receiver 2 output

ESP-NOW Serial is only suitable to a limited extent for more complex networks with many and/or changing participants. In such cases, you should use ESP-NOW. Take a look at the example sketches of the ESP32 board package (File → Examples → ESP_NOW). The ESP_NOW_Network sketch in particular is very convenient, even if it is relatively difficult to digest for the average user.

Sending structures with ESP-NOW Serial

Finally, I would like to show you how to send serial structures with ESP-NOW. Structures can be used as “data containers” if you want to send different data types in one message. Once again, I’ll use the example of a weather station that sends the humidity, temperature and rain status.

Transmitter

#include "ESP32_NOW_Serial.h"
#include "MacAddress.h"
#include "WiFi.h"
#include "esp_wifi.h"

#define ESPNOW_WIFI_CHANNEL 1
#define SEND_PERIOD 4000

const MacAddress peer_mac({0x94, 0x3C, 0xC6, 0x34, 0xCF, 0xA4});

ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA);

struct weatherData {
  int humidity;
  float temperature;
  bool rain;
};

weatherData currentWeather = { 32, 25.0, false };

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);

    while (!(WiFi.STA.started())) {
        delay(100);
    }
    NowSerial.begin(115200);
}

void loop() {
    static unsigned long lastSent = 0;
    
    if (millis() - lastSent > SEND_PERIOD) {
        int success = NowSerial.write((byte*)&currentWeather, sizeof(currentWeather));
        if (success) {
            Serial.println("Message sent"); 
        }
        else {
            Serial.println("Connection issue - rebooting in 3 seconds");
            delay(3000);
            ESP.restart();
        }
        lastSent = millis();        
    }
    
    if (NowSerial.available()) {
        String messageIn = NowSerial.readStringUntil('\n');
        Serial.print("Receiver feedback: ");
        Serial.println(messageIn);
        Serial.println();
    }

    delay(1);
}

 

Receiver

#include "ESP32_NOW_Serial.h"
#include "MacAddress.h"
#include "WiFi.h"
#include "esp_wifi.h"

#define ESPNOW_WIFI_CHANNEL 1

const MacAddress peer_mac({0xC8, 0xC9, 0xA3, 0xCA, 0x22, 0x70});

ESP_NOW_Serial_Class NowSerial(peer_mac, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA);

struct weatherData {
  int humidity;
  float temperature;
  bool rain;
};

weatherData currentWeather = { 0, 0.0, false };

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.setChannel(ESPNOW_WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);

    while (!(WiFi.STA.started())) {
        delay(100);
    }
    NowSerial.begin(115200);
}

void loop() {
    if (NowSerial.available()) {
        NowSerial.readBytes((byte*)&currentWeather, sizeof(currentWeather));
        Serial.print("Humidity [%]    : ");
        Serial.println(currentWeather.humidity);
        Serial.print("Temperature [°C]: ");
        Serial.println(currentWeather.temperature, 1);
        if (currentWeather.rain) {
            Serial.println("It's raining.\n");
        }
        else {
            Serial.println("It doesn't rain.\n");
            NowSerial.println("Thank you!");
        }
    }
    delay(1);
}
 

Here are the outputs:

Transmitter output
Receiver output

Acknowledgement

I have S K on Pixabay to thank for the “NOW note” on the post image.

Leave a Reply

Your email address will not be published. Required fields are marked *