ESP8266 with ESP-NOW as an alternative to nRF24L01+

INTRODUCTION
I have just discovered the ESP-NOW system which allows ESP8266 modules to be used in a peer-to-peer system similar to an nRF24. The ESP8266 combines the wireless and microprocessor in the same small package. And using the ESP-NOW protocol there is no need for any of the complexities of WiFi.

For some time now I have been using nRF24L01+ modules for a few radio control applications. I wanted the smallest possible package so that I could fit it into an 00 Gauge model railway locomotive and I was very pleased with what I achieved using an Attiny 1634 as the microprocessor. But soldering all the small wires is very tedious.

In contrast I have made a working ESP8266-07 by soldering some 0.1" 2mm header pins and 4 x 10k resitors. You probably don't need the header pins but they make it easy to attach the resistors and connect to Tx Rx and GPIO0 for programming. I snipped off the other header pins as I will be using solder connections for my other peripherals.

I presume the ESP-NOW system can be used with regular ESP8266 boards such as a Wemos D1 if very small size is not a requirement.

OVERVIEW
I found the ESP-NOW jargon rather confusing and I got a lot of help from this video and the accompanying code

I hope the following explains the key parts of it more clearly.

In an ESP-NOW communication the module acting as CONTROLLER sends a message to the module acting as SLAVE. The controller needs to know the MAC address of the slave it wants to send data to. It is possible to set the slave mac address in your program so that it is independent of the actual ESP8266 that is acting as slave.

In the controller program the slave to which it proposes to send data is referred to as a PEER and you need to add a peer with the appropriate mac address. I believe you can add up to 20 peers but I am only using one peer "slot" and just adding it and deleting it as I need to change to communication to another slave.

You can easily have two-way communication (not quite as simple as the nRF24) because when a message is received it includes the mac address of the device that sent the message and it can use that address to create a peer and send a message back to the sender. There is a COMBO mode which is both CONTROLLER and SLAVE. The very convenient nRF24 facility to include a message in the auto-acknowledgement does not seem to exist in ESP-NOW.

The ESP-NOW system works with call-back functions which are called by the system when a message is received or when a sent-message is acknowledged (or reported as failed). You will note that the slave programs don't need any code in loop().

PERFORMANCE
My tests have shown that the time to send a message and receive an acknowledgement is about 400 µsecs and if there is no acknowledgement the time is about 8500 µsecs (presumably there is a timeout). And when I arranged for the slave to send back a reply immediately after receiving a message the time (as measured in the controller) between sending the message and getting the reply is about 900 µsecs.

A brief test outdoors showed that the communication was still working at a range of 120m and that without a completely clear line of sight.

(See example code in next Post)

...R

1 Like

EXAMPLE PROGRAMS
The following 2 pairs of examples are functionally similar to the first 2 examples in my Simple nRF24L01+ Tutorial

Simple one-way transmission from controller to slave. I am presenting the slave code first because it is where the mac address is defined and the controller must use that address.

EspnowSlave.ino

// EspnowSlave.ino

// a minimal program derived from
//          https://github.com/HarringayMakerSpace/ESP-Now

// This is the program that receives the data. (The Slave)

//=============

#include <ESP8266WiFi.h>
extern "C" {
    #include <espnow.h>
     #include <user_interface.h>
}

// it seems that the mac address needs to be set before setup() is called
//      and the inclusion of user_interface.h facilitates that
//      presumably there is a hidden call to the function initVariant()

/* Set a private Mac Address
 *  http://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
 * Note: by setting a specific MAC you can replace this slave ESP8266 device with a new one
 * and the new slave will still pick up the data from controllers which use that MAC
 */
uint8_t mac[] = {0x36, 0x33, 0x33, 0x33, 0x33, 0x33};

//==============

void initVariant() {
  WiFi.mode(WIFI_AP);
  wifi_set_macaddr(SOFTAP_IF, &mac[0]);
}

//==============

#define WIFI_CHANNEL 4

    // must match the controller struct
struct __attribute__((packed)) DataStruct {
    char text[32];
    unsigned int time;
};

DataStruct myData;

//============

void setup() {
    Serial.begin(115200); Serial.println();
    Serial.println("Starting EspnowSlave.ino");

    Serial.print("This node AP mac: "); Serial.println(WiFi.softAPmacAddress());
    Serial.print("This node STA mac: "); Serial.println(WiFi.macAddress());

    if (esp_now_init()!=0) {
        Serial.println("*** ESP_Now init failed");
        while(true) {};
    }

    esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);

    esp_now_register_recv_cb(receiveCallBackFunction);


    Serial.println("End of setup - waiting for messages");
}

