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
- 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.
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
24:6F:28:1A:2B:3C. The transmitter must know the receiver's address to send it messages.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
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}.
typedef 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
- “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
Messagestructures on the two boards are not identical. - Compile errors in the callback functions: you are using a different version of the ESP32 package; the
#iflines 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
- 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:FFto send the same message to all the boards nearby.
- 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.
Useful products
Other guides
- Resistor colour code and tolerances
- N-channel and P-channel MOSFETs
- Bipolar transistor (BJT) or MOSFET?
- Relays: how to drive them and how much current they draw
- THT and SMD/SMT components
- XIAO board pinouts
- LEDs: forward voltage and series resistor
- The multimeter: measuring voltage and current
- Wemos D1 mini: first steps
- MOSFET module with Arduino and ESP32
- Relay module with Arduino and ESP32
- 3D printing filaments: PLA, PETG and fibre-filled versions
- Analog and digital: the differences
- ADS1115: 16-bit converter with Arduino and ESP32
- From bits to real values: converting sensor readings
- DS3231 RTC clock with Arduino and ESP32
- Linear or switching regulator: which one to choose
- Step-down, step-up and buck-boost: choosing the right module
- Voltage divider: reading voltages higher than 5 V or 3.3 V
- Pull-up, pull-down and bounce: wiring a button properly
- 3.3 V and 5 V logic levels: getting an ESP32 and an Arduino to talk
- The I²C bus: addresses, pull-ups and the scanner
- Lithium batteries: voltages, charging and protection
- ESP32 power consumption and deep sleep: how long the battery lasts
- Temperature sensors compared
- ESP32: which pins to use and which to avoid
- Getting started with the ESP32 in Arduino IDE 2 and PlatformIO
- SSD1306 OLED display and 16×2 I²C LCD with Arduino and ESP32
- PIR motion sensor and HC-SR04 ultrasonic sensor
- Date and time from the Internet with the ESP32: NTP, Italian time zone and daylight saving time
- MicroSD data logger: saving measurements to CSV
- Solar-powered ESP32: panel, charger and the numbers for Italy
- Web server on the ESP32: control a relay from your phone
- Your first LED: wire it up and make it blink
- The button: let Arduino decide
- The potentiometer: turn a knob and read its value
- Soft light: adjusting the brightness of an LED
- The buzzer: playing notes and melodies
- The traffic light: three LEDs in sequence
- The thermometer: reading the temperature with the TMP36
- The servo motor: moving an arm with Arduino
- The night light: the photoresistor and your first decision
- The relay: switching on a light bulb with Arduino
- The LCD display: making Arduino write
- The electronic dice: your first complete project
- The RGB LED: every color from three lights
- The joystick: two-axis control with a shield
- The LED ring: twelve colors from a single pin
- The DC motor: driving it with a MOSFET
- Soil moisture: the plant that asks for water
- The stepper motor: turn exactly as far as you want
- The parking sensor: faster and faster beeps
- The 7-segment display: big, bright numbers
- Automatic watering: the plant that waters itself
- The keypad: a lock with a secret code
- Networked weather station: values and chart on your phone
- A Telegram bot on the ESP32: your home from your phone
- MQTT with the ESP32: the language of connected devices
- Updating the ESP32 over Wi-Fi (OTA)
- WebSocket with the ESP32: the LED that follows the slider
- Readings on a Google sheet, with deep sleep
- WiFiManager: choose the Wi-Fi from your phone
- Bluetooth LE with the ESP32: the phone without a network
- The camera in your browser with the ESP32-S3
- A portable Bluetooth speaker you build yourself
- Bluetooth desktop speakers, 2 × 20 W
- Stereo MP3 player with two buttons
- The shelf that plays music: a Visaton exciter
- How to solder well
- How much does it draw? Current and power with the INA219
- Load-cell scale with the HX711
- CO2 traffic light: when to open the windows
- RFID card door opener
- GPS: track logger on microSD
- LoRa: how far does it go? A range test with two boards
- Battery weather sign with an e-paper display
- Web radio with an ESP32 and an I2S amplifier
- Getting started with the Raspberry Pi Pico 2 in MicroPython
- Your first amplifier: the LM386 and a speaker
- Talking Arduino: the DFR0760 speech synthesis module
- The spectrum of your music on an LED matrix
- Drawing with 64 LEDs: the 8×8 matrix with the MAX7219
- A real voltage: the 12-bit MCP4725 DAC
- The heartbeat on Arduino: an ECG with the AD8232