Home

Guide no. 60

A button on one board, an LED on the other: direct messages using the MAC address, with no Wi-Fi network and no Internet.

By Ada Intelisano · informaticait.it technical team

Download the PDF (A4, 6 pages) Free to print and share, without changes — CC BY-ND 4.0
In short
  • ESP-NOW: messages of up to 250 bytes, directly between boards.
  • The transmitter uses the receiver's MAC.
  • Same data structure on both boards, same Wi-Fi channel.
  • The #if lines make the code work with versions 2.x and 3.x.
Time: 45 minutesLevel: next stepsBefore: guide no. 27

Two ESP32 boards sending messages to each other directly, without a router and without the Internet: a remote control in the garage, a sensor at the far end of the garden. This is ESP-NOW, a protocol by Espressif. Here you press a button on one board and the LED lights up on the other.

1How it works

ESP-NOW: board to board−+2/D927/D4ESP32-WROOM-32EespressifFireBeetle 2 ESP32-ERST3V3GNDENSCKMOSIMISOSCLSDAD9D7D6D5D3D2TXRXVCC3V3GNDSCLSDAA4A3A2A1A0D13D12D11D102/D927/D4ESP32-WROOM-32EespressifFireBeetle 2 ESP32-ERST3V3GNDENSCKMOSIMISOSCLSDAD9D7D6D5D3D2TXRXVCC3V3GNDSCLSDAA4A3A2A1A0D13D12D11D10direct message, no routertransmitterreceiverinformaticait.it
New word: MAC addressThe “license plate” of each board's Wi-Fi part: six pairs of digits like 24:6F:28:1A:2B:3C. The transmitter must know the receiver's address to send it messages.
New word: ESP-NOWShort messages (up to 250 bytes), without joining a network. A board can be paired with up to 20 others. The boards must be on the same Wi-Fi channel: in this example neither of them connects to the home router, so they stay on the starting channel.

2Read the receiver's MAC

// ESP-NOW, step zero: prints the board's MAC address. You need the one of the receiving board.
#include <WiFi.h>

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);             // the MAC must be read with Wi-Fi in station mode
  delay(500);
  Serial.print("MAC address: ");
  Serial.println(WiFi.macAddress());
}

void loop() {}

Upload this sketch to the board that will act as the receiver and write down the address that appears in the Serial Monitor (115200).

3The receiver

Upload the sketch below to the same board. It uses the LED already mounted on the FireBeetle (GPIO 2): no wires.

// ESP-NOW, the receiving board: turns the LED on or off according to the message.
#include <WiFi.h>
#include <esp_now.h>

const int LED = 2;                 // the LED on the board

typedef struct {                   // identical to the transmitter's
  int counter;
  bool on;
} Message;

#if ESP_ARDUINO_VERSION_MAJOR >= 3   // since version 3.0 the first argument is a structure, not the MAC
void received(const esp_now_recv_info_t* info, const uint8_t* data, int length) {
#else
void received(const uint8_t* mac, const uint8_t* data, int length) {
#endif
  if (length != sizeof(Message)) return;   // not one of our messages
  Message msg;
  memcpy(&msg, data, sizeof(msg));
  digitalWrite(LED, msg.on ? HIGH : LOW);
  Serial.printf("Message no. %d: LED %s\n", msg.counter, msg.on ? "on" : "off");
}

void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  WiFi.mode(WIFI_STA);
  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW does not start");
    return;
  }
  esp_now_register_recv_cb(received);
}

void loop() {}

4The transmitter

The transmitter button−+2/D927/D4ESP32-WROOM-32EespressifFireBeetle 2 ESP32-ERST3V3GNDENSCKMOSIMISOSCLSDAD9D7D6D5D3D2TXRXVCC3V3GNDSCLSDAA4A3A2A1A0D13D12D11D10button between D7 (GPIO 13) and GNDinformaticait.it

On the second board, connect a pushbutton between D7 (GPIO 13) and GND. In the sketch, on the receiver[] line, write the MAC you noted in step 2: 24:6F:28:1A:2B:3C becomes {0x24, 0x6F, 0x28, 0x1A, 0x2B, 0x3C}.

New word: structtypedef struct groups several values into one package, here a number and a yes/no. The transmitter sends it as it is; the receiver, which has the exact same structure, puts it back in order.
// ESP-NOW, the board with the button: each press sends a message to the other board, no router needed.
#include <WiFi.h>
#include <esp_now.h>

uint8_t receiver[] = {0x24, 0x6F, 0x28, 0x00, 0x00, 0x00};   // write the MAC of the receiving board here
const int BUTTON = 13;             // D7: button between this pin and GND

typedef struct {                   // the message: the same on both boards
  int counter;
  bool on;
} Message;
Message msg = {0, false};

#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 3, 0)   // since version 3.3 the function receives other data
void sent(const wifi_tx_info_t* info, esp_now_send_status_t status) {
#else
void sent(const uint8_t* mac, esp_now_send_status_t status) {
#endif
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivered" : "Not delivered");
}

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON, INPUT_PULLUP);
  WiFi.mode(WIFI_STA);
  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW does not start");
    return;
  }
  esp_now_register_send_cb(sent);
  esp_now_peer_info_t peer = {};
  memcpy(peer.peer_addr, receiver, 6);
  peer.channel = 0;                // 0 = the channel the board is already on
  peer.encrypt = false;
  esp_now_add_peer(&peer);
}

void loop() {
  if (digitalRead(BUTTON) == LOW) {
    msg.counter++;
    msg.on = !msg.on;              // each press: on, off, on...
    esp_now_send(receiver, (uint8_t*)&msg, sizeof(msg));
    delay(300);                    // wait for the button to stop bouncing
  }
}

5Try it

✓ What you should seePower both boards, even from two USB chargers. Press the button: the receiver's LED turns on; press it again: it turns off. In the transmitter's monitor you read “Delivered”, in the receiver's the message number.
If it doesn't work
  • “Not delivered”: the MAC written in the transmitter is not the receiver's, or the receiver is off.
  • “Delivered” but the LED does not change: the Message structures on the two boards are not identical.
  • Compile errors in the callback functions: you are using a different version of the ESP32 package; the #if lines cover 2.x and 3.x, check that you copied them.

6Two things to know

  • “Delivered” means that the message reached the other board's radio, not that the sketch used it: to be sure, the receiver can send a reply.
  • If one of the two boards also connects to the home Wi-Fi, it moves to the router's channel: the other one must be on the same channel.
  • The functions that receive and confirm messages run inside the Wi-Fi system: they must be short, no delay() in there.

7Challenge yourself

Try it yourself
  • Send the BME280 temperature (guide no. 56) inside the structure as well.
  • Instead of the LED, have the receiver drive the relay from guide no. 43: a real remote control.
  • Use the address FF:FF:FF:FF:FF:FF to send the same message to all the boards nearby.
✓ What you learned
  • ESP-NOW links two ESP32 boards without a router, with messages of up to 250 bytes;
  • the transmitter must know the receiver's MAC;
  • the two boards must use the same structure and the same channel.
Sources: Espressif, ESP-IDF: ESP-NOW and migration guides 5.0 and 5.5 · Espressif, arduino-esp32: ESP_NOW library and examples · DFRobot, FireBeetle 2 ESP32-E wiki · informaticait guides no. 27, 43 and 56

Useful products

Other guides

All our guides →
Loading...
Back to top