//============

void loop() {

}

//============

void receiveCallBackFunction(uint8_t *senderMac, uint8_t *incomingData, uint8_t len) {
    memcpy(&myData, incomingData, sizeof(myData));
    Serial.print("NewMsg ");
    Serial.print("MacAddr ");
    for (byte n = 0; n < 6; n++) {
        Serial.print (senderMac[n], HEX);
    }
    Serial.print("  MsgLen ");
    Serial.print(len);
    Serial.print("  Text ");
    Serial.print(myData.text);
    Serial.print("  Time ");
    Serial.print(myData.time);
    Serial.println();
}

EspnowController.ino

// EspnowController.ino

// a minimal program derived from
//          https://github.com/HarringayMakerSpace/ESP-Now

// This is the program that sends the data. (The Controller)

//=============

#include <ESP8266WiFi.h>
extern "C" {
    #include <espnow.h>
}

    // this is the MAC Address of the slave which receives the data
uint8_t remoteMac[] = {0x36, 0x33, 0x33, 0x33, 0x33, 0x33};

#define WIFI_CHANNEL 4

    // must match the slave struct
struct __attribute__((packed)) DataStruct {
    char text[32];
    unsigned long time;
};

DataStruct myData;

unsigned long lastSentMillis;
unsigned long sendIntervalMillis = 1000;
unsigned long sentMicros;
unsigned long ackMicros;

unsigned long lastBlinkMillis;
unsigned long fastBlinkMillis = 200;
unsigned long slowBlinkMillis = 700;
unsigned long blinkIntervalMillis = slowBlinkMillis;

byte ledPin = 14;


//==============

void setup() {
    Serial.begin(115200); Serial.println();
    Serial.println("Starting EspnowController.ino");

    WiFi.mode(WIFI_STA); // Station mode for esp-now controller
    WiFi.disconnect();

    Serial.printf("This mac: %s, ", WiFi.macAddress().c_str());
    Serial.printf("slave mac: %02x%02x%02x%02x%02x%02x", remoteMac[0], remoteMac[1], remoteMac[2], remoteMac[3], remoteMac[4], remoteMac[5]);

    Serial.printf(", channel: %i\n", WIFI_CHANNEL);

    if (esp_now_init() != 0) {
        Serial.println("*** ESP_Now init failed");
        while(true) {};
    }
    esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
    esp_now_add_peer(remoteMac, ESP_NOW_ROLE_SLAVE, WIFI_CHANNEL, NULL, 0);

    esp_now_register_send_cb(sendCallBackFunction);

    strcpy(myData.text, "Hello World");
    Serial.print("Message "); Serial.println(myData.text);

    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, HIGH);
    delay(500);
    digitalWrite(ledPin, LOW);

    Serial.println("Setup finished");

}

//==============

void loop() {
    sendData();
    blinkLed();
}

//==============

void sendData() {
    if (millis() - lastSentMillis >= sendIntervalMillis) {
        lastSentMillis += sendIntervalMillis;
        myData.time = millis();
        uint8_t bs[sizeof(myData)];
        memcpy(bs, &myData, sizeof(myData));
        sentMicros = micros();
        esp_now_send(NULL, bs, sizeof(myData)); // NULL means send to all peers
        Serial.println("sent data");
    }
}

//==============

void sendCallBackFunction(uint8_t* mac, uint8_t sendStatus) {
    ackMicros = micros();
    Serial.print("Trip micros "); Serial.println(ackMicros - sentMicros);
    Serial.printf("Send status = %i", sendStatus);
    Serial.println();
    Serial.println();
    if (sendStatus == 0) {
        blinkIntervalMillis = fastBlinkMillis;
    }
    else {
        blinkIntervalMillis = slowBlinkMillis;
    }
}

//================

void blinkLed() {
    if (millis() - lastBlinkMillis >= blinkIntervalMillis) {
        lastBlinkMillis += blinkIntervalMillis;
        digitalWrite(ledPin, ! digitalRead(ledPin));
    }
}

(More in next Post)

...R

1 Like

Simple two-way communication in which the slave sends a reply when it receives a message.

EspnowTwoWaySlave.ino

// EspnowTwoWaySlave.ino

// a minimal program derived from
//          https://github.com/HarringayMakerSpace/ESP-Now

// This is the program that receives the data and sends a reply. (The Slave)

//=============

#include <ESP8266WiFi.h>
extern "C" {
    #include <espnow.h>
     #include <user_interface.h>
}

// it seems that the mac address needs to be set before setup() is called
//      and the inclusion of user_interface.h facilitates that
//      presumably there is a hidden call to initVariant()

/* Set a private Mac Address
 *  http://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
 * Note: the point of setting a specific MAC is so you can replace this Gateway ESP8266 device with a new one
 * and the new gateway will still pick up the remote sensors which are still sending to the old MAC
 */
uint8_t mac[] = {0x36, 0x33, 0x33, 0x33, 0x33, 0x33};

//=============

void initVariant() {
  WiFi.mode(WIFI_AP);
  wifi_set_macaddr(SOFTAP_IF, &mac[0]);
}

//==============


#define WIFI_CHANNEL 4

    // keep in sync with slave struct
struct __attribute__((packed)) DataStruct {
    char text[32];
    unsigned int time;
};

DataStruct receivedData;

DataStruct replyData;

//~ uint8_t incomingData[sizeof(receivedData)];


//============

void setup() {
    Serial.begin(115200); Serial.println();
    Serial.println("Starting EspnowTwoWaySlave.ino");

    Serial.print("This node AP mac: "); Serial.println(WiFi.softAPmacAddress());
    Serial.print("This node STA mac: "); Serial.println(WiFi.macAddress());

    if (esp_now_init()!=0) {
        Serial.println("*** ESP_Now init failed");
        while(true) {};
    }
        // role set to COMBO so it can receive and send - not sure this is essential
    esp_now_set_self_role(ESP_NOW_ROLE_COMBO);

    esp_now_register_recv_cb(receiveCallBackFunction);


    strcpy(replyData.text, "Goodnight John-Boy");
    Serial.print("Message "); Serial.println(replyData.text);


    Serial.println("End of setup - waiting for messages");
}

//============

void loop() {

}

//============

void receiveCallBackFunction(uint8_t *senderMac, uint8_t *incomingData, uint8_t len) {
    memcpy(&receivedData, incomingData, sizeof(receivedData));
    Serial.print("NewMsg ");
    Serial.print("MacAddr ");
    for (byte n = 0; n < 6; n++) {
        Serial.print (senderMac[n], HEX);
    }
    Serial.print("  MsgLen ");
    Serial.print(len);
    Serial.print("  Name ");
    Serial.print(receivedData.text);
    Serial.print("  Time ");
    Serial.print(receivedData.time);
    Serial.println();

    sendReply(senderMac);
}

//=============

void sendReply(uint8_t *macAddr) {
        // create a peer with the received mac address
    esp_now_add_peer(macAddr, ESP_NOW_ROLE_COMBO, WIFI_CHANNEL, NULL, 0);

    replyData.time = millis();
    uint8_t byteArray[sizeof(replyData)];
    memcpy(byteArray, &replyData, sizeof(replyData));

    esp_now_send(NULL, byteArray, sizeof(replyData)); // NULL means send to all peers
    Serial.println("sendReply sent data");

        // data sent so delete the peer
    esp_now_del_peer(macAddr);
}

EspnowTwoWayController.ino

// EspnowTwoWayController.ino

// a minimal program derived from
//          https://github.com/HarringayMakerSpace/ESP-Now

// This is the program that sends the data and receives the reply. (The Controller)

//=============

#include <ESP8266WiFi.h>
extern "C" {
    #include <espnow.h>
}

    // this is the MAC Address of the slave which receives these sensor readings
uint8_t remoteMac[] = {0x36, 0x33, 0x33, 0x33, 0x33, 0x33};

#define WIFI_CHANNEL 4

    // must match slave struct
struct __attribute__((packed)) DataStruct {
    char text[32];
    unsigned long time;
};

DataStruct sendingData;

DataStruct receivedData;
    // receivedData could use a completely different struct as long as it matches
    //   the reply that is sent by the slave

unsigned long lastSentMillis;
unsigned long sendIntervalMillis = 1000;
unsigned long sentMicros;
unsigned long ackMicros;
unsigned long replyMicros;

unsigned long lastBlinkMillis;
unsigned long fastBlinkMillis = 200;
unsigned long slowBlinkMillis = 700;
unsigned long blinkIntervalMillis = slowBlinkMillis;

byte ledPin = 14;

//==============

void setup() {
    Serial.begin(115200); Serial.println();
    Serial.println("Starting EspnowTwoWayController.ino");

    WiFi.mode(WIFI_STA); // Station mode for esp-now controller
    WiFi.disconnect();

    Serial.printf("This mac: %s, ", WiFi.macAddress().c_str());
    Serial.printf("target mac: %02x%02x%02x%02x%02x%02x", remoteMac[0], remoteMac[1], remoteMac[2], remoteMac[3], remoteMac[4], remoteMac[5]);
    Serial.printf(", channel: %i\n", WIFI_CHANNEL);

    if (esp_now_init() != 0) {
        Serial.println("*** ESP_Now init failed");
        while(true) {};
    }
        // role set to COMBO so it can send and receive - not sure this is essential
    esp_now_set_self_role(ESP_NOW_ROLE_COMBO);

    esp_now_add_peer(remoteMac, ESP_NOW_ROLE_COMBO, WIFI_CHANNEL, NULL, 0);


    esp_now_register_send_cb(sendCallBackFunction);
    esp_now_register_recv_cb(receiveCallBackFunction);

    strcpy(sendingData.text, "Hello World");
    Serial.print("Message "); Serial.println(sendingData.text);

    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, HIGH);
    delay(500);
    digitalWrite(ledPin, LOW);
    Serial.println("Setup finished");

}

//==============

void loop() {
    sendData();
    blinkLed();
}

//==============

void sendData() {
    if (millis() - lastSentMillis >= sendIntervalMillis) {
        lastSentMillis += sendIntervalMillis;
        sendingData.time = millis();
        uint8_t byteArray[sizeof(sendingData)];
        memcpy(byteArray, &sendingData, sizeof(sendingData));
        sentMicros = micros();
        esp_now_send(NULL, byteArray, sizeof(sendingData)); // NULL means send to all peers
        Serial.println("Loop sent data");
    }
}

//==============

void sendCallBackFunction(uint8_t* mac, uint8_t sendStatus) {
    ackMicros = micros();
    Serial.print("Trip micros "); Serial.println(ackMicros - sentMicros);
    Serial.printf("Send status = %i", sendStatus);
    Serial.println();
    if (sendStatus == 0) {
        blinkIntervalMillis = fastBlinkMillis;
    }
    else {
        blinkIntervalMillis = slowBlinkMillis;
    }

}

//================

void receiveCallBackFunction(uint8_t *senderMac, uint8_t *incomingData, uint8_t len) {
    replyMicros = micros();
    Serial.print("Reply Trip micros "); Serial.println(replyMicros - sentMicros);
    memcpy(&receivedData, incomingData, sizeof(receivedData));
    Serial.print("NewReply ");
    Serial.print("MacAddr ");
    for (byte n = 0; n < 6; n++) {
        Serial.print (senderMac[n], HEX);
    }
    Serial.print("  MsgLen ");
    Serial.print(len);
    Serial.print("  Text ");
    Serial.print(receivedData.text);
    Serial.print("  Time ");
    Serial.print(receivedData.time);
    Serial.println();
    Serial.println();
}

//================

void blinkLed() {
    if (millis() - lastBlinkMillis >= blinkIntervalMillis) {
        lastBlinkMillis += blinkIntervalMillis;
        digitalWrite(ledPin, ! digitalRead(ledPin));
    }
}

Have fun ...

...R

1 Like

Reserved for future use

...R

Very interesting, Robin2! Thanks.
I have read about EPS-NOW but never tested.
nRF24L01+ should uses less power than ESP8266 using WiFi since it can be driven using the 3.3V Arduino rail (but a cap is useful!). I don't know if in ESP-NOW mode the ESP8266 uses less current than WiFi mode.

zoomx:
nRF24L01+ should uses less power than ESP8266 using WiFi since it can be driven using the 3.3V Arduino rail (but a cap is useful!). I don't know if in ESP-NOW mode the ESP8266 uses less current than WiFi mode.

I have not considered that. I know the ESP8266 has a deep sleep mode but that is not practical for my immediate application.

The correct comparison is the combined consumption of the Attiny1634 plus nRF24 vs the ESP8266.

Prompted by your comment my initial measurements suggest the ESP8266 draws 80mA and my Attiny + nRF24 draws 30mA.

[paragraph deleted - added no value]

I will need to consider whether my little radio-control application can afford the extra current.

...R

ESP8266 draws 80mA but has short peak at around 300mA
nRF24 draws 30mA but has short peaks too, that's the reason you should add a cap.

Deep sleep on ESP8266 is different fron the Arduino one, when it awake it restart like a reset and the power consuption is very high.
https://www.esp8266.com/viewtopic.php?f=13&t=3875

Thanks for this introudction!

I want to transfere from ESP-now to MQTT.

My plan is to modify the master scetch such that the transmitted info would be like "TemperatureOutdoor,21.3", and that the ESP to MQTT part will use the TemperatureOutdoor as the MQTT topic and 21.3 as the payload. I do not know if "," is the best character to use for this.

My set-up is the slave and master scetches from above and an additional ESP2866 connected to the slave by Tx-> Rx, Rx->Tx and gnd->Gnd.

Using the code below I can transfere the complete transmission from the master to MQTT, it will be posted in the MQTT topic "test". However, despite many many atempts I have not been able to spit the message into two parts (i.e "TemperatureOutdoor" and use this as the MQTT topic instead of "test" and use "21.3" as MQTT payload).

Is there some one that can help me to do this?

The Slave to MQTT code is attached below.

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "yyy";
const char* password = "xxxxx";
const char* mqtt_server = "192.168.2.171";

const byte numChars = 100;
char receivedChars[numChars]; // an array to store the received data
boolean newData = false;

WiFiClient espClient;
PubSubClient client(espClient);

void setup() {
  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void setup_wifi() {
  delay(10);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

String macToStr(const uint8_t* mac)
{
  String result;
  for (int i = 0; i < 6; ++i) {
    result += String(mac[i], 16);
    if (i < 5)
      result += ':';
  }
  return result;
}

void reconnect() {
  String clientName;
  clientName += "esp8266-";
  uint8_t mac[6];
  WiFi.macAddress(mac);
  clientName += macToStr(mac);
  clientName += "-";
  clientName += String(micros() & 0xff, 16);
  while (!client.connected()) {
    if (client.connect((char*) clientName.c_str())) { // random client id
      digitalWrite(BUILTIN_LED, LOW); // low = high, so this turns on the led
      client.subscribe("test"); // callback: mqtt bus -> arduino
    } else {
      digitalWrite(BUILTIN_LED, HIGH); // high = low, so this turns off the led
      delay(5000);
    }
  }
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  recvWithEndMarker();
  showNewData();
}

void recvWithEndMarker() {
  static byte ndx = 0;
  char endMarker = '\n';
  char rc;
 
  while (Serial.available() > 0 && newData == false) {
    rc = Serial.read();

    if (rc != endMarker) {
      receivedChars[ndx] = rc;
      ndx++;
      if (ndx >= numChars) {
        ndx = numChars - 1;
      }
    }
    else {
      receivedChars[ndx] = '\0'; // terminate the string
      ndx = 0;
      newData = true;
    }
  }
}

void showNewData() {
  if (newData == true) {
    client.publish("test", receivedChars); // publish: arduino -> mqtt bus
    newData = false;
  }
}

patikpatrik:
I want to transfere from ESP-now to MQTT.

Why are you asking about that here?

AFAIK ESP-NOW has nothing to do with MQTT

...R

Hello thanks for the interesting code , I would like to know if it would be possible to have 2 simultaneous peers at the same time and send data to which peer separately.

Thanks again

gcharles:
Hello thanks for the interesting code , I would like to know if it would be possible to have 2 simultaneous peers at the same time and send data to which peer separately.

I assume you mean 1 master sending to 2 slaves?

It's while since I was familiar with this because the ESP8266 uses too much current in my application. So I don't know the answer to your question.

Re-reading my earlier Replies here I suspect you could give each of the slaves the same MAC address and then they would receive the same message. It should not be difficult to set up a simple test.

You could include an ID code in the message so that the slave would know whether the message was intended for it.

You would need to ensure that only one device transmits at any one time.

...R

hi

can you explain the use of the WIFI_CHANNEL. For example how did you know in the your CONTROLLER code it is WIFI_CHANNEL 4 where it does not seem to be used at ALL in your SLAVE code ?

mars000:
can you explain the use of the WIFI_CHANNEL.

Simple answer is that I can't.

I have forgotten most that I learned while I was working on that code as I have not used it since. And, to be honest, I can't recall if I ever knew the answer to your question.

...R

mars000:
can you explain the use of the WIFI_CHANNEL. For example how did you know in the your CONTROLLER code it is WIFI_CHANNEL 4 where it does not seem to be used at ALL in your SLAVE code ?

Maybe you both need better eyes. :slight_smile:

(deleted)

To make it easy for people to help you please modify your post and use the code button </>
codeButton.png

so your code looks like this and is easy to copy to a text editor. See How to use the Forum

Your code is too long for me to study quickly without copying to my text editor. The text editor shows line numbers, identifies matching brackets and allows me to search for things like all instances of a particular variable or function.

Also please use the AutoFormat tool to indent your code for easier reading.

...R

Using the code tag the green highlight will be lost, I believe.

zoomx:
Using the code tag the green highlight will be lost, I believe.

You are quite correct. But that is not an excuse for not using code tags.

A suitable comment can easily be included in a program.

...R

I agree!

Hi Robin2!
how can i use you example for remote control?
can you write this example with the code for one button master
that to light the LED on the slave
for example:
for the master - pin buttons - 14
for the slave - pin LED - 2
i use WEMOS D1 mini Pro